我之前有一篇博客Convert PLY to VTK Using PCL 1.6.0 or PCL 1.8.0 使用PCL庫將PLY格式轉為VTK格式展示了如何將PLY格式文件轉化為VTK格式的文件,在文章的最后提到了VTK文件保存紋理的兩種方式,第一種是需要有texture的圖片,然后每個點存儲上該點在圖片中的x,y坐標,一般會normalize到[0,1]之間。第二種方法是直接存每個點的rgb值,上面的方法用的是第二種,因為導入的PLY格式就直接存儲的texture的rgb值,並沒有額外提供texture圖片。
對於一般的PLY或者PCD格式的點雲,一般都是用第二種方式來保存紋理的,即直接存儲rgb值,這樣轉換成的vtk文件自然也是第二種情況,而對於大多數的可視化軟件,比如ParaView或者3D Slicer,貌似只支持第一種方式,即需要導入texture圖片(如果大家知道直接顯示rgb值的方法,請在下方留言告知博主)。這樣就極大的不方便了,而且PCL庫中的點雲格式一般也是XYZRGBA,並沒有帶UV,紋理有專門的數據結構。我們的目標是生成帶texture coordinates的VTK文件,那么可以通過修改pcl自帶的saveVTKFile函數來實現目標。
這里我們把紋理坐標單獨抽出來,用下面的數據結構來表示:
std::vector<Eigen::Vector2f> texcoord;
pcl自帶的pcl::io::saveVTKFile函數所在的文件的地址是.../io/src/vtk_io.cpp。默認的是寫入RGB的值,我們只需要注釋掉寫入RGB的部分,添加上寫入紋理坐標的部分:
Using PCL 1.6.0
// PCL 1.6.0
int save_vtk_file(const std::string &file_name, const sensor_msgs::PointCloud2 &cloud, const std::vector<Eigen::Vector2f>& texcoord, unsigned precision) { if (cloud.data.empty ()) { PCL_ERROR ("[pcl::io::saveVTKFile] Input point cloud has no data!\n"); return (-1); } // Open file std::ofstream fs; fs.precision (precision); fs.open (file_name.c_str ()); unsigned int nr_points = cloud.width * cloud.height; unsigned int point_size = static_cast<unsigned int> (cloud.data.size () / nr_points); // Write the header information fs << "# vtk DataFile Version 3.0\nvtk output\nASCII\nDATASET POLYDATA\nPOINTS " << nr_points << " float" << std::endl; // Iterate through the points for (unsigned int i = 0; i < nr_points; ++i) { int xyz = 0; for (size_t d = 0; d < cloud.fields.size (); ++d) { int count = cloud.fields[d].count; if (count == 0) count = 1; // we simply cannot tolerate 0 counts (coming from older converter code) int c = 0; if ((cloud.fields[d].datatype == sensor_msgs::PointField::FLOAT32) && ( cloud.fields[d].name == "x" || cloud.fields[d].name == "y" || cloud.fields[d].name == "z")) { float value; memcpy (&value, &cloud.data[i * point_size + cloud.fields[d].offset + c * sizeof (float)], sizeof (float)); fs << value; if (++xyz == 3) break; } fs << " "; } if (xyz != 3) { PCL_ERROR ("[pcl::io::saveVTKFile] Input point cloud has no XYZ data!\n"); return (-2); } fs << std::endl; } // Write vertices fs << "\nVERTICES " << nr_points << " " << 2*nr_points << std::endl; for (unsigned int i = 0; i < nr_points; ++i) fs << "1 " << i << std::endl; // Write RGB values // int field_index = pcl::getFieldIndex (cloud, "rgb"); // if (field_index != -1) // { // fs << "\nPOINT_DATA " << nr_points << "\nCOLOR_SCALARS scalars 3\n"; // for (unsigned int i = 0; i < nr_points; ++i) // { // int count = cloud.fields[field_index].count; // if (count == 0) // count = 1; // we simply cannot tolerate 0 counts (coming from older converter code) // int c = 0; // if (cloud.fields[field_index].datatype == sensor_msgs::PointField::FLOAT32) // { // pcl::RGB color; // memcpy (&color, &cloud.data[i * point_size + cloud.fields[field_index].offset + c * sizeof (float)], sizeof (pcl::RGB)); // int r = color.r; // int g = color.g; // int b = color.b; // fs << static_cast<float> (r) / 255.0f << " " << static_cast<float> (g) / 255.0f << " " << static_cast<float> (b) / 255.0f; // } // fs << std::endl; // } // } // Write texture coordinates fs << "\nPOINT_DATA " << nr_points << "\nTEXTURE_COORDINATES tcoords 2 float\n"; for (unsigned int i = 0; i < nr_points; ++i) { //fs << texcoord[i][0] << " " << texcoord[i][1] << "\n"; fs << texcoord[i][1] << " " << texcoord[i][0] << "\n"; } fs << std::endl; // Close file fs.close (); return (0); }
Using PCL 1.8.0
// PCL 1.8.0 int save_vtk_file (const std::string &file_name, const pcl::PCLPointCloud2 &cloud, const std::vector<Eigen::Vector2f>& texcoord, unsigned precision) { if (cloud.data.empty ()) { PCL_ERROR ("[pcl::io::saveVTKFile] Input point cloud has no data!\n"); return (-1); } // Open file std::ofstream fs; fs.precision (precision); fs.open (file_name.c_str ()); unsigned int nr_points = cloud.width * cloud.height; unsigned int point_size = static_cast<unsigned int> (cloud.data.size () / nr_points); // Write the header information fs << "# vtk DataFile Version 3.0\nvtk output\nASCII\nDATASET POLYDATA\nPOINTS " << nr_points << " float" << '\n'; // Iterate through the points for (unsigned int i = 0; i < nr_points; ++i) { int xyz = 0; for (size_t d = 0; d < cloud.fields.size (); ++d) { int count = cloud.fields[d].count; if (count == 0) count = 1; // we simply cannot tolerate 0 counts (coming from older converter code) int c = 0; if ((cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && ( cloud.fields[d].name == "x" || cloud.fields[d].name == "y" || cloud.fields[d].name == "z")) { float value; memcpy (&value, &cloud.data[i * point_size + cloud.fields[d].offset + c * sizeof (float)], sizeof (float)); fs << value; if (++xyz == 3) break; } fs << " "; } if (xyz != 3) { PCL_ERROR ("[pcl::io::saveVTKFile] Input point cloud has no XYZ data!\n"); return (-2); } fs << '\n'; } // Write vertices fs << "\nVERTICES " << nr_points << " " << 2*nr_points << '\n'; for (unsigned int i = 0; i < nr_points; ++i) fs << "1 " << i << '\n'; // // Write RGB values // int field_index = getFieldIndex (cloud, "rgb"); // if (field_index != -1) // { // fs << "\nPOINT_DATA " << nr_points << "\nCOLOR_SCALARS scalars 3\n"; // for (unsigned int i = 0; i < nr_points; ++i) // { // int count = cloud.fields[field_index].count; // if (count == 0) // count = 1; // we simply cannot tolerate 0 counts (coming from older converter code) // int c = 0; // if (cloud.fields[field_index].datatype == pcl::PCLPointField::FLOAT32) // { // pcl::RGB color; // memcpy (&color, &cloud.data[i * point_size + cloud.fields[field_index].offset + c * sizeof (float)], sizeof (RGB)); // int r = color.r; // int g = color.g; // int b = color.b; // fs << static_cast<float> (r) / 255.0f << " " << static_cast<float> (g) / 255.0f << " " << static_cast<float> (b) / 255.0f; // } // fs << '\n'; // } // } // Write texture coordinates fs << "\nPOINT_DATA " << nr_points << "\nTEXTURE_COORDINATES tcoords 2 float\n"; for (unsigned int i = 0; i < nr_points; ++i) { //fs << texcoord[i][0] << " " << texcoord[i][1] << "\n"; fs << texcoord[i][1] << " " << texcoord[i][0] << "\n"; } fs << '\n'; // Close file fs.close (); return (0); }
注意上面紋理的x和y的值,根據貼圖的情況來看是否需要調換位置。