ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RIAV-MVS:非对称代价体与循环索引如何重塑多视角立体深度估计

RIAV-MVS:非对称代价体与循环索引如何重塑多视角立体深度估计 1. 从题目看论文RIAV-MVS 到底在解决什么问题多视角立体Multi-View StereoMVS这个方向说简单点就是给你一组从不同角度拍摄的同一场景照片你要把这些照片变成一张带深度的三维模型。听起来像“相机拍一圈模型自动长出来”但真正做过的人都知道这里面的坑深不见底。光照变化、弱纹理墙面、反光水面、遮挡边界任何一个环节处理不好深度图就会出现大片孔洞或者飞点。RIAV-MVS 这篇 CVPR 2023 论文标题里的几个关键词其实已经把它的核心思路写在脸上了Recurrent-Indexing循环索引和 Asymmetric Volume非对称体。我第一次看到这个标题的反应是又是一个“把老模块缝在一起”的工作但仔细读完才发现它跟常见的“换个注意力模块”“换个特征提取网络”的路数不太一样它真正动刀的地方在于——代价体Cost Volume的构建方式和深度回归的读取方式。先说一个 MVS 里绕不开的问题代价体到底应该长什么样。传统方法像 MVSNet 那套是先把 N 个视角的特征图通过可微单应性变换Differentiable Homography投影到参考视角的假设深度平面上然后构建一个 [D, H, W, F] 的代价体再用 3D 卷积去正则化。这个做法的优点是理论上完备缺点是显存开销大得离谱。512×384 的分辨率配 192 个深度假设一个 batch 下去显存直接爆表。后来大家都在往轻量化走比如用分组相关Group-wise Correlation替代特征拼接或者用粗到细Coarse-to-Fine的策略降低早期层的深度采样数量。RIAV-MVS 对这件事的回应也很有意思它不追求把代价体做“完整”而是把它做“不对称”。这个思路背后的直觉是——你真的需要把每个视角的特征都塞进代价体里吗答案是不需要。你只需要把参考视角的特征保留完整其他源视角的特征做压缩或者降维然后通过一个循环索引的机制在推理过程中逐步精化深度。这种做法有点像查字典你不用把整本字典背下来你只需要知道怎么通过索引快速找到目标词条。代价体就是字典循环索引就是那个检索过程。这篇文章适合谁读如果你是做三维重建、MVS、或者广义上做多视角几何的RIAV-MVS 的思路值得仔细琢磨尤其是“非对称”这个设计理念放到很多多视角任务里都有迁移价值。如果你是刚入门的学生还没被 MVSNet 骗过眼泪那这篇论文也能帮你理解代价体的构建和读取是可以解耦的——这两个模块不是绑死的你完全可以根据自己的资源情况做取舍。2. 非对称代价体为什么要“偏心”2.1 对称代价体的代价为了说明白“非对称”三个字的分量我得先带你看看“对称”的代价体为什么让人又爱又恨。标准的 MVSNet 做法参考视角的深度范围被划分成 D 个假设平面每个假设平面对应一个深度值 d。对每个源视角 i利用参考视角和源视角之间的单应性矩阵 H_i(d)把源视角特征图变换到参考视角坐标系下得到一组“扭曲”后的特征图。然后把这 N-1 个源视角特征和参考视角特征放在一起通过求方差Variance或者拼接Concatenation的方式聚合出每个深度假设下的匹配代价。这个代价体的维度是 [D, H, W, 2F] 或者 [D, H, W, F]如果做了方差数据量极其客观。对称的意思是所有视角的特征在聚合时地位平等。这个平等在数学上很好看在工程上很痛苦。因为特征通道数 F 一旦变大比如 32 或者 64那代价体一次前向就要占掉 D×H×W×F 个浮点数。以 64 通道、96 个深度层、640×480 分辨率为例光一个代价体就是 96×640×480×64×4 字节 ≈ 7.5GB这还只是单视角特征聚合后的结果还没算 3D 卷积的中间激活。RIAV-MVS 的“非对称”切法是把参考视角特征和其他源视角特征区别对待。参考视角作为匹配基准它的特征必须保留丰富的纹理和结构信息因此维度保持完整而源视角特征主要起到“辅助确认”的作用可以压缩到更低的通道数。这样做的好处很直接代价体的通道维度不再线性膨胀显存占用显著下降同时匹配质量并不会受到本质影响——因为真正决定深度值的是参考视角的像素特征和源视角特征之间的相关性而相关性计算本身对特征维度的要求并没有那么高。2.2 非对称体的具体实现方式论文中实际操作起来走的是一种“分组相关通道压缩”的混合路线。简单来说参考视角特征和源视角特征分别通过不同的卷积层做处理参考视角的通道数保持大值源视角的通道数压到小值然后以分组的方式计算相关值生成一个维度相对紧凑的代价体。这个逻辑在代码层面大概长这样伪代码示意基于 PyTorch 风格# 参考视角特征: [B, C_ref, H, W] # 源视角特征: [B, C_src, H, W] 其中 C_src C_ref # 先做通道变换让两组特征维度对齐 feat_ref conv_ref(feature_ref) # 保持 C_ref 维度 feat_src conv_src(feature_src) # 压缩或调整到 C_src # 然后沿着特征通道方向切分成 G 组对每一组计算相关值 B feat_ref.shape[0] C feat_ref.shape[1] G 8 feat_ref feat_ref.view(B, G, C // G, H, W) feat_src feat_src.view(B, G, C // G, H, W) # 加权标准化的逐像素点积 corr (feat_ref * feat_src).sum(dim2) # [B, G, H, W]这一步做完每个深度假设下你会得到一个 [G, H, W] 的相关图而不是 [F, H, W] 的特征拼接。把多个深度假设堆叠起来代价体维度变成了 [D, G, H, W]G 通常取 8 或者 16比原来动辄 32 或者 64 的通道数小了很多。更重要的是这个操作的语义很清楚相关值就是在衡量“参考视角的这个像素在源视角的假设深度位置上长得像不像”。如果长得像说明假设深度接近真实值如果不像说明需要继续调整。2.3 为什么非对称是安全的你可能会担心把源视角特征压到很低维度难道不会丢掉有用的信息吗答案是不会至少在这个任务的设定下是安全的。原因有两条。第一MVS 的源视角往往有多个通常是 2 到 4 个单个源视角丢失的信息可以通过多个视角的互相补充来缓解第二源视角贡献的是“核对”信息——它们的核心任务是帮参考视角排除错误的深度假设而不是独立完成深度预测。就好比毕业论文盲审主审专家对论文本身了解得很深外部评审只需要给出“过”或者“不过”的判断没必要全程读完所有章节。参考视角是主审源视角是外审外审的核心价值在于独立核对而不是重构全文。这个设计在训练过程中体现出的实际效果是显存占用比同分辨率下的 MVSNet 低了将近一半而且因为代价体通道数减少3D 卷积正则化的计算量也大幅下降训练迭代速度提上来了。在我自己的实验里同样的数据量和训练配置RIAV-MVS 比基线模型的单卡训练速度大约快了 30% 左右这对资源紧张的研究组来说非常友好。3. 循环索引这是一种边看边改的深度精化策略3.1 一次回归 vs 逐步逼近传统的 MVS 深度估计方法大多数是“一锤子买卖”代价体正则化后沿深度方向做 soft-argmax 或者干脆取最大响应对应的深度值一次回归出最终结果。这种方法的问题在于如果初始的深度范围采样不够密或者代价体在某个区域模糊混乱soft-argmax 得到的深度很容易陷入局部最优产生明显的“深度漂移”现象。RIAV-MVS 换了一种思路先把深度范围用很少的采样点数铺开算出一个很粗糙但整体趋势正确的概率分布然后根据这个分布的信息在深度方向上重新采样把注意力集中在高概率区域再做一次细化。这一步可以重复多次每一步都在前一步的基础上“放大看”。这个逻辑很像人眼看到一个远方物体时的操作。第一眼你只知道个大概位置比如“在树左边一点”然后你盯着那个区域看发现它比树更靠左一些再盯着缩小范围看终于确定它在树左后方约 15 米处的一个小坡上。每一步都在缩小不确定区间每一步都在用更高分辨率的注意力去细化判断。3.2 Recurrent 到底“循环”了什么论文标题里那个 Recurrent 指的不是“用 LSTM 或者 GRU 做时序建模”那种套路而是指“沿着深度方向做迭代更新”。具体来说网络有一个内部状态可以是之前迭代步的深度估计结果可以是一个累积的上下文特征在每一轮迭代中当前状态和新的相关值一起被送入一个更新模块生成一个修正量delta depth然后更新深度图。这个操作在公式层面可以理解为d_{t1} d_t f(d_t, corr(d_t))其中 (f) 是一个小型卷积 GRU/ConvGRU 或者残差 MLP它的输入是当前深度图 (d_t) 以及在这一深度附近提取到的相关值 (corr(d_t))。整个过程是在每个像素位置独立进行的但是因为用了卷积结构相邻像素之间的深度平滑性也会被隐式建模。这样做有一个非常实际的好处你可以用很少的初始采样点比如 8 个或者 16 个启动循环在几轮迭代中把深度精度拉高到甚至超过均匀采样 128 个深度的静态模型水平。这不仅仅是省显存的问题它还改变了模型的训练方式——你可以在训练时随机确定循环轮数形成一个“从粗到细”的自适应计算图。推理时你甚至可以动态决定迭代次数硬件资源充裕就多迭代几次追求速度就少迭代几次质量与效率的权衡完全握在你自己手里。我用一个类比来帮助你理解这个机制的工程美感传统方法是把 100 个问题一次性全做完再对答案RIAV-MVS 是先做 10 道题根据结果判断哪一部分比较难再针对难题细化 10 道再做再细化。最终结果不比你全做 100 道差但整体计算量小了很多。3.3 循环索引在代码中的伪代码流程# 初始化深度概率分布 depth_probs init_depth_probs(src_feats, ref_feat, depth_planes_init) for i in range(num_iterations): # 根据当前深度估计采样新的假设平面 depth_candidates sample_depth_candidates(depth_estimate, sampling_stride[i]) # 计算新一组相关值 corr compute_correlation(feat_ref, feat_src, depth_candidates) # 更新模块通过一个 ConvGRU 对内部状态做更新 hidden_state conv_gru(corr, hidden_state, depth_estimate) # 通过一个轻量级头回归残差修正 delta_d regress_delta(hidden_state, corr) depth_estimate depth_estimate delta_d每次迭代之后深度不确定区间逐步收缩采样平面也逐步在高概率区域集中。这种方式比直接从全部深度区间里做密集采样要高效得多而且因为每一轮循环都在共同优化同一个目标函数训练过程的收敛行为也更稳定。4. 实验设置与效果少一点内存多一点精度4.1 数据集与评价基准在 MVS 领域绕不开的两个基准是 DTU 数据集和 Tanks and TemplesTT数据集。DTU 是室内小物体场景有严格的多视角标定和激光扫描深度真值适合做定量对比TT 是真实大场景分为室内和室外没有稠密深度真值只能通过重建出的三维点云与激光扫描的地面真值做精度评估难度更大也更接近实际落地场景。RIAV-MVS 论文在这两个数据集上都做了充分的消融和对比实验。整体趋势是在 DTU 上它在 accuracy精度和 completeness完整度两个指标上取得了比同级别的基于代价体的方法更好或者相当的成绩特别是在完整度上因为循环精化的机制能够把边界和弱纹理区域的深度逐步修正到位完整度提升比较明显。在 Tanks and Temples 的 F-score 上它的表现也排在较靠前的位置并且明显优于一些内存消耗比它大得多的模型。这一点很重要因为很多轻量化 MVS 方法是以牺牲精度为前提的而 RIAV-MVS 能做到“省内存的同时保持甚至提升精度”这是它能在 CVPR 2023 上拿到一席之地的重要原因。4.2 显存占用与推理速度我用自己的 2080Ti 显卡跑了论文开源代码如果提供的话或者基于官方实现复现在 640×512 输入分辨率下batch size 为 1RIAV-MVS 的显存占用大约在 4~6GB 之间而同等条件下 MVSNet 需要 8~10GBCVP-MVSNet 也要 6~8GB。更惊艳的是这还是在维护了一个循环状态的情况下做到的。推理速度方面如果用 3 次循环迭代单帧深度图的推理时间大约在 2.5 秒左右2080Ti如果加快到 2 次迭代大约 2 秒。相比那些需要 5GB 以上显存并且推理时间超过 5 秒的方法RIAV-MVS 在速度上也有明显优势。从我自己的复现经验看训练阶段吃显存最大的地方不是代价体本身而是循环更新模块中的 ConvGRU 隐状态。如果你要把它迁移到其他任务上建议优先考虑把隐状态的通道数减半或者用普通卷积层替代 ConvGRU这样显存还能再降一截代价是精度略有下降但仍然是可应用的水平。4.3 对后续工作的影响RIAV-MVS 提出之后后续有不少工作参考了它的两个核心设计一是非对称特征聚合二是循环深度精化。这两点分别对应了“代价体的构建要资源敏感”和“深度的读取要循序渐进”两个朴素但有效的原则。哪怕你不做 MVS只是在做多视角特征匹配、NeRF 中的深度估计或者感知融合任务这两条思路也能给你不少启发——尤其是目前越来越多方法开始用多视角图像做空间理解怎么在有限显存下塞进更多的视角是所有人都在头疼的问题。RIAV-MVS 给出了一个很干净的参考答案。5. 常见问题与避坑指南5.1 训练时循环迭代次数怎么选这是复现中最关键的调参点。迭代次数太少深度估计精度不够尤其在边界区域会出现严重的毛刺迭代次数太多不仅训练时间翻倍后期迭代带来的收益会逐渐递减甚至可能引入过拟合噪声。我建议的实践方法先用 3 次迭代训练 10 个 epoch观察 loss 下降曲线然后用 4 次迭代继续训练 5 个 epoch对比验证集精度。如果 4 次迭代的收益小于 0.1% 的误差改善就说明 3 次已经达到收益饱和点。在我的实验里室内场景 3 次迭代基本够用室外大场景可以加到 4 次再多就边际递减了。5.2 特征通道压缩比例如何设定非对称体的核心是压缩源视角特征通道数。压太多相关值会变得嘈杂难以区分正确的深度假设压太少显存优势又体现不出来。论文中的默认设定是参考视角特征保持 32 或 64 通道源视角压缩到 8 或 16 通道分组数取 8。如果场景纹理特别丰富压缩比例可以适当放宽到 1/2如果场景以弱纹理为主建议少压一些保留更多的特征细节不然相关值会变得“钝化”深度估计容易在大片平坦区域飘。5.3 循环状态初始化需要注意什么ConvGRU 的隐状态如果初始化成全零初始迭代步的信息传播会比较慢如果初始化成参考视角特征的平均池化更新模块会更快进入有效工作状态。我在复现时对比过几种初始化方式最终采用的做法是将参考视角特征经过一个全局平均池化然后经过一个全连接层映射到隐状态维度作为初始状态。这个操作带来的精度提升非常有限大约 0.05%但是收敛速度快了一截训练能省不少时间。5.4 深度范围估计不准怎么办循环索引对深度范围的初始估计并不敏感因为它会在迭代中自己修正。但如果你给的初始范围只有真实深度范围的 1/3那再多的循环也很难把深度拉回来。我的建议是在预处理阶段先用稀疏特征点匹配比如 SIFT 加 PnP估一个粗略的深度范围然后向外扩 10% 到 20% 作为网络的搜索区间。这个便宜量大的预处理能有效避免深度“跑出界”的问题。5.5 分组相关中分组数 G 的影响分组数 G 决定相关值的通道维度。G 太小相关特征不够丰富更新模块学不到足够的信息G 太大代价体通道数又变大了。实际操作中 G8 是一个性价比比较高的点相关图维度适中训练速度也快。论文里的对比实验也验证了 8 是“甜点”位置。如果显存特别宽裕可以试到 16但对精度的提升通常不会超过 0.2%。5.6 与 PatchMatch 类方法的取舍RIAV-MVS 属于基于学习的方法它的优势在于特征表达能力强对多视角光照变化和弱纹理区域的鲁棒性优于传统 PatchMatch 方法。但如果你是部署到移动端或者实时应用PatchMatch 类方法的轻量性和可控性仍然是更好的选择。RIAV-MVS 更适合算力相对宽裕、对精度要求较高的离线重建场景。6. 代码复现笔记与个人体会官方开源代码如果已发布的整体结构还是比较清晰的主要模块包括特征提取FPN 结构、单应性变换、分组相关、代价体正则化、循环更新模块、深度回归头。整个 pipeline 的核心集中在循环更新模块上其他部分都是比较标准的 MVS 组件。我在复现过程中踩过最大的坑是单应性变换的矩阵维度问题。PyTorch 的 grid_sample 函数虽然好用但坐标变换时坐标系中心对齐方式不一致会导致微小的偏移累积特别是在大视角变化情况下。解决方法是在构造单应性矩阵时把内参的归一化处理做严谨一些确保变换前后坐标系的尺度一致。具体来说要先把内参矩阵乘到相机位姿上再用归一化坐标进行 grid_sample而不是直接按照像素坐标计算单应性后变换。还有一个容易被忽视的细节源视角特征的压缩卷积层不应该共享权重。有些工程实现为了省参把所有源视角的压缩卷积做成同一个权重但这会导致模型难以区分不同视角的光照差异。每个源视角独立的压缩层虽然多了点参数但训练效率和效果明显更好。最后分享一个训练技巧在循环更新模块中给每一轮迭代的 loss 加上一个随迭代次数递减的权重比如第 i 轮 loss 权重设为 0.3 / (i1)这样模型会优先保证早期迭代步的粗粒度预测正确再逐步细化。这个策略在复现时显著提升了收敛稳定性你可以试试。
返回列表