8

我想从Stanford Bunny读取未重建的数据。点数据存储为多个范围图像,必须将其转换为一个大点云,就像在README中所写的那样:

These data files were obtained with a Cyberware 3030MS optical
triangulation scanner.  They are stored as range images in the "ply"
format.  The ".conf" file contains the transformations required to
bring each range image into a single coordinate system.

这是.conf -文件:

camera -0.0172 -0.0936 -0.734  -0.0461723 0.970603 -0.235889 0.0124573
bmesh bun000.ply 0 0 0 0 0 0 1
bmesh bun045.ply -0.0520211 -0.000383981 -0.0109223 0.00548449 -0.294635 -0.0038555 0.955586
bmesh bun090.ply 2.20761e-05 -3.34606e-05 -7.20881e-05 0.000335889 -0.708202 0.000602459 0.706009
bmesh bun180.ply 0.000116991 2.47732e-05 -4.6283e-05 -0.00215148 0.999996 -0.0015001 0.000892527
bmesh bun270.ply 0.000130273 1.58623e-05 0.000406764 0.000462632 0.707006 -0.00333301 0.7072
bmesh top2.ply -0.0530127 0.138516 0.0990356 0.908911 -0.0569874 0.154429 0.383126
bmesh top3.ply -0.0277373 0.0583887 -0.0796939 0.0598923 0.670467 0.68082 -0.28874
bmesh bun315.ply -0.00646017 -1.36122e-05 -0.0129064 0.00449209 0.38422 -0.00976512 0.923179
bmesh chin.ply 0.00435102 0.0882863 -0.108853 -0.441019 0.213083 0.00705734 0.871807
bmesh ear_back.ply -0.0829384 0.0353082 0.0711536 0.111743 0.925689          -0.215443 -0.290169

对于每个范围图像,存储了七个值。但我不知道,从这些值中可以得到什么信息。我想其中三个将包含一些关于翻译的信息,也许三个包含关于旋转的信息。但是我没有找到关于这些值的顺序以及如何转换这些值以获得一个点云的信息。

wiki 页面不处理范围图像,我在斯坦福页面上找不到更多内容。他们只是说,Turk94的方法用于扫描这个数据集,但是该方法没有关于所需转换的信息。(或者我无法从这篇论文中获取信息。)

有人知道如何正确读取这些值吗?为什么相机位置会发生变化?这只是查看整个点云的一个很好的初始值吗?

谢谢你的帮助。

编辑:

行。此时,我已经尝试读取数据并正确转换它们,但一切都不起作用。我使用 boost 库来处理四元数

这是我的代码:

boost::math::quaternion<double> translation, quaternionRotation;
//Get Transformation
translation = boost::math::quaternion<double>(0.0, lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble());
quaternionRotation = boost::math::quaternion<double>(lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble(),lineData[8].toDouble());

//do some file related stuff 
//...

//for each line: read the point data and transform it and store the point in a data array
pointData[j].x = stringPointData[0].toDouble();
pointData[j].y = stringPointData[1].toDouble();
pointData[j].z = stringPointData[2].toDouble();
tmpQuat = boost::math::quaternion<double> (0.0,pointData[j].x,pointData[j].y,pointData[j].z);
//first translation
tmpQuat += translation;
//then quaternion rotation
tmpQuat = (quaternionRotation * (tmpQuat) * boost::math::conj(quaternionRotation));
//read the data from quaternion to a usual type
pointData[j].x = tmpQuat.R_component_2();
pointData[j].y = tmpQuat.R_component_3();
pointData[j].z = tmpQuat.R_component_4();

我假设四元数的第一个组件是w组件,其他组件指的是,x就像等式 2 中的一样。如有必要,我可以提供错误转换的屏幕截图。yz

编辑:它写在 zipper.c 文件中的 zipper 源代码中,这 7 个值保存如下:

transX transY transZ quatX quatY quatZ quatW

然后将四元数转换为旋转矩阵,然后使用这个新矩阵执行旋转。但即使有了这些信息,我也无法正确转换它。为了测试它,我在我的项目中从 zipper 中实现了函数 quat_to_mat():

