【三维重建笔记】02 OpenMVS 流程主要 API 整理

引言

在 前一节 中初步尝试使用 colmap + MVS 流程重建了人物的 三维模型, 但是还遗留了 执行时间偏长(几个小时), 结果遗留噪声, 部分区域显得有些粗糙等问题。

前一节直接使用了 openMVS 官方 编译好的 exe 以及程序默认参数,来生成结果。本节试着通过编写程序的方式, 来走一遍 openMVS 的流程, 顺便进一步了解 OpenMVS API, 看看能否按照自己的需求,来调节参数,处理上面遗留的一些问题。

故本节主要梳理 OpenMVS 流程的 API 调用 与 入参的含义。

最终本次实验中, 生成的模型结果如下

观感上比之前 好了不少

另外, 本次试验中几个主要步骤的耗时如下
总的流程跑下来大致是 38 分钟 左右(设备: RTX2060, Core i7-10875, 实验时没带充电器,笔记本用电池运行,通电应该能更快一些)。

此次,最耗时的主要是 refineMesh 那一步, 后续看看是否还能优化。

0.Scene 类与一些基础操作

OpenMVS 流程的几个主要操作都以 Scene 的成员方法的形式进行了封装。

【1】加载 mvs 文件

复制代码
Scene scene(MAX_THREADS_NUM);
if (scene.Load(INPUT_MVS_FILE) == Scene::SCENE_NA) {
    std::cerr << "Failed to load input scene: " << INPUT_MVS_FILE << std::endl;
    return LOAD_INPUT_SCENE_FAILED;
}

【2】保存 mvs 文件

复制代码
scene.Save(OUTPUT_SCENE_ROI_TO_MVS);

【3】保存 mesh 文件

复制代码
scene.mesh.Save(OUTPUT_ORIGIN_MESH);

1.scene.DenseReconstruction 生成稠密点云

【0】生成稠密点云前,先计算 ROI(Bounding box)范围,然后将 ROI 之外的无用点去除掉,避免其参与计算

复制代码
scene.EstimateROI(1.0f, -1);// 使用第一个缩放参数可以整体调整生成的 ROI boundingBox 的大小。(无法只沿着某个方向缩小, 不过也够用)
scene.CropToROI(scene.obb);

【1】调用函数前需要通过如下方式先进行参数配置

复制代码
OPTDENSE::init();
MVS::OPTDENSE::update();
OPTDENSE::nResolutionLevel = 1;          // How many times to scale down the images before dense reconstruction (0=original, 
                                         // 1=half, 2=quarter, etc.).Higher values process faster but produce less detail.
OPTDENSE::nMaxResolution = 2560;         // Maximum image resolution in pixels. Images larger than this will be downscaled to 
                                         // this resolution.Set to 0 for no limit.
OPTDENSE::nMinResolution = 640;          // Minimum image resolution in pixels.Images can not be downscaled to a resolution 
                                         // smaller than this.
OPTDENSE::nSubResolutionLevels = 1;      // Number of additional lower resolution levels to process for better multi-scale 
                                         // depth estimation.0 means only process at the selected resolution level.
OPTDENSE::nNumViews = 4;                 // Number of neighbor images to use for depth estimation (0 to select valid views).
                                         // More views increase accuracy, but slow down processing.
OPTDENSE::nMinViews = 4;                 // Minimum number of views in which a point must be visible to be considered during 
                                         // neighbor views estimation. Higher values produce more similar neighbor views, 
										 // but may discard some valid points.
OPTDENSE::nMinViewsFuse = 5;             // Minimum number of views required to include a depth point in the final fused point 
                                         // cloud. Higher values produce cleaner results, but may lose coverage.
										 //(融合到最终点云中所需的最小视图数量。数值越高,结果越干净,但可能会降低覆盖范围。)
OPTDENSE::nMinViewsTrustPoint = 3;       // Minimum number of views for a point to be considered for approximating the 
                                         // depth-maps during initialization (<2 - random initialization).
OPTDENSE::nEstimationIters = 5;          // Number of iterations for photometric refinement of each depth estimate.More 
                                         // iterations improve accuracy, but increase computation time.
OPTDENSE::nEstimationGeometricIters = 3; // Number of iterations for geometric consistency filtering (0 disabled). More 
                                         // iterations may produce more accurate results, but increase computation time.
OPTDENSE::nEstimateColors = 2;           // Estimate color for each point in the dense cloud based on the source images. 
                                         // Disable to skip color computation.
OPTDENSE::nEstimateNormals = 2;          // Store estimated normals for each point. Normals are useful for surface 
                                         // reconstruction and visualization.
