输入数据包括ply模型、彩色图片和对应的相机内外参数。
其中txt文件中数据格式如下:
translation_x translation_y translation_z
rotation_00 rotation_01 rotation_02
rotation_10 rotation_11 rotation_12
rotation_20 rotation_21 rotation_22
fx
fy
cx
cy
image_height
image_width
使用PCL库
#include <fstream>
#include <iostream>
#include <sstream>
#include <pcl/io/pcd_io.h>
#include <pcl/io/obj_io.h>
#include <pcl/io/vtk_lib_io.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/features/normal_3d.h>
#include <pcl/surface/texture_mapping.h>
#include <pcl/visualization/pcl_visualizer.h>
std::ifstream& go_to_line(std::ifstream& file, unsigned int num)
{
file.seekg(std::ios::beg);
for (int i = 0; i < num - 1; ++i)
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return file;
}
bool read_cam_pose_file(std::string filename, pcl::TextureMapping<pcl::PointXYZ>::Camera& cam)
{
std::ifstream myReadFile;
myReadFile.open(filename.c_str(), ios::in);
if (!myReadFile.is_open())
{
PCL_ERROR("Error opening file %d\n", filename.c_str());
return false;
}
myReadFile.seekg(ios::beg);
float val;
go_to_line(myReadFile, 1);
myReadFile >> val;
cam.pose(0, 3) = val; // TX
myReadFile >> val;
cam.pose(1, 3) = val; // TY
myReadFile >> val;
cam.pose(2, 3) = val; // TZ
go_to_line(myReadFile, 2);
myReadFile >> val;
cam.pose(0, 0) = val;
myReadFile >> val;
cam.pose(0, 1) = val;
myReadFile >> val;
cam.pose(0, 2) = val;
myReadFile >> val;
cam.pose(1, 0) = val;
myReadFile >> val;
cam.pose(1, 1) = val;
myReadFile >> val;
cam.pose(1, 2) = val;
myReadFile >> val;
cam.pose(2, 0) = val;
myReadFile >> val;
cam.pose(2, 1) = val;
myReadFile >> val;
cam.pose(2, 2) = val;
cam.pose(3, 0) = 0.0;
cam.pose(3, 1) = 0.0;
cam.pose(3, 2) = 0.0;
cam.pose(3, 3) = 1.0; // Scale
go_to_line(myReadFile, 5);
myReadFile >> val;
cam.focal_length_w = val;
myReadFile >> val;
cam.focal_length_h = val;
myReadFile >> val;
cam.center_w = val;
myReadFile >> val;
cam.center_h = val;
myReadFile >> val;
cam.height = val;
myReadFile >> val;
cam.width = val;
myReadFile.close();
return true;
}
int main(int argc, char** argv)
{
pcl::PolygonMesh triangles;
pcl::io::loadPolygonFilePLY("./data/obj.ply", triangles);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(triangles.cloud, *cloud);
// Create the texturemesh object that will contain our UV-mapped mesh
pcl::TextureMesh mesh;
mesh.cloud = triangles.cloud;
std::vector<pcl::Vertices> polygon_1;
// push faces into the texturemesh object
polygon_1.resize(triangles.polygons.size());
for (size_t i = 0; i < triangles.polygons.size(); ++i)
polygon_1[i] = triangles.polygons[i];
mesh.tex_polygons.push_back(polygon_1);
// Load textures and cameras poses and intrinsics
pcl::texture_mapping::CameraVector my_cams;
const int nums = 3;
std::string prefix = "./data/color";
std::vector<std::string> filenames;
for (size_t i = 0; i < nums; i++)
{
std::stringstream ss;
ss << std::setw(3) << std::setfill('0') << i;
std::string filename = prefix + ss.str() + ".txt";
filenames.push_back(filename);
}
for (int i = 0; i < nums; ++i)
{
pcl::TextureMapping<pcl::PointXYZ>::Camera cam;
read_cam_pose_file(filenames[i], cam);
cam.texture_file = filenames[i].substr(0, filenames[i].size() - 3) + "png";
my_cams.push_back(cam);
}
// Create materials for each texture (and one extra for occluded faces)
mesh.tex_materials.resize(my_cams.size() + 1);
for (int i = 0; i <= my_cams.size(); ++i)
{
pcl::TexMaterial mesh_material;
mesh_material.tex_Ka.r = 0.2f;
mesh_material.tex_Ka.g = 0.2f;
mesh_material.tex_Ka.b = 0.2f;
mesh_material.tex_Kd.r = 0.8f;
mesh_material.tex_Kd.g = 0.8f;
mesh_material.tex_Kd.b = 0.8f;
mesh_material.tex_Ks.r = 1.0f;
mesh_material.tex_Ks.g = 1.0f;
mesh_material.tex_Ks.b = 1.0f;
mesh_material.tex_d = 1.0f;
mesh_material.tex_Ns = 75.0f;
mesh_material.tex_illum = 2;
std::stringstream tex_name;
tex_name << "material_" << i;
tex_name >> mesh_material.tex_name;
if (i < my_cams.size())
mesh_material.tex_file = my_cams[i].texture_file;
else
mesh_material.tex_file = "occluded.jpg";
mesh.tex_materials[i] = mesh_material;
}
// Sort faces
pcl::TextureMapping<pcl::PointXYZ> tm; // TextureMapping object that will perform the sort
tm.textureMeshwithMultipleCameras(mesh, my_cams);
// compute normals for the mesh
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n;
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud);
n.setInputCloud(cloud);
n.setSearchMethod(tree);
n.setKSearch(20);
n.compute(*normals);
// Concatenate XYZ and normal fields
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>);
pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
pcl::toPCLPointCloud2(*cloud_with_normals, mesh.cloud);
pcl::io::saveOBJFile("./data/res.obj", mesh, 5);
return 0;
}
使用OpenMVS库
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/io/obj_io.h>
#include <pcl/io/vtk_lib_io.h>
#include <opencv2/opencv.hpp>
#include <OpenMVS/MVS/Common.h>
#include <OpenMVS/MVS/Scene.h>
#include <OpenMVS/Common/Types.h>
int main()
{
const int numViews = 3;
std::string prefix = "./data/color";
std::vector<cv::Mat> v_color;
double t1, t2, t3;
double r11, r12, r13, r21, r22, r23, r31, r32, r33;
std::vector<Eigen::Matrix4d> poses;
for (size_t i = 0; i < numViews; i++)
{
std::stringstream ss;
ss << std::setw(3) << std::setfill('0') << i;
cv::Mat img = cv::imread(prefix + ss.str() + ".png");
std::cout << prefix + ss.str() + ".png" << std::endl;
v_color.push_back(img);
std::ifstream f(prefix + ss.str() + ".txt");
f >> t1 >> t2 >> t3
>> r11 >> r12 >> r13
>> r21 >> r22 >> r23
>> r31 >> r32 >> r33;
Eigen::Matrix4d pose;
pose << r11, r12, r13, t1,
r21, r22, r23, t2,
r31, r32, r33, t3,
0, 0, 0, 1;
std::cout << pose << std::endl;
poses.push_back(pose);
}
MVS::Scene MVS_scene(8);
MVS_scene.platforms.Reserve(numViews);
MVS_scene.images.Reserve(numViews);
for (size_t i = 0; i < numViews; i++)
{
MVS::Image& image = MVS_scene.images.AddEmpty();
image.image = cv::MatExpr(v_color[i]);
image.platformID = i;
image.ID = i;
image.height = 1080;
image.width = 1920;
image.cameraID = 0;
image.poseID = 0;
MVS::Platform& platform = MVS_scene.platforms.AddEmpty();
MVS::Platform::Camera& camera = platform.cameras.AddEmpty();
//内参
camera.K(0, 0) = 1.045238890839152646e+03;
camera.K(1, 1) = 1.046345842268217893e+03;
camera.K(0, 2) = 9.518466822597985129e+02;
camera.K(1, 2) = 5.069212131671924340e+02;
camera.K = camera.GetScaledK(REAL(1) / MVS::Camera::GetNormalizationScale(image.height, image.width));
camera.R = RMatrix::IDENTITY;
camera.C = CMatrix::ZERO;
//外参
MVS::Platform::Pose & openMVS_pose = platform.poses.AddEmpty();
openMVS_pose.R = poses[i].block<3, 3>(0, 0);
openMVS_pose.R = openMVS_pose.R.inv();
openMVS_pose.C = poses[i].block<3, 1>(0, 3);
image.UpdateCamera(MVS_scene.platforms);
}
pcl::PolygonMesh triangles;
pcl::io::loadPolygonFilePLY("./data/obj.ply", triangles);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(triangles.cloud, *cloud);
MVS_scene.mesh.vertices.Resize(cloud->size());
MVS_scene.mesh.faces.Resize(triangles.polygons.size());
for (size_t i = 0; i < cloud->size(); i++)
{
MVS_scene.mesh.vertices[i].x = cloud->points[i].x;
MVS_scene.mesh.vertices[i].y = cloud->points[i].y;
MVS_scene.mesh.vertices[i].z = cloud->points[i].z;
}
for (size_t i = 0; i < triangles.polygons.size(); i++)
{
MVS_scene.mesh.faces[i].x = triangles.polygons[i].vertices[0];
MVS_scene.mesh.faces[i].y = triangles.polygons[i].vertices[1];
MVS_scene.mesh.faces[i].z = triangles.polygons[i].vertices[2];
}
MVS_scene.TextureMesh(0, 1920, 0.0, 0.0, 1, 1, 0, 0);
MVS_scene.mesh.Save("./data/res.obj");
return 0;
}
PCL和OpenMVS库都能实现上面展示的纹理贴图效果,生成结果都有一个obj模型文件和一个mtl材质文件。不同的是使用PCL生成的mtl文件对应多张图片,而使用OpenMVS库会另外生成一个png文件。结果可以通过meshlab可视化查看:
输入ply文件
输出obj文件
可视化
C++版本:使用PCL库,但无法读取上面PCL库贴图生成的obj模型纹理,可能是PCL库内部对多视角贴图的不兼容。
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/obj_io.h>
#include <pcl/io/vtk_lib_io.h>
#include <pcl/visualization/pcl_visualizer.h>
int main(int argc, char** argv)
{
std::string objPath = "./data/res.obj";
pcl::TextureMesh mesh;
pcl::io::loadOBJFile(objPath, mesh);
pcl::TextureMesh mesh2;
pcl::io::loadPolygonFileOBJ(objPath, mesh2);
mesh2.tex_materials = mesh.tex_materials;
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("viewer"));
viewer->addTextureMesh(mesh2, "mesh");
while (!viewer->wasStopped()) // 在按下 "q" 键之前一直会显示窗口
{
viewer->spinOnce();
}
return 0;
}
python版本:使用open3d库。
import open3d as o3d
def visualize(mesh):
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(mesh)
vis.run()
vis.destroy_window()
mesh = o3d.io.read_triangle_mesh("./data/res.obj",True)
mesh.compute_vertex_normals()
#visualize(mesh)
o3d.visualization.draw([mesh])
所有代码和数据放在下载链接:texture mapping纹理贴图