简介:面向深度学习三维重建学习者与MVS方向研究者的MVSNet系列笔记总结资源,系统梳理从MVSNet(CVPR 2018)到PatchMatchNet(CVPR 2021)等代表性模型的演进脉络,覆盖3D Cost Volume、GRU循环网络、特征金字塔、Patch-wise聚合、稀疏代价体、点云表示等关键策略,并针对单应性变换推导、代价体构造、深度估计与优化等方面给出公式细节与易错点说明,有助于快速把握深度MVS从内存受限到高效高精的整体发展线索。压缩包内为1个docx文档,共53页约3.75MB,采用目录导航式结构,开篇先按输出表示总结MVS三类方法(直接点云重建、体积重建、深度图重建),后续逐个拆解MVSNet、R-MVSNet、Cascade-MVSNet、P-MVSNet、Fast-MVSNet、CVP-MVSNet、Point-MVSNet、PatchMatchNet的论文亮点与网络设计,并穿插PyTorch代码笔记。目前这份总结在CSDN已有862人学习下载,适合课程复习、考研保研面试准备或作为三维重建专题的查漏补缺手册。
1. 深度学习三维重建的笔记不该是流水账
看过 MVSNet、R-MVSNet、CasMVSNet 再到各种 Transformer 变体的人,大多会有同一种体验:单篇论文能看懂,但要横向比较的时候,发现每篇的网络结构图画法都不一样,正则化模块换了名字,深度假设平面数目改了,损失函数括号里的权重也各有说法。深度学习三维重建本来就是一个多模块的串联系统,特征提取、成本体构建、正则化、深度回归、深度图融合各自独立又互相影响,任何一个环节的小改都会让实验结果对不齐,这也是为什么“笔记总结”和“问题总结”比阅读本身更重要。这篇博客围绕 MVSNet 系列顶刊工作,给出一种既能记录原理、又能服务于复现的笔记方法论,同时把训练和评估中常见的坑做成可复用的问题清单。适合正在系统性阅读三维重建论文的研究生,也适合在工程里快速验证 SOTA 方法的算法工程师。
2. 从 MVSNet 到最新顶刊:先建立论文对比的骨架
MVSNet 系列已经发展出清晰的谱系,读论文时如果只看单篇,容易遗漏它们之间共享的基线和各自的增量贡献。我一般会把每篇论文拆成“输入输出、前端、成本体、正则化、深度回归、损失、训练细节”七个格子,然后按版本演进填表。这样对比出来的差异才是可以落到代码里的,而不是只看图。
2.1 成本体构建与正则化:MVSNet 的基线逻辑
最初的 MVSNet 把不同视角的特征图通过单应性变换投影到参考相机的假设平面上,构建一个 N×D×H×W 的成本体。这里的 D 是深度假设平面数量,H、W 是特征图尺寸。成本体通过方差操作聚合多视图特征,随后用 3D 卷积进行正则化,回归出深度概率分布。这个思路把多视图立体重建问题变成了一个可微的深度采样与分类问题,后续绝大多数工作都保留了“不同深度平面上做特征匹配”这个核心操作。
R-MVSNet 引入循环正则化,用 GRU 沿着深度方向逐步处理成本体,显著降低了显存占用。CasMVSNet 则采用粗到细的策略,先在低分辨率下估计深度范围,再在更高分辨率下细化,避免在原始分辨率上直接构建巨大的成本体。这三篇是理解整个系列的重要基准。把它们的区别写进笔记时,我会特别标注“正则化维度”和“显存曲线”,因为这两个参数直接决定了后续复现时的硬件需求。
2.2 顶刊变体:跨视图注意力与动态迭代
近几年顶刊的工作基本集中在两个方向:一是用注意力机制替代固定的方差聚合,二是用迭代优化替代一次性的深度回归。跨视图注意力试图解决遮挡和弱纹理区域的匹配歧义,通过让每个参考像素去关注所有源视图的相关区域,而不是对所有特征做等权融合。迭代式方法,例如类似 RAFT 的更新策略,把深度估计视为一个从初始化解逐步修正的过程,每次迭代输出深度残差和置信度。
这些工作对笔记总结提出了新要求:不能再只记录网络层数,而要记录注意力作用的位置(是特征级还是成本体级)、迭代次数对精度的影响曲线、训练时是否使用了循环一致性损失。我的做法是在每条笔记后面加一个“与 MVSNet 的差异”字段,只写一句增量,例如“把 3D 卷积换成注意力,但成本体仍然显式构建”。这样等复习的时候,只需要看差异句就能想起整篇论文的贡献。
2.3 用表格记录参数与设计选择
整理论文笔记时,一个结构统一的表格远比长篇文字有效。下面是我常用的对比模板,字段根据论文选择填写:
| 模型 | 深度表示 | 成本体构建 | 正则化 | 显存特点 | 备注 |
|---|---|---|---|---|---|
| MVSNet | 均匀采样 | 单尺度方差 | 3D CNN | 高 | 基线 |
| R-MVSNet | 均匀采样 | 单尺度方差 | 循环 GRU | 中 | 深度方向序贯 |
| CasMVSNet | 粗到细 | 多尺度级联 | 3D CNN | 中 | 先粗后精 |
| TransMVSNet | 均匀采样 | 特征级注意力 | 自适应令牌 | 中高 | 引入跨视图注意力 |
表格里的“深度表示”一列值得重点维护,因为很多新论文会把均匀采样换成可学习的深度偏移量,这一变化会直接影响回归损失的计算方式。我在记录时会把对应的训练超参数也放进去,比如深度平面数从 192 降到 96 时,batch size 能翻倍,但精度会掉多少,需要实验才能知道。表格不是死数据,而是准备跑实验前的检查清单。
2.4 用 Python 整理对比笔记
手写表格效率太低,我会用一个小脚本把论文要点结构化存储,然后自动生成 Markdown 表格。脚本本身不复杂,但它强制我每次读论文时把信息压缩成固定字段,比自由摘录更容易暴露“我其实没看懂”的地方。
# papers.py import json papers = { "MVSNet": { "depth_rep": "uniform", "cost_volume": "variance-based", "regularizer": "3D CNN", "memory": "high", "note": "ECCV 2018 baseline" }, "CasMVSNet": { "depth_rep": "coarse-to-fine", "cost_volume": "cascade", "regularizer": "3D CNN", "memory": "medium", "note": "CVPR 2019, 3 stages" }, } def to_markdown(data: dict) -> str: header = "| 模型 | 深度表示 | 成本体 | 正则化 | 显存 | 备注 |" sep = "|---|---|---|---|---|---|" rows = [header, sep] for name, attr in data.items(): rows.append( f"| {name} | {attr['depth_rep']} | {attr['cost_volume']} | " f"{attr['regularizer']} | {attr['memory']} | {attr['note']} |" ) return "\n".join(rows) if __name__ == "__main__": print(to_markdown(papers))这个脚本把每个模型的关键设计写成字典,扩展新论文时只需要增加一个键值对。to_markdown函数负责生成表格,字段顺序与表格列保持一致。我通常在读完全文后马上记录,否则三天后再回忆,写出来的很容易变成“借鉴了注意力机制”这种没营养的话。参数说明:uniform表示深度均匀采样,cascade表示多阶段细化,variance-based对应 MVSNet 原版方差成本体。这类脚本的价值不在于自动化排版,而在于强迫你把信息归类到固定槽位。
3. 深度学习三维重建笔记的落地工具:Markdown、Git 与批处理
有了框架,接下来是笔记本身怎么组织。我的方案是纯 Markdown + Git,不用在线笔记平台,因为论文笔记包含大量图表和公式,还需要随时和代码仓库放在一起对照。
3.1 目录结构与模板
每个 MVSNet 系列项目我会单独建一个目录,结构如下:
mvs-notes/ ├── README.md ├── papers/ │ ├── MVSNet.md │ ├── R-MVSNet.md │ ├── CasMVSNet.md │ └── template.md ├── experiments/ │ ├── configs/ │ └── logs/ └── scripts/template.md是论文笔记模板,包含标题、作者、会议年份、核心思想、网络设计、损失函数、训练设置、实验结论、复现注意点九个部分。每读一篇新论文,直接复制模板,填充内容。experiments目录用来记录自己的训练实验,每个实验配置和日志单独存放,防止笔记和现实脱节。
写作模板时有一个原则:所有数值必须带有上下文。比如“深度平面数 192”要写成“输入分辨率 W×H 下,深度范围 [0.5, 5.5] 均匀切 192 份”,否则半年后回看,完全想不起来当时这个值对应什么场景。
3.2 用 Git 管理笔记版本
笔记和代码一样需要版本控制,因为我们对论文的理解会随着阅读量变化。用 Git 的好处是可以回滚,也可以查看某个结论是什么时候改的。
cd mvs-notes git init git add . git commit -m "init: MVSNet survey notes" git log --oneline -- papers/CasMVSNet.md上面的命令把整个笔记目录初始化为仓库,git log查看某个文件的所有提交记录。每次修改一篇笔记就提交一次,提交信息写清楚改了什么,比如“补充 CasMVSNet 的深度范围公式”。这比在文件里保留多个副本干净得多。
3.3 从 PDF 批量提取图表与文字
顶刊论文通常有很详细的网络结构图,但这些图在阅读器里不好标注。我会用pdftotext提取文字,用pdfimages提取图片,再配合tesseract做 OCR 把图表里的文本转成可检索内容。下面是常用的命令组合:
# 提取 pdf 中的纯文本 pdftotext -layout mvsnet.pdf mvsnet.txt # 提取所有图片,-png 输出为 png 格式 pdfimages -png mvsnet.pdf images/ # 对提取出的图片做 OCR(需要 tesseract 和中文语言包) tesseract images-000.png stdout -l chi_sim+eng > image-text.txtpdftotext -layout保留原始版面,公式和段落排布更接近阅读版。pdfimages会把 PDF 内嵌的每张图片单独导出,适合把网络结构图存到笔记对应的文件夹里。tesseract用于图片中的文字识别,虽然公式识别能力有限,但足以抽取训练参数表里的数字。提示:不要用pdfgrep替代pdftotext,前者只能做简单匹配,后者输出文本后配合 Vim 或 VSCode 做全局搜索更灵活。
3.4 用 Python 可视化深度分布
笔记里除了表格,还可以放训练集深度统计的直方图。MVSNet 系列的深度采样范围依赖场景深度分布,超参数设错会导致大量无效采样。下面这段代码读取预测深度和真实深度,绘制两者的分布对比:
# depth_hist.py import numpy as np import matplotlib.pyplot as plt def show_depth(pred_path: str, gt_path: str, save: str = "depth_hist.png"): pred = np.load(pred_path) gt = np.load(gt_path) plt.figure(figsize=(10, 4)) plt.hist(pred.ravel(), bins=100, alpha=0.6, label="pred") plt.hist(gt.ravel(), bins=100, alpha=0.6, label="gt") plt.xlabel("depth value") plt.ylabel("count") plt.legend() plt.savefig(save, dpi=150) print(f"save to {save}") print(f"pred range: [{pred.min():.3f}, {pred.max():.3f}]") print(f"gt range: [{gt.min():.3f}, {gt.max():.3f}]")np.load读取的深度图是单通道稠密图。bins=100把深度值划分成 100 个区间,分布对比能直观发现预测深度是否集中在某个区间,比如地面真实深度在 2 到 5 米,预测却集中在 0.5 到 1 米,说明深度范围配置有问题。这个脚本的save参数可以指定输出路径,pred range和gt range的输出是排查深度范围的重要参考。
4. 问题总结:训练 MVSNet 系列最常见的四个瓶颈
笔记总结如果不包含踩坑记录,就只是文献搬运。下面四个问题是我在复现 MVSNet 系列时反复遇到的,每个都和网络设计直接相关。
4.1 显存溢出:从降 batch 到梯度检查点
成本体的大小和分辨率、深度平面数、视角数成三次方关系。例如 192 个深度平面,1/4 分辨率下的成本体已经能占满 11GB 显存。减小 batch size 是最直接的方案,但 batch size 降到 1 后批量归一化的统计量会不稳定,因此需要同步减少 BN 的 momentum 或换成 InstanceNorm。
另一个做法是梯度检查点(gradient checkpointing),它不保存正向传播中的全部激活值,而是在反向传播时重新计算。实现起来通常只需要一行:
# train.py import torch cost_volume = torch.utils.checkpoint.checkpoint( regularizer, cv, use_reentrant=False )checkpoint包住的是成本体正则化模块,因为它占用的中间激活最大。use_reentrant=False是 PyTorch 2.x 的新接口,避免旧的重新进入模式的兼容问题。代价是训练时间增加约 20%,但换来的是 batch size 可以翻倍,总体迭代次数反而减少。提示:不要对包含小型 BatchNorm 的模块使用 checkpoint,因为重新计算时 running mean 的更新会不一致。
4.2 深度范围设定与成本体尺寸
深度范围是 MVSNet 系列最重要的超参数。设置过窄会截断真实深度,过宽会让采样间隔变大,深度精度下降。一个常见做法是把训练集所有场景的深度按分位数统计,取 2% 和 98% 的分位值作为范围,然后每个场景再额外加 10% 的余量。
# 用 python 统计深度范围 python - <<EOF import numpy as np, glob deps = [] for f in glob.glob('depths/*.npy'): d = np.load(f) deps.append(d[d > 0]) # 忽略无效深度 all_depths = np.concatenate(deps) low, high = np.percentile(all_depths, [2, 98]) print(f"depth_min: {low:.3f}, depth_max: {high:.3f}") EOF这段统计脚本把所有有效深度拼接起来,用 2% 和 98% 分位去除离群点。输出结果可以直接填进数据集的depth_interval字段。注意有些数据集的深度图会有一个极大的最大值,这类离群值会明显拉大范围,所以必须先过滤d > 0,再取分位数而不是 min/max。
4.3 损失函数与深度标签的关系
多数 MVSNet 系列使用 L1 损失,但在训练初期深度预测误差较大,L1 的梯度恒定为 ±1,容易在两个方向震荡。常见做法是把 L1 换成 smooth L1,它用小梯度处理残差小于 1 的情况,抑制噪声。但 smooth L1 的阈值不能直接套用,应该根据深度范围缩放。比如深度范围是 5 米,smooth L1 的阈值设为 0.5 米会比默认 1.0 更合理。
# loss.py import torch def smooth_l1_with_scale(pred, gt, scale=0.5): diff = torch.abs(pred - gt) loss = torch.where(diff < scale, 0.5 * diff**2 / scale, diff - 0.5 * scale) return loss.mean()scale参数控制平滑区域的大小,它和深度残差的绝对单位强相关。如果你的模型输出的是逆深度而不是深度,那么scale需要按逆深度的量级重新设定。这个公式在回归问题里很常见,但很少有人会提醒你针对深度范围调整scale。我在笔记里会给每个实验记录下scale的取值和对应的误差中位数,方便后续回归对比。
4.4 指标对比与点云可视化
论文里的精度指标是在固定阈值下计算的,例如 1mm、2mm、4mm 的准确率。自己训练时如果发现绝对误差不大,但阈值准确率低,往往不是模型坏了,而是预测深度存在系统性偏移。这时除了计算平均绝对误差,还要看误差分布直方图。下面是常用的评估代码:
# eval.py import numpy as np def evaluate_depth(pred, gt, mask, thresholds=(1, 2, 4)): diff = np.abs(pred - gt) * mask mae = diff.sum() / mask.sum() acc = [] for t in thresholds: acc.append(((diff < t) * mask).sum() / mask.sum()) return {"MAE": mae, "acc1mm": acc[0], "acc2mm": acc[1], "acc4mm": acc[2]}mask用于屏蔽无效像素,通常来自数据集的遮挡掩码。diff乘上mask后,无效像素的残差变成 0,避免拉低 MAE。acc1mm到acc4mm是三维重建论文常用指标。如果在调试时发现acc1mm明显低于论文复现值,先检查mask是否对应一致,很多数据集有多个 mask 版本,混用会导致指标完全不可比。
5. 一个技巧:把问题总结压缩成可执行的 checklists
笔记最后的部分,我会把每条踩坑记录改写成带触发条件的问题清单,而不是一段段警告文字。例如“显存溢出”这条,我写成 checklists:当 batch size 小于等于 2 时,检查是否开启了 gradient checkpoint;当显存占用波动大于 30% 时,检查是否使用了动态形状的成本体;当训练中断后恢复时,确认随机种子和 dataloader 的 shuffle 状态。每个条目都可以在半小时内验证,而不是一句“注意显存”。
具体做法是给每个问题编号,配合 Git tag 记录当时的代码版本。比如v0.1-train,v0.2-fix-depth-range这样的 tag,让笔记里提到的每次修复都能对应到可运行的代码。我的问题清单模板如下:
- [ ] 深度范围是否在训练集统计范围内?
- [ ] 代价体构建时是否保持 batch 维度与视角维度顺序一致?
- [ ] 损失函数中
scale参数是否与当前深度范围匹配? - [ ] 评估时掩膜是否来自训练集同一数据源?
- [ ] 是否记录了每次训练实验的完整 config 哈希值?
最后一行的 config 哈希值尤其关键。我会在训练脚本里加上自动记录 config 内容到日志目录:
sha256sum configs/casmvsnet.yaml > experiments/logs/$(date +%Y%m%d-%H%M).sha256sha256sum对配置文件生成哈希,这个哈希和训练日志放在一起,任何配置改动都能通过对比哈希发现。配合上面的 checklists,在复现顶刊结果时,每一条未勾选项都会成为排查方向的锚点。这个简单的“问题总结 + 配置哈希”组合,就能让笔记系统真正服务于实验,而不只是收藏夹里的又一份 PDF。
本文还有配套的精品资源,点击获取