OPTDENSE::nFuseFilter = 2;               // Fusion quality level:- Merge only(0): Fast, just merge all points; 
                                         // Fuse(1): Standard fusion with outlier removal; 
										 // Dense fuse(2): Slower but produces the densest, highest quality result
OPTDENSE::nOptimize = 4;                 // flags used to filter the depth-maps after estimation (0 - disabled, 
                                         // 1 - remove-speckles, 2 - fill-gaps, 4 - adjust-confidence) 并且这个值是可以叠加的
										 //(按位运算理解  如果是 5(4 + 1), 即应用了移除噪点(1), 又调整了置信度(4))
OPTDENSE::nIgnoreMaskLabel = -1;         // label value to ignore in the image mask, stored in the MVS scene or next to each 
                                         // image with '.mask.png' extension (<0 - disabled)
OPTDENSE::fDepthReprojectionErrorThreshold = 0.5f;  // dense-fuse maximum distance between measured and depth projected pixel 
                                                    //(稠密融合(dense-fuse)中,实测像素与深度投影像素之间的最大允许误差距离)
OPTDENSE::bRemoveDmaps = true;           // Delete intermediate depth maps after fusion to save disk space. Disable to keep 
                                         // depth maps for later inspection or re-fusion.

函数声明

复制代码
bool Scene::DenseReconstruction(int nFusionMode, bool bCrop2ROI, float fBorderROI, float fSampleMeshNeighbors)

调用示例

复制代码
if (!scene.DenseReconstruction(0, true, 0.f, 0.f)) {
    std::cerr << "Dense reconstruction failed." << std::endl;
    return EXIT_FAILURE;
}

生成的稠密点云结果大致如下 (黄线 bounding box 即为 ROI 区域)

2.scene.ReconstructMesh 将稠密点云转为 Mesh

输入参数

复制代码
struct ReconstructMeshOptions {
	float minPointDistance{1.5};      // Increase for smoother, coarser mesh; decrease for finer detail
	bool isUseFreeSpaceSupport{false};// 对于室外 或者 复杂的场景, 可以考虑设置为false; 是否使用相机的射线信息来雕刻空白区域以 
	                                  // 优化表面重建
	bool isIntegrateOnly{false};      // 是否只计算 ROI 中的点
	float thicknessFactor{1.0};       // 更高的值可以减少噪声, 但可能导致更大的 hole 与 移除 点云分布比较薄的区域
	float qualityFactor{1.0};
	float decimate{0.2};              // 用于控制三角形面片数 有效 范围 0 - 1
	float removeSpurious{20.0};       // Higher value remove more isolated piece
	bool removeSpikes{true};          // Automatically detect and remove spike artifacts (sharp, thin protrusions) 
	                                  // from the mesh. Recommended for cleaner results.
	int closeHoleNum{30};             // larger values close bigger holes
	int smoothSteps{5};               // More iterations create smoother surfaces, but may lose detail
	float edgeLength{0.0};            // Controls mesh resolution and uniformity
};

调用示例

复制代码
if (!scene.ReconstructMesh(
	reconstructMeshOptions.minPointDistance,
	reconstructMeshOptions.isUseFreeSpaceSupport,
	reconstructMeshOptions.isIntegrateOnly,
	4,
	reconstructMeshOptions.thicknessFactor,
	reconstructMeshOptions.qualityFactor)) {
	std::cerr << "ReconstructMesh failed." << std::endl;
	return EXIT_FAILURE;
}

调用 ReconstructMesh 之后 使用 clean 方法清理一下生成的mesh

复制代码
// 官方示例的 demo 中, ReconstructMesh  之后是这样清理了三次
scene.mesh.Clean(
	1.f,
	reconstructMeshOptions.removeSpurious,
	reconstructMeshOptions.removeSpikes,
	reconstructMeshOptions.closeHoleNum,
	reconstructMeshOptions.smoothSteps,
	reconstructMeshOptions.edgeLength,
	false);

scene.mesh.Clean(
	reconstructMeshOptions.decimate,
	0.f,
	reconstructMeshOptions.removeSpikes,
	reconstructMeshOptions.closeHoleNum,
	0u,
	0.f,
	false);

scene.mesh.Clean(1.f, 0.f, false, 0u, 0u, 0.f, true);

生成的结果

(感觉 这个 Mesh 结果也够用了, 后面也可以权衡一下,是否要花 大量的时间 进行后一步优化)

3.scene.RefineMesh 优化输入的 Mesh

输入参数

