综述
现在是2022年的春节期间,终于有一个大片的时间进行学习和自我充电。OpenMVS是三维重建的重要和经典开源库,网上关于OpenMVS的介绍基本都是配置和使用,几乎没有逐行的代码解析,所以我打算借自学的这段时间做这件事情,当然了,限于本人能力有限,博客内容都是本人的理解,所以必然会出现对代码的错误理解,欢迎随时批评指正。OpenMVS解析这个系列博客,如有转载,请与我本人确认并注明出处。
代码地址:GitHub - cdcseacave/openMVS: open Multi-View Stereo reconstruction library
OpenMVS到底干了什么事情?
它所处的阶段是三维重建后半部分,它需要的输入是位姿,稀疏点云,它在这基础上,对点云进行稠密化,然后把点云转化为曲面,再对曲面进行优化,最后加上纹理。
工程结构
OpenMVS-master
->apps
->build
->docker
->docs
->libs
-->Common
-->IO
-->MVS
-->Math
很明显,OpenMVS的执行入口是在apps目录下,相关算法在libs目录下。在apps目录下,可以看到OpenMVS可以接收来自colmap,Mateshape,OpenMVG,VisualSFM的数据,或者是文件数据。
首先介绍的是OpenMVS的第一步:点云稠密化,他在DensifyPointCloud.cpp文件中。
1. main()
进入到DensifyPointCloud.cpp文件,看看在main()里面究竟干了那些事情。代码里面接收了一些指令参数,会根据不同的指令执行不同的过程。以下内容的具体实现会在后面的文章里介绍。
1.1 功能一:在曲面上进行点云采样的功能
//option1: enable uniformly samples points on a mesh
Scene scene(OPT::nMaxThreads);
if (OPT::fSampleMesh != 0) {
// sample input mesh and export the obtained point-cloud
//load .ply of .obj pointcloud
if (!scene.mesh.Load(MAKE_PATH_SAFE(OPT::strInputFileName)))
return EXIT_FAILURE;
TD_TIMER_START();
PointCloud pointcloud;
if (OPT::fSampleMesh > 0) // set points density
scene.mesh.SamplePoints(OPT::fSampleMesh, 0, pointcloud);
else // set points number
scene.mesh.SamplePoints((unsigned)ROUND2INT(-OPT::fSampleMesh), pointcloud);
VERBOSE("Sample mesh completed: %u points (%s)", pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str());
pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T(".ply"));
Finalize();
return EXIT_SUCCESS;
}
首先创建了scene对象,这个对象是执行点云稠密化的最重要的工具。在这里,如果外界输入的指令是在曲面上进行点云采样,那么将会执行这个部分的代码。首先,他会加载曲面模型文件,即scene.mesh.Load(input_path);然后进行点云采样工作,即scene.mesh.SamplePoints(pointcloud);最后把采集的点云保存出来,即pointcloud.Save(output_path)。
1.2 导入图片和里程计
// load and estimate a dense point-cloud
// input images and poses
if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName)))
return EXIT_FAILURE;
// input mesh
if (!OPT::strMeshFileName.empty())
scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName));
if (scene.pointcloud.IsEmpty() && OPT::strViewNeighborsFileName.empty() && scene.mesh.IsEmpty()) {
VERBOSE("error: empty initial point-cloud");
return EXIT_FAILURE;
}
1.3 功能二:降采样
//option2: split the scene in sub-scenes such that each sub-scene surface does not exceed the given maximum sampling area (0 - disabled)
if (OPT::fMaxSubsceneArea > 0) {
// split the scene in sub-scenes by maximum sampling area
Scene::ImagesChunkArr chunks;
// downsampling depth pixels and images and get pintcloud
scene.Split(chunks, OPT::fMaxSubsceneArea);
// save each chunk as a .mvs
scene.ExportChunks(chunks, Util::getFilePath(MAKE_PATH_SAFE(OPT::strOutputFileName)), (ARCHIVE_TYPE)OPT::nArchiveType);
Finalize();
return EXIT_SUCCESS;
}
对每一张照片都在像素层级上进行降采样,同时在照片层级上也进行降采样,把那些距离很近,区别不大的照片删掉一部分。然后把整个过程分割成若干个chunk,每一个chunk保存成一个.mvs文件。
1.4 功能三:根据是否可见对点云进行过滤
//option3: filter dense point-cloud based on visibility (0 - disabled)
if (OPT::thFilterPointCloud < 0) {
// filter point-cloud based on camera-point visibility intersections
scene.PointCloudFilter(OPT::thFilterPointCloud);
// save
const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_filtered"));
scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType);
scene.pointcloud.Save(baseFileName+_T(".ply"));
Finalize();
return EXIT_SUCCESS;
}
1.5 功能四:根据观测次数输出过滤后的点云
//option4: export points with >= number of views (0 - disabled)
if (OPT::nExportNumViews && scene.pointcloud.IsValid()) {
// export point-cloud containing only points with N+ views
const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)));
scene.pointcloud.SaveNViews(baseFileName+String::FormatString(_T("_%dviews.ply"), OPT::nExportNumViews), (IIndex)OPT::nExportNumViews);
Finalize();
return EXIT_SUCCESS;
}
1.6 默认功能:(核心)点云稠密化
//option default: dense reconstruction
if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS) {
// for each image, find its neighbor images
if (!OPT::strViewNeighborsFileName.empty())
scene.LoadViewNeighbors(MAKE_PATH_SAFE(OPT::strViewNeighborsFileName));
TD_TIMER_START();
if (!scene.DenseReconstruction(OPT::nFusionMode)) {
if (ABS(OPT::nFusionMode) != 1)
return EXIT_FAILURE;
VERBOSE("Depth-maps estimated (%s)", TD_TIMER_GET_FMT().c_str());
Finalize();
return EXIT_SUCCESS;
}
VERBOSE("Densifying point-cloud completed: %u points (%s)", scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str());
}
1.7 输出结果
// save the final point-cloud
const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)));
scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType);
scene.pointcloud.Save(baseFileName+_T(".ply"));
#if TD_VERBOSE != TD_VERBOSE_OFF
if (VERBOSITY_LEVEL > 2)
scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+_T(".ply"));
#endif
Finalize();
return EXIT_SUCCESS;