glm::dmat4 cPlyObjectLoader::quat_to_mat(boost::math::quaternion<double> quat) const
{
 float s;
 float xs,ys,zs;
 float wx,wy,wz;
 float xx,xy,xz;
 float yy,yz,zz;
 glm::dmat4 mat(1.0);

 s = 2 / (quat.R_component_2()*quat.R_component_2() +
          quat.R_component_3()*quat.R_component_3() + 
          quat.R_component_4()*quat.R_component_4() + 
          quat.R_component_1()*quat.R_component_1());

 xs = quat.R_component_2() * s;
 ys = quat.R_component_3() * s;
 zs = quat.R_component_4() * s;

 wx = quat.R_component_1() * xs;
 wy = quat.R_component_1() * ys;
 wz = quat.R_component_1() * zs;

 xx = quat.R_component_2() * xs;
 xy = quat.R_component_2() * ys;
 xz = quat.R_component_2() * zs; 

 yy = quat.R_component_3() * ys;
 yz = quat.R_component_3() * zs;
 zz = quat.R_component_4() * zs;

 mat[0][0] = 1 - (yy + zz);
 mat[0][1] = xy - wz;
 mat[0][2] = xz + wy;
 mat[0][3] = 0;

 mat[1][0] = xy + wz;
 mat[1][1] = 1 - (xx + zz);
 mat[1][2] = yz - wx;
 mat[1][3] = 0;

 mat[2][0] = xz - wy;
 mat[2][1] = yz + wx;
 mat[2][2] = 1 - (xx + yy);
 mat[2][3] = 0;

 mat[3][0] = 0;
 mat[3][1] = 0;
 mat[3][2] = 0;
 mat[3][3] = 1;

 return mat;
}

现在我正在使用向量和这个矩阵进行平移和旋转:

quaternionRotation = boost::math::quaternion<double>(lineData[8].toDouble(),lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble());
rotationMat = this->quat_to_mat(quaternionRotation);
translationVec = glm::dvec4(lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble(),0.0);

//same stuff as above
//...

glm::dvec4 curPoint =   glm::dvec4(pointData[j].x,pointData[j].y,pointData[j].z,1.0);
curPoint += translationVec;
curPoint = rotationMat*curPoint;

结果与我的四元数旋转不同(为什么?应该是一样的。),但不正确。

调试信息:

  • 所有转换的输入都是正确的
  • 所有点的输入都是正确的
4

5 回答 5

7

正如我从stanford 3d 扫描中看到的那样

对于所有斯坦福模型,对齐是使用修改后的 ICP 算法完成的,如本文所述。这些对齐存储在“.conf”文件中,其中列出了模型中的每个范围图像以及平移和四元数旋转。

这是“本文”的链接

编辑:这两种方法称为拉链和体积合并

于 2015-06-11T14:34:38.493 回答
6

正如 Ello 所提到的,它写在stanford 3D repo中:

对于所有斯坦福模型,对齐是使用修改后的 ICP 算法完成的,如本文所述。这些对齐存储在“.conf”文件中,其中列出了模型中的每个范围图像以及平移和四元数旋转。

但这还不足以理解这个数据文件的所有内容。

第一行是正确的:

camera -0.0172 -0.0936 -0.734  -0.0461723 0.970603 -0.235889 0.0124573

存储一个良好的初始相机位置,并且以开头的每隔一行bmesh引用一个.ply-file,该文件存储一个范围图像。

转换值存储如下:

transX transY transZ quatX quatY quatZ quatW

其中trans...指的是平移值,quat...指的是四元数的值。目前,我不知道为什么它不能单独使用四元数旋转,但是通过使用zipper代码将其转换为旋转矩阵,转换是正确的。请注意,首先存储平移,但要获得正确的变换,必须在开始时进行旋转,然后进行平移。

我读取文件并对其进行转换的代码片段如下:

boost::math::quaternion<double> translation, quaternionRotation;
//Get Transformation
translationVec = glm::dvec4(lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble(),0.0);
quaternionRotation = boost::math::quaternion<double>(lineData[8].toDouble(),lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble());
//calculate the unit quaternion
double magnitude = std::sqrt(
      quaternionRotation.R_component_1()*quaternionRotation.R_component_1()+
      quaternionRotation.R_component_2()*quaternionRotation.R_component_2()+
      quaternionRotation.R_component_3()*quaternionRotation.R_component_3()+
      quaternionRotation.R_component_4()*quaternionRotation.R_component_4());
quaternionRotation /= magnitude;
rotationMat = this->quat_to_mat(quaternionRotation);

//do some file related stuff 
//...

//for each line: read the point data and transform it and store the point in a data array
pointData[j].x = stringPointData[0].toDouble();
pointData[j].y = stringPointData[1].toDouble();
pointData[j].z = stringPointData[2].toDouble();
//transform the curren point
glm::dvec4 curPoint =  glm::dvec4(pointData[j].x,pointData[j].y,pointData[j].z,1.0);
//first rotation
curPoint = rotationMat*curPoint;
//then translation
curPoint += translationVec;
//store the data in a data array
pointData[j].x = curPoint.x;
pointData[j].y = curPoint.y;
pointData[j].z = curPoint.z;