复制代码
struct RefineMeshOptions {
	unsigned nResolutionLevel{0};        // Image resolution scale for refinement (0=original, 1=half, etc.).Higher values are
                            	         // faster but less detailed. Start with lower resolution for coarse refinement.
	unsigned nMinResolution{640};        // Minimum image resolution in pixels.Images can not be downscaled to a resolution 
	                                     // smaller than this.
	unsigned nMaxViews{8};               // Maximum number of view neighbors to use during refinement. More views improve 
	                                     // accuracy, but increase computation time and memory usage.
	float fDecimateMesh{0.3f};           // Simplify the input mesh before refinement (0 = no decimation, 1 = maximum). 
	                                     // Useful for reducing computation on high-poly meshes.
	unsigned nCloseHoles{30};            // Maximum hole size (in edges) to fill before refinement.Closing holes prevents 
	                                     // artifacts at boundaries (0 - disabled)
	unsigned nEnsureEdgeSize{1};         // Subdivide or collapse edges to ensure uniform size (0=no change, 1=moderate, 
	                                     // 2=aggressive).Helps create more uniform mesh topology.
	unsigned nMaxFaceArea{32};           // Maximum face area projected in any pair of images that is not subdivided 
	                                     // (0 - disabled) (在任何一对图像中,一个面(通常是三维模型中的三角片面)所投影的最大面积,
										 // 超过这个面积将不会被进一步细分)
	unsigned nScales{2};                 // Number of multi-scale refinement passes. More scales improve convergence from 
	                                     // coarse to fine detail.(更多的尺度有助于从粗略结构到精细细节的收敛)
	float fScaleStep{0.5};               // Resolution scaling factor between successive refinement scales. Lower values 
	                                     // create more gradual transitions between scales.
	unsigned nAlternatePair{0};          // Which image pairs to use as reference during multi-view refinement:- Both 
	                                     // references: Use all paired views (most accurate)- Alternate: Switch between left/right 
										 // (balanced)- Left/Right only: Use only one reference (faster, less accurate)
	float fRegularityWeight{0.2};        // Weight for mesh regularity term.Higher values produce smoother surfaces, but may lose 
	                                     // detail.Lower values preserve sharp features, but can be noisy.(低的值会更平滑, 但可能
										 // 会丢失一些细节。 高的值会保留一些更锐利的细节, 但可能会有更多的噪声)
	float fRatioRigidityElasticity{0.90};// Balance between mesh rigidity and elasticity:- 0 = fully elastic (flexible 
	                                     // deformation)- 1 = fully rigid (minimal deformation)Affects how much the mesh can 
										 // deform. (网格刚性与弹性的平衡: 0 = 完全弹性, 1 完全刚性)
	float fGradientStep{45.05};          //(文档里该值的理解拆分为两部分, 整数部分负责控制迭代次数, 小数部分赋值微调)
										 // opts.gradientStep = iters + gstep*0.1f;
										 // iters: Number of iterations of gradient descent optimization.
										 // gstep: Step size for gradient descent optimization.Larger values converge faster, 
										 // but may be unstable.Smaller values are more stable, but slower.
	float fThPlanarVertex{0.f};          // Ratio of vertices to treat as planar (constrained to move along their normal).
	                                     // Higher values preserve flat surfaces better, but reduce flexibility. (数值越高,
										 // 越能有效保持平坦表面,但会降低网格的变形灵活性。)
	unsigned nReduceMemory{1};           // Memory reduction strategy:- 0 = no reduction (fastest, most memory)- 
	                                     // 3 = maximum reduction (slowest, least memory)Use higher values for large scenes 
										 // or limited RAM.
};

函数调用

复制代码
if (!scene.RefineMesh(
	refineMeshOptions.nResolutionLevel,
	refineMeshOptions.nMinResolution,
	refineMeshOptions.nMaxViews,
	refineMeshOptions.fDecimateMesh,
	refineMeshOptions.nCloseHoles,
	refineMeshOptions.nEnsureEdgeSize,
	refineMeshOptions.nMaxFaceArea,
	refineMeshOptions.nScales,
	refineMeshOptions.fScaleStep,
	refineMeshOptions.nAlternatePair,
	refineMeshOptions.fRegularityWeight,
	refineMeshOptions.fRatioRigidityElasticity,
	refineMeshOptions.fGradientStep,
	refineMeshOptions.fThPlanarVertex=0.f,
	refineMeshOptions.nReduceMemory=1)) {
	std::cerr << "RefineMesh failed." << std::endl;
	return EXIT_FAILURE;
}

结果如下

Mesh 文件比优化前 大 1000 多 K, 效果优化前更平滑一些。

4.scene.TextureMesh 为 Mesh 生成贴图

输入参数

