MVS参数配置实战:从稀疏点云到稠密点云的关键调参指南
刚接触MVS的时候我对它的理解很肤浅前面跑完特征匹配和位姿恢复把照片和位姿丢给MVS它就会自动长出一片稠密点云。直到有次在某室内重建项目里稀疏点云漂亮得能当作品展示可MVS出来的东西却像洒了一地芝麻——大量飞点、墙面半透明、柱子断成两截。我才意识到MVS的使用和参数配置根本不是默认参数一把梭能搞定的事它才是整条重建链路里最考验经验的一环。这篇文章我想从一个实际使用者的角度把MVS的使用和参数配置讲透。不管你是刚入门的小白还是已经跑过几个项目、但对参数一头雾水的同行应该都能找到对应的答案。我默认你手里有一个可用的MVS工具开源社区版也好商业软件内置的也行参数命名或许略有差异但底层逻辑和调参思路是通用的。这篇是这个系列的第一篇重点放在会用它、会看参数、会排错上后面再聊进阶的工程化加速和分布式计算。1. MVS在重建链路里的定位为什么前面的成果会被这一步无限放大或彻底毁掉1.1 一条完整的三维重建流水线其实是个接力赛做三维重建的人都知道从一堆照片到你最后能拿出去展示的三角网格中间要过好几道工序。很多人习惯性把这整条链路叫跑一下重建但真正动手拆开每一环的职责和输出是完全不同的。我习惯把它分成五个阶段影像采集、特征提取与匹配、稀疏重建也叫相机位姿恢复、MVS稠密重建、表面重建与纹理映射。这里用一张简表帮大家理清关系阶段主要输入主要输出你可能熟悉的产物特征提取与匹配多张影像特征点和同名点匹配关系特征描述子文件稀疏重建/SfM匹配后的特征点相机内参、外参、稀疏点云相机位姿文件、稀疏模型MVS稠密重建原始影像 相机内外参深度图 / 稠密点云每张影像对应的深度图表面重建稠密点云网格曲面.obj / .ply 网格纹理映射网格 原始影像带纹理的模型贴图 纹理模型从这张表能看出来MVS是典型的承上启下环节。它把稀疏的、只有特征点级别的几何信息扩展成每个像素级别都有深度的密集几何信息。如果没有MVS你后面无论是做网格重建还是做纹理映射得到的东西都会是漏风的筛子很多结构空洞、表面破损。1.2 MVS不是一键生成稠密点云而是一场逐像素的深度搜索MVS的全称是多视图立体匹配英文是Multi-View Stereo。它做的事情本质上可以这样理解已知一组相机的位置朝向也已知同一物体在每个相机图像里的成像位置我们希望为空间中的每一个小面片找到一个合适的空间位置使得它在所有可见相机里的投影颜色一致性最高。这个描述听起来抽象举一个生活化的例子。你和朋友站在不同位置看同一栋楼你脑子里会自动判断窗户在哪个高度、墙面大概离你多远。因为两个视角看到的图像有差异你的大脑可以借此估算深度。MVS就是把这个过程程序化而且是一次性处理几万、几十万个空间候选位置。实际操作中绝大多数MVS工具会为每张输入影像生成一张深度图。深度图上的每个像素值代表相机射线上那个点离相机多远。然后所有影像的深度图被融合到同一个世界坐标系里就成了你看到的稠密点云。所以生成稠密点云只是结果中间最关键的是深度估计和深度融合两步。参数配置要想清楚也是在帮这两步做决策。1.3 MVS调参失败代价为什么比稀疏重建翻车更严重稀疏重建阶段哪怕有一批特征点匹配错了错误也会被随机算法筛掉一部分最后留下的模型只是多了一些短线噪声整体还能看。MVS完全不一样它对每一个像素都要给出一个深度答案。弱纹理墙面、反光玻璃、重复纹理的地板这些区域没有清晰特征算法只能靠周围信息和匹配代价硬猜一旦参数不合适猜错的地方会成片出现。更要命的是MVS的计算量远大于稀疏重建。一张4000万像素航拍影像如果要采样256层深度候选理论上会产生上亿次匹配代价计算。这意味着你如果参数给错重跑一次可能就要付出几小时的GPU/CPU代价。我在模拟项目X里第一次跑MVS默认参数直接跑了一个通宵出来一片飞点连问题在哪都看不清。所以后来我学乖了先理解每个参数在控制什么再动手调。2. 参数配置前必须排查的输入约束位姿、影像质量和掩膜2.1 位姿精度是第一根柱子你省掉的优化会在点云里加倍还回来很多人调MVS参数调得焦头烂额但真正的问题根本不在MVS这一层而在上游SfM留下的相机位姿。MVS的深度估计是建立在相机内外参数之上的如果相机位姿本身有偏差无论你怎么调匹配窗口、怎么加滤波输出点云都必然变形。位姿误差的影响会随距离放大。假设某个相机外参有0.5度的旋转误差对于100米外的地面点单是横向偏移就接近100*tand(0.5)大约是0.87米。对MVS来说这个偏差会让同一空间点在多视图里的投影颜色互相对不上于是深度估计会给出一个折中的错误值严重时整个曲面会像被吹胀的气球。反过来距离越近的物体受位姿误差影响越小这也是近景重建往往比大场景航测更容易出现好结果的原因之一。我的建议是在跑MVS之前先花十分钟检查位姿质量。一个实用的办法是看稀疏点云是否干净、有无明显的双层层叠另一个办法是看工具输出的重投影误差。如果数值已经偏高先回头优化SfM、检查相机内参标定不要急着把锅甩给MVS参数。2.2 影像质量对匹配代价的直接伤害模糊、过曝与白平衡漂移MVS的匹配代价计算依赖像素灰度/颜色在多个视角之间的一致度。影像质量不过关匹配代价就没有可信度。我在实际项目里最常遇到三类问题。第一类是运动模糊。手持拍摄或者无人机大风天拍摄影像边缘会有轻微拖影。一旦窗口内信号被抹平不同深度候选之间的代价差异变小深度图就很容易出现糊成一片的误匹配。第二类是过曝和欠曝。高光墙面、车窗玻璃、或大面积天空如果某一张图过曝全区域灰度都砍到255匹配代价失效。第三类是白平衡不一致比如室内混合日光灯和落地灯光源两台相机白平衡不同同一种材质颜色差异会非常大。处理思路其实不复杂采集阶段就尽量控制曝光和白平衡后期能做色彩校正的话不要省略。多数工具都有全局或分块色彩一致性校正跑MVS前建议先执行一次。真正的坑是很多人在工程里图省事直接拿原始照片丢进MVS到头来点云里出现整个立面偏色加深度断层这时再去排查MVS参数得浪费大量时间。2.3 掩膜/ROI让MVS把精力花在刀刃上MVS的工具里通常有一个掩膜mask或者ROI的概念也就是说你可以主动告诉算法哪些区域不要计算深度。这一步经常被新手忽略但带来的收益极其明显。先举三个高价值场景。第一个是天空。天空区域没有固定深度匹配代价几乎全是噪声不掩膜的话会导致一批深度无穷大的飞点弥漫在整个点云外围。第二个是动态物体比如行人、车辆、飘动的树叶。不同视角下它们的位置是变化的MVS永远无法用多视角一致性去解释它们最后只会产生一团乱麻。第三个是玻璃和反光材质这类表面在不同视角呈现的影像内容完全不同MVS的代价模型天然失效硬算只会污染周围区域。我自己的习惯是在正式跑MVS之前先在预处理阶段生成一张二进制掩膜图1表示有效区域0表示忽略。有些商业软件可以在界面上手动涂掩膜开源方案则经常需要你准备同名掩膜文件。做完这一步MVS的深度图和融合点云会干净一个数量级我实测在模拟项目X里行人掩膜加上之后飞点数至少下降了七八成。3. 核心参数逐项拆解视图选择、匹配窗口、深度范围和融合阈值3.1 视图选择一张影像的最佳搭档数量不是越多越好MVS并不是所有相机一起匹配。对每一张参考影像工具要先挑出若干张邻居视图作为它的匹配对象这个过程叫视图选择。你可能会想参与匹配的视图越多不是越稳吗实际恰恰相反视图数量过多会引入大量低质量匹对既增加计算量又把深度估计向量带偏。选择视图的核心逻辑是基线与交会角的权衡。交会角太小两条视线几乎平行深度分辨率差没法精确定距交会角太大遮挡增多视角差异太大影像内容也不一致。好的视图选择会综合考虑参考影像朝向、相机间距、两两之间的可视面片数量、三角化夹角而不是单纯取最近的几张。我从不同数据规模的实践中给出的通用区间是邻居视图数量4到10个这个区间既能保证足够的几何约束又不会因为冗余而崩掉。具体到航测影像重叠率通常很高我一般设5到7近景环绕拍摄则看张数的密集程度6到12都有可能。很多工具还会输出每个视图的匹配置信度你可以据此把明显不对的视图权重压低。3.2 匹配代价窗口窗口太大会吃掉细节太小会怕噪声MVS在比较两个视角的像素是否对应时很少只看单个像素而是取以该像素为中心的一小块区域这叫做匹配代价窗口匹配窗口。窗口大小是MVS参数里最容易直觉感受到的一个。窗口小比如3×3或5×5对边缘和细细节保留最友好因为匹配代价不会被窗口内其他像素平均掉。但代价是它对单像素噪声、曝光差异和轻微畸变极其敏感弱纹理区域几乎没法匹配。窗口大比如15×15甚至21×21相当于用一片区域的整体纹理去匹配噪声被削弱弱纹理也能借周围信息蒙出深度但代价是细节会被磨平柱子边缘、扶手棱角这类地方会肿胀。我的做法是先看一眼场景的纹理尺度。如果是白墙、大片地面这种低纹理场景果断把窗口调到13×13以上如果是钢架、栏杆、树枝这类细结构多、层次感强的场景就把窗口收到7×7或9×9。不要一个参数打天下很多大场景项目可以按分区或按多次跑不同窗口只是成本和复杂度偏高新手慎用。3.3 深度范围与采样层数先圈定范围再把采样砸在刀刃上深度估计本质上是在相机射线上搜索一个候选深度值。搜索范围就是深度范围搜索的离散程度则由采样层数决定。这两个参数放在一起调效果最明显。先说深度范围。理论上有相机位姿你可以估算出场景近处和远处到底离相机多远。有些工具提供自动估算但自动估算往往会把范围拉得很宽导致在有限采样层数下深度分辨率被稀释。比如一个室内场景实际深度在2到6米自动估算却给了0.5到50米那采样层数再多也可能在关键距离段上颗粒感太粗。手动把深度范围缩窄到场景物理边界附近通常能立刻看到深度图变扎实。再说采样层数。每增加一倍层数对应的代价计算量几乎线性上涨而精度增长却不是线性的。我一般先把层数设到64或128快速看一个粗略结果确认深度范围没问题后再把层数提到256甚至更高。如果工具支持由粗到细的多阶段估计那就利用它先用粗采样锁定区域再在局部细化这比直接上一坨高采样要理智得多。另外提醒一下非常大的影像比如4K以上配合256层代价体可能吃掉几个G内存机器配置一般的话容易直接被压死。3.4 深度图滤波与最终融合精度和完整度始终在拔河深度图估计出来只是中间产物直接融合通常会出现大量噪声和孔洞。几乎所有的MVS实现都会做两块工作一致性检查和滤波融合融合成一整个点云。一致性检查也叫几何一致性验证是指用当前深度图去预测其他视图里对应像素的位置再反向验证预测深度和那个视图自己估计的深度是否一致。允许误差越小保留的点越可信但完整度会下降允许误差越大点云越密但飞点和错层也会变多。这个阈值一般以像素为单位比如0.5像素、1像素、2像素。多数工具默认在1到2像素之间我习惯在最终出图前先设严一点比如1像素看完整度是否可接受。融合环节还有两个高频参数一是最小可见次数也就是一个三维点至少要被多少张影像的支持投票才保留。设成2点云会非常稀疏设成5飞点被压掉很多但一些边缘薄片也会被干掉。二是角度阈值指参与融合的各深度图视线与表面法线夹角的上限夹角太大表示某张图对该点几乎是侧视匹配可信度低。在实际项目中最小可见次数我通常取3左右这是一个在精度和完整度之间比较均衡的起点后续按症状微调。4. 不同数据场景的参数偏好航测、手持近景和弱纹理大平层4.1 航测/倾斜摄影场景重叠规则但地面弱纹理和立面差异是主战场航测影像的典型特点是飞行轨迹规则、重叠率有保证、相机朝向变化不大。看起来是相对友好的MVS场景但因为测区往往包含大片植被、裸地、屋顶平面弱纹理和重复纹理反而成为主要挑战。在航测数据上我的参数起点是邻居视图5到7匹配窗口11×11深度范围用测区高程差异手动缩窄最小可见次数2到3一致性检查阈值调到1到1.5像素。如果看到地面出现明显波浪状起伏大概率是深度范围给得过宽或者滤波强度不足如果屋顶边缘鼓起一大圈就要检查是不是邻居视图里混入了交会角过大的侧视影像。倾斜摄影的立面重建是个特别容易出问题的地方。同一栋楼从多个方向拍摄有些立面纹理很贫乏只有窗框和空调外机。这时我常常会把窗口调高到15×15同时把平滑项权重调大逼着算法在纹理不足的区域用周围平面趋势去外推。这样做立面会更完整但代价是细节边缘会有一定圆角化所以要看项目到底更在意完整度还是尺寸精度。4.2 手持近景/室内部件级重建噪声多、光照乱去噪优先于追求密度手持相机绕着物体拍一圈图像数量可能上百张视角变化复杂而且手持照片最常见的运动模糊、景深差异、光照方向和色温变化都会直接伤害匹配代价。室内部件级重建的另一个麻烦是场景尺度不大但对细节完整性要求极高比如管线、法兰、设备支架。近景场景我会优先强调掩膜。玻璃、反光金属、黑色塑料这些区域能mask就mask不能mask也得做好心理准备。参数上邻居视图可以给到8到12因为近景影像视角变化大多一些视图能提高全局一致性匹配窗口我建议7×9或9×9细节优先深度范围务必手动贴近物体实际尺寸不然采样层数完全被浪费。这里有一个真实踩坑模拟项目X里我在一个室内场景把采样层数提到了512结果机器直接爆内存算到一半崩了。后来把影像降到2K分辨率、层数降到256、深度范围缩到0.5到5米结果反而更快更干净。高采样层数永远不应该是第一选择先让范围和分辨率变得合理再考虑要不要增加层数。4.3 弱纹理和重复纹理场景没有特征可抓就靠先验和约束硬扛纯白墙、玻璃幕墙、大面积光面石材这类几乎无纹理的区域在任何MVS工具里都是天敌。稀疏重建阶段这里本来就没有特征点MVS虽然想给每个像素一个深度但周围没有纹理梯度匹配代价曲线平坦得一塌糊涂算法只能在候选深度里随机选。应对弱纹理第一梯队是几何先验。如果周围已经有可靠深度的点很多工具会用平面假设向外扩展或者用超像素分割把无纹理区域当成一个整体平面来估计。参数上通常有Smoothness或平滑权重这类概念调大它会让深度图更趋于连续和平面化适合大平层场景但也会牺牲真实凹凸细节。第二梯队就是调整数据来源。把拍摄高度放低、离表面更近、开闪光或补光制造纹理梯度、或者在表面加遮罩纹理都能显著改善匹配质量。千万别以为MVS参数能包治百病弱纹理区域的深度本质上是一个信息不足的求解问题参数只能把不确定性压到最小没有办法无中生有。重复纹理则是另一个坑比如地砖、百叶窗、瓦片。这类区域在多视角匹配时会出现哪块都对得上的歧义MVS容易在整层上方选错深度形成周期性错层。对付重复纹理最有效的参数不是窗口大小而是视图数量和交会角增加参与匹配的视图可以让错误候选因为无法同时满足所有视图而被淘汰。同时一致性检查阈值要更严格宁可牺牲一些完整度也要把错层点清掉。5. 效果不对劲时的排查链路从飞点、条纹到空洞5.1 第一步把参数问题和输入问题分开看到MVS结果很烂第一反应不要是把所有参数调一遍。那样完全是在碰运气。我现在的习惯是先花时间把问题归类到底是输入问题还是参数问题。输入问题包括但远远不限于位姿不对、影像过曝、色彩不一致、掩膜缺失、影像分辨率悬殊。参数问题主要包括深度范围过宽、采样层数不够、窗口不合理、视图选择失败、滤波阈值不当。判断方法其实很直接先把邻居视图数、匹配窗口、采样层数这些核心参数都拨到一个大众化的保守值比如邻居视图6、窗口11×11、采样层数128然后看结果。如果保守参数下依然惨不忍睹那大概率是输入问题如果保守参数下比默认参数好得多那说明是参数问题这时候再逐项优化也不迟。很多新手一上来就动高级参数最后根本没法定位是哪一步出了问题这种教训我见过不止一次。5.2 第二步把中间产物当成第一优先级检查对象点云是MVS的最终输出但它不是唯一输出。绝大多数工具都会保留每个视图的深度图即使没有保留我也建议在跑正式大场景前先设定导出深度图的选项。排查MVS问题最快的方式是看深度图而不是直接看三维点云。深度图其实是二维图像很容易用看图软件打开。它的颜色或灰度表示估计深度值所以问题一目了然大片黑色区域说明没有估计出深度通常是对应弱纹理、超出深度范围或掩膜遮掉了一片平滑但突然有一道深色条纹往往是窗口太大把前景背景在深度不连续处混在了一起整张图像雪花一样噪点丛生多半是影像质量太差或位姿噪声过大或者采样层数严重不足。我自己的流程是先挑两三张角度最复杂、包含前景后景交错的影像看它们的深度图如果这两张深度图合理再去看融合后的点云如果深度图已经烂了那点云烂只是连带的。看完深度图再改参数每一轮试错的目的性会强得多。5.3 第三步典型症状与参数调整方向对照形态各异的MVS坏结果其实可以通过症状快速对应到参数。我这里整理了一份我自己经常对照的表不保证百分百灵验但能省掉大量盲目尝试。症状最可能的原因优先调整方向点云里稀疏飞点遍布一致性检查太松、视图数量太少、影像噪声增大最小可见次数、调严一致性阈值边缘和细结构膨胀匹配窗口过大、平滑权重过高调小窗口、降低平滑权重大块区域空洞弱纹理、深度范围过宽、采样层数不足缩窄深度范围、增加层数、考虑掩膜补充深度图出现平行条纹匹配窗口跨过了深度不连续处、交会角过大的长基线调小窗口、过滤长基线视图曲面鼓起或水面化位姿误差、深度范围错误、平滑过度先检查上游位姿再平衡平滑权重周期性错层重复纹理歧义增加邻居视图、调严一致性检查这张表不能直接替代你理解参数但能帮助你把有限的试错机箱时间花在刀刃上。每改一次参数记得记录结果。我一般会把参数和对应点云截图存成一个简单表格最后调参调得再乱也能回退到相对好的那一版。5.4 一个快速验证调参效果的策略小范围试跑MVS调参最忌讳的就是每次都用整个场景几百张影像全量跑一遍。跑一次几小时一天下来调不了几轮。更合理的策略是从整个数据集里挑一段影像连续、覆盖典型地物、包含前后景交错的子集比如二三十张图在它上面快速跑参数组合。为什么要选覆盖典型地物的子集因为调参的目的不是让这一小段跑出完美模型而是让参数在大场景里也能站得住。子集里必须要有你想保留的细节结构、有大片弱纹理区域、有前景后景层次变化这样你才能提前看到把窗口调大或调小时到底哪些地方会变好、哪些地方会变坏。如果工具支持你还可以只让MVS输出深度图和一小块融合点云不跑表面重建因为网格重建阶段会掩盖很多问题。表面上看少跑一步能省时间但实际上排查时多了这一步反而容易把深度图的错误隐藏掉。我在前面那个模拟项目X里正是靠着这种小范围试跑在半天内把图像子集的深度图从满屏雪花调到了基本干净最后全量数据一次性跑通几乎没返工。调参数这件事说到底就是一个观察中间产物、猜问题原因、改一个变量、再看结果的循环。别指望一次把所有参数调到完美也别被一两个参数吓住。MVS没有那么神秘它只是一堆匹配代价的运算而你要做的就是帮它选一个合适的搜索范围和搜索策略罢了。后面等我整理完手头的材料我再写一篇关于MVS工程化加速和分布式跑批的文章到时候咱们接着聊。