我如何 read/transform stanford bunny .ply 文件的范围图像?

How can I read/transform the range images of the stanford bunny .ply-files?

我想从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 page doesn't handle with range images and I found nothing more at the Stanford pages. They just talk about, that the method of 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 分量,其他分量指的是 xyz,就像来自 here。如有需要,我可以提供错误转换的屏幕截图。

EDIT: zipper 的源代码在文件 zipper.c 中写到,这 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;

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

调试信息:

正如我从 stanford 3d scan

读到的

For all the Stanford models, alignment was done using a modified ICP algorithm, as described in this paper. These alignments are stored in ".conf" files, which list each range image in the model along with a translation and a quaternion rotation.

这里是link到“this paper

编辑:这两种方法称为压缩和 volmetric merging

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

For all the Stanford models, alignment was done using a modified ICP algorithm, as described in this paper. These alignments are stored in ".conf" files, which list each range image in the model along with a translation and a quaternion rotation.

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

第一行是正确的:

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

存储良好的初始相机位置,以 bmesh 开头的每隔一行指的是一个 .ply 文件,该文件存储一个范围图像。

转换值存储如下:

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;

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

这是我写的文件转换器。它将 assemble 所有扫描到一个文件中,每行一个点。它支持不同的文件格式(包括 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] = '[=10=]' ;
        // 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 ;
}

好的,这是我的解决方案,因为上面的 none 对我有用(注意这是在 python 中使用 blender 的 bpy)。看来我需要转置我的 4x4 变换矩阵的旋转部分(注意我使用标准方法将四元数转换为旋转矩阵而不是来自 zipper 的)。另请注意,由于我在导入或使用任何模型时使用 blender,它仅存储模型相对于对象世界变换的局部坐标,因此您不必执行此操作 point = objWorld * point,它是特定于 blender 的。

#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

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

我也被兔子模型的这个注册问题卡住了,根据我的测试,我有一些额外的注意事项要加起来。当应用变换时——更具体地说是旋转——我意识到四元数值没有在正确的方向上旋转云,但是,当使用相应的欧拉符号时,通过改变一个特定旋转轴的符号,我得到了正确的登记。因此,回到“.conf”文件中使用的四元数表示法,经过一些测试后,我注意到通过更改四元数中 'w' 组件的符号,在每个 'bmesh' 行中,但是首先(bun000.ply),可以使用四元数旋转

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

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