复制代码
struct TextureMeshOptions {
	unsigned resolutionLevel{0};     // Image resolution scale for texture extraction (0=original, 1=half, etc.).
	                                 // Higher values are faster but produce lower quality textures.
	unsigned minResolution{640};     // Minimum image resolution in pixels.Images can not be downscaled to a 
	                                 // resolution smaller than this.
	unsigned minCommonCameras{0};    // Minimum number of cameras that must see a face for it to be textured.
	                                 // Higher values ensure better texture quality but may leave some faces 
									 // untextured.
	float outlierThreshold{6e-2f};   // Threshold for rejecting outliers during views to face assignment.Higher 
	                                 // values are more permissive.(用于剔除噪点的阈值。 数值越高, 条件越宽松)
	float ratioDataSmoothness{0.6f}; // Balance between data term and smoothness term:0 = prioritize photometric quality 
	                                 // 1 = prioritize seam smoothness(0 优先保证光度质量 1 优先保证接缝的平滑度)
	bool globalSeamLeveling{true};   // Apply global color adjustment to minimize exposure differences between texture 
	                                 // patches.Recommended for better visual consistency across the entire model.
									 // (应用全局色彩调整,以最小化纹理块之间的曝光差异。建议启用此选项,以提升整个模型的视觉一致性。)
	bool localSeamLeveling{true};    // Apply local color blending along texture seams.Smooths transitions between 
	                                 // patches.Works well with global seam leveling for best results.
	unsigned textureSizeMultiple{0}; // Texture dimensions will be multiples of this value (0 - power of two)
	                                 //(纹理尺寸将会是此值的整数倍(0 表示使用 2 的幂次)。)
	unsigned rectPackingHeuristic{3};// Algorithm for packing texture patches into atlas:- 0 = MaxRects BSSF (best)- 
	                                 // 1 = MaxRects BL (fast)- 2 = Skyline BL Higher numbers are faster, but may be less efficient.
	uint32_t emptyColor{0x00000000}; // 用于填充没有 texture 覆盖到的区域的颜色
	float sharpnessWeight{0.5f};     // Sharpness weight to be applied on the texture (0 - disabled, 0.5 - good value).
	int ignoreMaskLabel{-1};         // Label value to ignore in the image mask, stored in the MVS scene or next to each image with 
	                                 // '.mask.png' extension(-1 - auto estimate mask for lens distortion, -2 - disabled)
	int maxTextureSize{8192};        // Maximum texture atlas size in pixels per dimension.Multiple textures are created if needed. 
	                                 // Larger values allow higher resolution textures, but require more memory (0 - no limit)
};

调用示例

复制代码
if (!scene.TextureMesh(
	textureOptions.resolutionLevel,
	textureOptions.minResolution,
	textureOptions.minCommonCameras,
	textureOptions.outlierThreshold,
	textureOptions.ratioDataSmoothness,
	textureOptions.globalSeamLeveling,
	textureOptions.localSeamLeveling,
	textureOptions.textureSizeMultiple,
	textureOptions.rectPackingHeuristic,
	Pixel8U(textureOptions.emptyColor),
	textureOptions.sharpnessWeight,
	textureOptions.ignoreMaskLabel,
	textureOptions.maxTextureSize)) {
	std::cerr << "RefineMesh failed." << std::endl;
	return EXIT_FAILURE;
}

结果如下

小结

本节进一步调用 API 来进行模型重建, 执行耗时与生成结果上相较于之前都有不少提升。

梳理下来,流程中的 API 可控制的入参有点多,但 openMVS 的API封装用起来也是挺方便的。

后续可以再看看 API 的方法是如何实现的, 还能如何拓展。

相关推荐
by209991 小时前
学会使用std::string类,并理解其内部是如何管理字符串的详细阐述(上)
c++·笔记·字符串·类和对象·string
笑鸿的学习笔记2 小时前
C++笔记之大块顺序写
java·c++·笔记
山岚的运维笔记4 小时前
ComfyUI NVIDIA安装教程:官方便携版下载+run_nvidia_gpu.bat启动,8G显存Windows实操
运维·服务器·windows·笔记·prompt·aigc·comfyui
知潮网7 小时前
手机号注销两年,免密扣款仍在继续
笔记
Seraphina367 小时前
DVWA(SQL注入-low,medium,XSS反射-low)
前端·数据库·笔记·sql·网络安全·web·xss
维克兜率天9 小时前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化
路弥行至10 小时前
【Head First 设计模式】第 2 章:观察者模式 —— 用现代 C++ 重新解读“交互对象的松耦合
c++·经验分享·笔记·观察者模式·设计模式·入门教程·headfirst
坤坤子吖11 小时前
Python基础语法学习:条件、循环与程序控制
开发语言·笔记·python·学习
远离UE411 小时前
UE5.8 Substrate Glint 笔记
笔记
用户140360581938311 小时前
Phase A · Step 3:模型导出 ONNX
笔记