我知道,这不是最好的,但它有效。随意自己优化它。

于 2015-07-27T10:10:58.650 回答
0

这是我写的文件转换器。它将所有扫描组合到一个文件中,每行一个点。它支持不同的文件格式(包括 Stanford .conf 文件)。

#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265
#endif

class LineInput {
  public:
    LineInput(const std::string& filename) {
        F_ = fopen(filename.c_str(), "r" ) ;
        ok_ = (F_ != 0) ;
    }
    ~LineInput() {
        if(F_ != 0) {
            fclose(F_); F_ = 0 ; 
        }
    }
    bool OK() const { return ok_ ; }
    bool eof() const { return feof(F_) ; }
    bool get_line() {
        line_[0] = '\0' ;
        // Skip the empty lines
        while(!isprint(line_[0])) {
            if(fgets(line_, MAX_LINE_LEN, F_) == 0) {
                return false ;
            }
        }
        // If the line ends with a backslash, append
        // the next line to the current line.
        bool check_multiline = true ;
        int total_length = MAX_LINE_LEN ;
        char* ptr = line_ ;
        while(check_multiline) {
            int L = strlen(ptr) ;
            total_length -= L ;
            ptr = ptr + L - 2;
            if(*ptr == '\\' && total_length > 0) {
                *ptr = ' ' ;
                ptr++ ;
                fgets(ptr, total_length, F_) ;
            } else {
                check_multiline = false ;
            }
        }
        if(total_length < 0) {
            std::cerr
                << "MultiLine longer than " 
                << MAX_LINE_LEN << " bytes" << std::endl ;
        }
        return true ;
    }
    int nb_fields() const { return field_.size() ;  }
    char* field(int i) { return field_[i] ;   }
    int field_as_int(int i) {
        int result ;
        ok_ = ok_ && (sscanf(field(i), "%d", &result) == 1) ;
        return result ;
    }
    double field_as_double(int i) {
        double result ;
        ok_ = ok_ && (sscanf(field(i), "%lf", &result) == 1) ;
        return result ;
    }
    bool field_matches(int i, const char* s) {
        return !strcmp(field(i), s) ;
    }
    void get_fields(const char* separators=" \t\r\n") {
        field_.resize(0) ;
        char* tok = strtok(line_,separators) ;
        while(tok != 0) { 
            field_.push_back(tok) ; 
            tok = strtok(0,separators) ;
        }
    }

  private:
    enum { MAX_LINE_LEN = 65535 } ;
    FILE* F_ ;
    char line_[MAX_LINE_LEN] ;
    std::vector<char*> field_ ;
    bool ok_ ;
} ;

std::string to_string(int x, int mindigits) {
    char buff[100] ;
    sprintf(buff, "%03d", x) ;
    return std::string(buff) ;
}

double M[4][4] ;

void transform(double* xyz) {
    double xyzw[4] ;
    for(unsigned int c=0; c<4; c++) {
        xyzw[c] = M[3][c] ;
    }
    for(unsigned int j=0; j<4; j++) {
        for(unsigned int i=0; i<3; i++) {
            xyzw[j] += M[i][j] * xyz[i] ;
        }
    }
    for(unsigned int c=0; c<3; c++) {
        xyz[c] = xyzw[c] / xyzw[3] ;
    }
}

bool read_frames_file(int no) {
    std::string filename = "scan" + to_string(no,3) + ".frames" ;
    std::cerr << "Reading frames from:" << filename << std::endl ;
    LineInput in(filename) ;
    if(!in.OK()) {
       std::cerr << " ... not found" << std::endl ;
       return false ;
    }
    while(!in.eof() && in.get_line()) {
        in.get_fields() ;
        if(in.nb_fields() == 17) {
            int f = 0 ;
            for(unsigned int i=0; i<4; i++) {
                for(unsigned int j=0; j<4; j++) {
                    M[i][j] = in.field_as_double(f) ; f++ ;
                }
            }
        } 
    }
    return true ;
}

