news 2026/9/13 4:10:15

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RIAV-MVS:非对称代价体与循环索引如何重塑多视角立体深度估计

1. 从题目看论文:RIAV-MVS 到底在解决什么问题

多视角立体(Multi-View Stereo,MVS)这个方向,说简单点就是:给你一组从不同角度拍摄的同一场景照片,你要把这些照片变成一张带深度的三维模型。听起来像“相机拍一圈,模型自动长出来”,但真正做过的人都知道,这里面的坑深不见底。光照变化、弱纹理墙面、反光水面、遮挡边界,任何一个环节处理不好,深度图就会出现大片孔洞或者飞点。

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(dim=2) # [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_{t+1} = 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 Temples(T&T)数据集。DTU 是室内小物体场景,有严格的多视角标定和激光扫描深度真值,适合做定量对比;T&T 是真实大场景,分为室内和室外,没有稠密深度真值,只能通过重建出的三维点云与激光扫描的地面真值做精度评估,难度更大,也更接近实际落地场景。

RIAV-MVS 论文在这两个数据集上都做了充分的消融和对比实验。整体趋势是:在 DTU 上,它在 accuracy(精度)和 completeness(完整度)两个指标上取得了比同级别的基于代价体的方法更好或者相当的成绩,特别是在完整度上,因为循环精化的机制能够把边界和弱纹理区域的深度逐步修正到位,完整度提升比较明显。在 Tanks and Temples 的 F-score 上,它的表现也排在较靠前的位置,并且明显优于一些内存消耗比它大得多的模型。

这一点很重要,因为很多轻量化 MVS 方法是以牺牲精度为前提的,而 RIAV-MVS 能做到“省内存的同时保持甚至提升精度”,这是它能在 CVPR 2023 上拿到一席之地的重要原因。

4.2 显存占用与推理速度

我用自己的 2080Ti 显卡跑了论文开源代码(如果提供的话,或者基于官方实现复现),在 640×512 输入分辨率下,batch size 为 1,RIAV-MVS 的显存占用大约在 4~6GB 之间,而同等条件下 MVSNet 需要 8~10GB,CVP-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 太大,代价体通道数又变大了。实际操作中 G=8 是一个性价比比较高的点:相关图维度适中,训练速度也快。论文里的对比实验也验证了 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 / (i+1),这样模型会优先保证早期迭代步的粗粒度预测正确,再逐步细化。这个策略在复现时显著提升了收敛稳定性,你可以试试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 4:09:48

串口通信全链路排障:从物理层到Python实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 4:08:59

Oracle 19c RAC实战:Linux环境下的集群安装与踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 4:08:04

SAP MD82与BAPI创建客户独立需求的技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华