bool read_pose_file(int no) {
    std::string filename = "scan" + to_string(no,3) + ".pose" ;
    std::cerr << "Reading pose from:" << filename << std::endl ;
    LineInput in(filename) ;
    if(!in.OK()) {
       std::cerr << " ... not found" << std::endl ;
       return false ;
    }
    double xyz[3] ;
    double euler[3] ;
    in.get_line() ;
    in.get_fields() ;
    xyz[0] = in.field_as_double(0) ;
    xyz[1] = in.field_as_double(1) ;
    xyz[2] = in.field_as_double(2) ;
    in.get_line() ;
    in.get_fields() ;
    euler[0] = in.field_as_double(0) * M_PI / 180.0 ;
    euler[1] = in.field_as_double(1) * M_PI / 180.0 ;
    euler[2] = in.field_as_double(2) * M_PI / 180.0 ;

   double sx = sin(euler[0]);
   double cx = cos(euler[0]);
   double sy = sin(euler[1]);
   double cy = cos(euler[1]);
   double sz = sin(euler[2]);
   double cz = cos(euler[2]);

   M[0][0] = cy*cz;
   M[0][1] = sx*sy*cz + cx*sz;
   M[0][2] = -cx*sy*cz + sx*sz;
   M[0][3] = 0.0;
   M[1][0] = -cy*sz;
   M[1][1] = -sx*sy*sz + cx*cz;
   M[1][2] = cx*sy*sz + sx*cz;
   M[1][3] = 0.0;
   M[2][0] = sy;
   M[2][1] = -sx*cy;
   M[2][2] = cx*cy;
   M[2][3] = 0.0;
   M[3][0] = xyz[0];
   M[3][1] = xyz[1];
   M[3][2] = xyz[2];
   M[3][3] = 1.0;
   return true ;
}

void setup_transform_from_translation_and_quaternion(
    double Tx, double Ty, double Tz,
    double Qx, double Qy, double Qz, double Qw
) {
    /* for unit q, just set s = 2 or set xs = Qx + Qx, etc. */

    double s = 2.0 / (Qx*Qx + Qy*Qy + Qz*Qz + Qw*Qw);

    double xs = Qx * s;
    double ys = Qy * s;
    double zs = Qz * s;

    double wx = Qw * xs;
    double wy = Qw * ys;
    double wz = Qw * zs;

    double xx = Qx * xs;
    double xy = Qx * ys;
    double xz = Qx * zs;

    double yy = Qy * ys;
    double yz = Qy * zs;
    double zz = Qz * zs;

    M[0][0] = 1.0 - (yy + zz);
    M[0][1] = xy - wz;
    M[0][2] = xz + wy;
    M[0][3] = 0.0;

    M[1][0] = xy + wz;
    M[1][1] = 1 - (xx + zz);
    M[1][2] = yz - wx;
    M[1][3] = 0.0;

    M[2][0] = xz - wy;
    M[2][1] = yz + wx;
    M[2][2] = 1 - (xx + yy);
    M[2][3] = 0.0;

    M[3][0] = Tx;
    M[3][1] = Ty;
    M[3][2] = Tz;
    M[3][3] = 1.0;
}

bool read_points_file(int no) {
    std::string filename = "scan" + to_string(no,3) + ".3d" ;
    std::cerr << "Reading points from:" << filename << std::endl ;
    LineInput in(filename) ;
    if(!in.OK()) {
       std::cerr << " ... not found" << std::endl ;
       return false ;
    }
    while(!in.eof() && in.get_line()) {
        in.get_fields() ;
        double xyz[3] ;
        if(in.nb_fields() >= 3) {
            for(unsigned int c=0; c<3; c++) {
                xyz[c] = in.field_as_double(c) ;
            }
            transform(xyz) ;
            printf("%f %f %f\n",xyz[0],xyz[1],xyz[2]) ;
        }
    }
    return true ;
}


/* only works for ASCII PLY files */
void read_ply_file(char* filename) {
    std::cerr << "Reading points from:" << filename << std::endl;
    LineInput in(filename) ;
    if(!in.OK()) {
        std::cerr << filename << ": could not open" << std::endl ;
        return;
    }
    bool reading_vertices = false;
    int nb_vertices = 0 ;
    int nb_read_vertices = 0 ;
    while(!in.eof() && in.get_line()) {
        in.get_fields();
        if(reading_vertices) {
            double xyz[3] ;
            for(unsigned int c=0; c<3; c++) {
                xyz[c] = in.field_as_double(c) ;
            }
            transform(xyz) ;
            printf("%f %f %f\n",xyz[0],xyz[1],xyz[2]) ;
            ++nb_read_vertices;
            if(nb_read_vertices == nb_vertices) {
                return;
            }
        } else if(
            in.field_matches(0,"element") &&
            in.field_matches(1,"vertex") 
        ) {
            nb_vertices = in.field_as_int(2);
        } else if(in.field_matches(0,"end_header")) {
            reading_vertices = true;
        }
    }    
}

/* For Stanford scanning repository */
void read_conf_file(char* filename) {
    LineInput in(filename) ;
    if(!in.OK()) {
        std::cerr << filename << ": could not open" << std::endl ;
        return;
    }
    while(!in.eof() && in.get_line()) {
        in.get_fields();
        if(in.nb_fields() == 0) { continue ; }
        if(in.field_matches(0,"bmesh")) {
            char* filename = in.field(1);
            // Translation vector
            double Tx = in.field_as_double(2);
            double Ty = in.field_as_double(3);
            double Tz = in.field_as_double(4);
            /// Quaternion
            double Qx = in.field_as_double(5);
            double Qy = in.field_as_double(6);
            double Qz = in.field_as_double(7);
            double Qw = in.field_as_double(8);
            setup_transform_from_translation_and_quaternion(Tx,Ty,Tz,Qx,Qy,Qz,Qw);
            read_ply_file(filename);
        }
    }
 }


int main(int argc, char** argv) {
    if(argc != 2) { return -1 ; }
    if(strstr(argv[1],".conf")) {
        read_conf_file(argv[1]);
    } else {
        int max_i = atoi(argv[1]) ;
        for(int i=0; i<=max_i; i++) {
            if(!read_frames_file(i)) {
                read_pose_file(i) ;
            }
            read_points_file(i) ;
        }
    }
    return 0 ;
}
于 2015-07-28T09:06:31.447 回答
0

好的,这是我的解决方案,因为上述方法都不适合我(注意这是在 python 中使用搅拌机的 bpy)。看来我需要转置我的 4x4 变换矩阵的旋转部分(注意我使用标准方法将四元数转换为旋转矩阵,而不是来自拉链的)。另请注意,由于我在导入或使用任何模型时使用搅拌器,它仅存储模型相对于对象世界变换的局部坐标,因此您不必这样做point = objWorld * point,它是搅拌器特定的。

#loop
for meshName, transform in zip(plyFile, transformations):
    #Build Quaternion
    #transform structure [x, y, z, qx, qy, qz, qw]
    Rt = mathutils.Quaternion((transform[6], transform[3], transform[4], transform[5])).to_matrix().to_4x4()
    Rt.normalize()
    Rt.transpose()
    Rt[0][3] = transform[0]
    Rt[1][3] = transform[1]
    Rt[2][3] = transform[2]

    bpy.ops.object.select_all(action='DESELECT')
    #import the ply mesh into blender
    bpy.ops.import_mesh.ply(filepath=baseDir + meshName)
    #get the ply object
    obj = bpy.context.object
    #get objects world matrix
    objWorld = obj.matrix_world

    for index in range(len(obj.data.vertices)):
        #get local point
        point = mathutils.Vector([obj.data.vertices[index].co[0],obj.data.vertices[index].co[1], obj.data.vertices[index].co[2], 1.])
        #convert local point to world
        point = objWorld * point
        #apply ply transformation
        point = Rt * point
        #update the point in the mesh
        obj.data.vertices[index].co[0] = point[0]
        obj.data.vertices[index].co[1] = point[1]
        obj.data.vertices[index].co[2] = point[2]
#all vertex positions should be updated correctly
于 2017-05-04T10:35:09.893 回答
0

正如其他答案中提到的,斯坦福 3D 存储库在“.conf”文件中提供了一些有关数据组织的信息,但是,使用提供的四元数数据时,兔子模型的转换无法正常工作。

我也陷入了兔子模型的注册问题,根据我的测试,我有一些额外的考虑要加起来。当应用变换时 - 旋转更具体 - 我意识到四元数值没有以正确的方向旋转云但是,当使用相应的欧拉符号时,通过改变一个特定旋转轴的符号,我得到了正确的登记。所以,回到“.conf”文件中使用的四元数符号,经过一些测试,我注意到通过改变四元数中“w”组件的符号,在每一'bmesh'行,但第一个(bun000.ply),可以使用四元数的旋转。

此外,由于某种原因,在注册龙(dragon_stand 和 dragon_side)和犰狳(armadillo_stand)斯坦福点云时,为了获得正确的结果,我必须使用不同的序列来读取“.conf”文件中的四元数数据. 它似乎存储为: tx ty tz qw qx qy qz 其中“t”指的是翻译值,“q”指的是四元数值。为了清楚起见,我刚刚测试了这三个模型,因此,我不知道四元值的默认模式是什么。此外,对于这最后两个点云模型,我不需要更改“.conf”文件。

我希望这对尝试做同样的其他人有用

于 2019-06-23T01:12:28.793 回答