1. 这篇论文想解决什么问题
单目深度估计一直是三维视觉里最让人头疼的问题之一。最早做无监督单目深度估计时,“没有深度标注又想预测出可用的深度图”这件事乍看像是自相矛盾的。直到把这套以 Left-Right Consistency 为核心思路的论文完整啃完,我才真正意识到,原来左右视图的几何关系本身就是训练信号,根本不需要人工给像素标深度。这篇笔记适合正在入门无监督深度预测的开发者,也适合想复现双目自监督深度模型的工程师,以及那些对自监督三维视觉感兴趣的算法同学。
要理解这个方法的价值,得先回到一个最基本的痛点:单目深度估计最常见的监督做法,需要大量像素级深度真值作为训练目标。可深度真值从来都不便宜。用深度传感器采集的数据有噪声、有空洞,边缘也不干净,标人的工作量极大。既然直接回归深度这条路在数据环节卡得死死的,那能不能在训练时用一个不需要真值的约束来替代监督?论文给出的答案是:让左右相机图像互相“校验”。
训练阶段输入一对双目图像,网络分别预测左右两张视差图,然后用视差图把左图采样成右图,再把右图采样成左图,对比原始图像计算重建误差。这是很多自监督深度估计方法都有的设计,但这篇工作最有辨识度的地方,是在图像重建之外又加了一个显式的 Left-Right Consistency 损失,让左右两条分支预测出的视差不仅各自合理,而且几何上完全互洽。测试时只用一张左图,却能拿到比单纯图像重建约束高出一截的深度结果。
如果你是第一次接触这个方向,我的建议是先记住一句话:这里说的“无监督”并不是说没有任何约束,而是把约束从“人工深度标注”换成了“双目图像之间的几何一致性”。有了这个前提,后面读方法、看损失、复现代码都会顺很多。
1.1 监督深度估计的“昂贵”瓶颈
传统监督式单目深度估计,训练时给网络一对输入,希望网络输出的深度图尽量接近真值。损失可以很简单:L1 loss 或 L2 loss,算一下预测深度和真值深度之间的差距,反向传播更新参数。问题来了,真值从哪来?
以自动驾驶场景为例,激光雷达扫描出来的点云是稀疏的,很多像素位置没有深度值。即便用深度补全得到稠密深度图,其质量往往也不均匀,尤其是远处物体、反射面和细小物体边界,深度真值本身就不可信。如果想要在不同天气、不同城市、不同传感器配置下都能用,就需要持续不断地重新采集和标注数据。这套流程既慢又贵,且数据覆盖率很难做全。对于很多三维视觉落地团队来说,标注成本往往是比模型精度更致命的瓶颈。
于是研究人员换了个思路:虽然我们没有稠密深度真值,但我们有双目相机。只要知道两个相机之间的基线长度和焦距,在理想情况下,视差和深度一一对应。如果模型输出的视差是对的,那么左图里每个物体在右图里的位置就是可预测的,反过来也一样。与其去拟合一个稀疏、有噪声的深度真值,不如直接用“把左图变到右图是否自洽”来当监督信号,把几何关系本身变成训练目标。
1.2 用双目几何替代深度标注
双目自监督的出发点非常优雅。对一组已经完成极线矫正的双目图像对,左右两幅图像的同一个三维点只存在水平方向的位移,这个位移量就是视差。视差和深度反过来就成简单关系:深度等于焦距乘以基线长度再除以视差。所以模型只要估计出准确的视差,深度自然就出来了。
训练网络时,如果我们有一张左图 L 和一张右图 R,还预测出左视差图 d_l,那么对于 L 中任意一点 p,它应该在右图的位置是 q = p - d_l(p)。顺着这个位置去右图采样像素,就能得到一张“重建出来的左图视角右图”。同理,用右视差也能重建出另一种视角的图像。把重建图和原图对比,如果两者不一致,说明视差图错了;如果一致,说明当前视差至少在几何上是合理的。
这个方案最大的诱惑在于:它可以无限量地从双目数据中学习,不需要任何人工深度标注。训练数据就是最普通、最廉价的双目视频帧。推理时更是只需要单张左图,因为训练好的网络已经学会了从图像外观中推测深度,不需要在运行时再做立体匹配。这也就是“单目深度估计”这个名称的由来:训练时是双目的,推理时是单目的。
1.3 核心贡献一句话
我把这篇论文的贡献提炼成一句话:网络同时预测左右两幅视差图,然后引入一个轻量的左右一致性约束,让两个分支的预测在几何上互相校验,既提升深度精度,也帮助处理遮挡和边界。
这个“一致性”并不是传统立体匹配中的硬约束,而是一个软性的损失项。它不要求左右视差在每一步都绝对相等,而是让它们在整个训练过程中逐步趋向一致。训练的最终产物却是一个单目深度模型。更妙的是,在测试时,同一个左右一致性检查还能用来生成一个可信区域掩码,并对可信像素做左右视差平均,进一步降低误差。后面我会围绕这几个核心设计点拆开细讲。
2. 方法核心:Left-Right 一致性是怎么设计的
这一节是整篇笔记最重点的部分。Left-Right Consistency 不是只在 loss 里加一个式子那么简单,它需要网络结构、图像采样、多尺度预测和推理后处理全部配合起来。理解了这个设计,你才算真正读懂这篇论文。
2.1 双分支网络与视差预测
方法的主体是一个编码器-解码器结构,输入一对已经完成极线矫正的双目图像,输出左视差图和右视差图。如果网络只有一个解码器,那它只能输出一张视差图,很难对左右两张图同时做一致性约束。因此原方法的做法是让编码器共享参数,解码部分分成两个分支,一个负责预测左视差,另一个负责预测右视差。
共享编码器这件事很好理解。左右两张图拍摄的是同一个场景,底层特征如纹理、边缘、物体形状基本一致。如果让两个 encoder 各跑一遍,参数翻倍,显存压力大,而且两个特征空间未必对齐。共享 encoder 后,两个解码器只需要分别从同一套特征里“解”出各自的视差图,简单高效。
视差预测时,网络输出是像素级数值。直接把视差当普通回归目标很容易导致输出值不可控,所以常见实现是把网络最后一层输出过 sigmoid,再乘以一个最大视差常数。这样视差被限制在一个合理范围内,比如图像宽度的 30% 或固定 192 像素。具体数值要看数据集的相机基线和物体远近范围,如果最大视差设置得太小,近处物体深度会被压到极限,导致训练不稳定。
2.2 外观重建损失:让两张图互相说得通
有了左右视差图,下一步是构造图像重建损失。以左图重建为例:从左视差图的每个像素出发,找到右图中对应像素,用双线性采样把右图像素“搬”回左图视角,得到重建左图。双线性采样最关键的性质是可微,像素偏移产生的梯度能反向传播到视差图。正因为这个可微采样操作,整张网络才可以端到端训练。
重建损失一般不是单纯 L1,而是 L1 和 SSIM 的加权组合。L1 对每个像素一视同仁,但在光照变化、低纹理区域容易出问题。SSIM 对局部结构更敏感,当两张图存在轻微亮度偏移时,SSIM 仍能给出合理相似度。我用得比较多的组合是 0.85 的 SSIM 项加 0.15 的 L1 项,对应到公式就是:
L_app = 0.85 * (1 - SSIM(L, L')) + 0.15 * ||L - L'||_1
其中 L' 是重建左图。左右两个方向都做同样计算,然后把两个损失平均或相加。为什么至少要两个方向都做:如果只做从左到右的重建,左图中被前景遮挡、只在左视图可见的区域,在右图中找不到对应点,网络会学出错误的视差。加上从右到左的重建,可以一定程度缓解单方向的遮挡问题,再配合左右一致性损失,能进一步缓解这种区域的不确定性。
2.3 左右一致性损失:关键中的关键
这是整个方法里最有含金量的一步。左视差和右视差之间存在严格的几何约束。对左图某个像素 p,它对应的三维点在右图的位置是 p - d_l(p)。既然这一点是同一个三维点的投影,那么右视差图在 p - d_l(p) 位置的值,理论上应该和 d_l(p) 一样。
写成损失就是:
L_lr = | d_l(p) - d_r(p - d_l(p)) |
也就是说,用左视差作为索引,从右视差图里采样一个值,再把采样到的值和左视差值比较。两个方向的损失都计算,一个是从左往右校验,另一个是从右往左校验。
这里有一个非常容易混淆的方向问题。对于右视差图里的点 q,它对应的左图位置是 q + d_r(q),而不是 q - d_r(q)。如果代码里把方向写反,左右一致性损失将失去意义,严重时整个网络训练都会震荡甚至发散。后面我会专门展开说怎么排查。
这个损失项的价值体现在两个层面。第一,它让左右两个分支的预测不再是独立输出,而必须互相“对齐”。第二,训练完成后它可以直接用于推理阶段的后处理,生成可信区域掩码。很多类似方法只是简单用了图像重建损失,没有显式约束左右一致性,边界和遮挡区域的表现通常不如这篇。
2.4 视差平滑与多尺度输出
仅仅有外观重建和左右一致性还不够。低纹理区域,比如白墙、天空、地面,外观重建损失非常小,模型会有无数种视差组合都能把图像重建得很好。为了抑制这种歧义,方法中加入了边缘感知的平滑项:在图像梯度大的地方允许视差突变,在图像平缓区域则尽量让视差变化不要那么剧烈。
平滑项的做法是,将视差图的横向、纵向梯度乘上一个由图像梯度的负指数构成的权重。如果图像在某处有强边界,那么权重很小,视差在这里可以正常跳变;如果图像在平坦区域,权重接近 1,视差突变会受到惩罚。这样模型既能在物体边界保持清晰,又不会在无纹理区域出现一团乱麻。
此外,网络在高、中、低多个尺度上分别输出视差图,再上采样到原图尺寸计算损失。多尺度预测帮助网络先从全局把握大致结构,再逐步细化细节。在实际复现中,多尺度损失还能让低频大区域更快收敛,相比只在最细尺度上算损失,整体训练更稳定。
2.5 推理阶段的后处理:左右一致性检查
训练完成后的模型,按理说只用左图预测左视差就够了。但作者发现,训练好的右分支也不是没有用。在推理阶段可以从左视差 d_l 出发,用右视差 d_r 做一次一致性检查:
如果 |d_l(p) - d_r(p - d_l(p))| <= 1(像素),认为该位置是可信的,最终结果取 d_l(p) 和 d_r(p - d_l(p)) 的平均值;如果超过 1,则认为该位置可能处于遮挡、边界或反射区域,直接保留 d_l(p) 作为最终结果。
这样做有双重好处。对于大面积可信区域,左右视差平均能抵消一部分随机误差,让深度更平滑,定量指标会明显改善。对于遮挡区域,直接通过一致性检查把它们剔除在平均之外,避免右分支的不可靠信息污染结果。这个后处理过程不需要重新训练模型,是纯粹的推理技巧,但效果非常明显。
3. 复现层面的关键实现与训练细节
读论文是一回事,真正动手复现又是另一回事。这一节我会把从网络搭建到训练调参过程中,最关键的几个实现细节按顺序列出来。很多坑我在第一次复现时都踩过,这里直接告诉你应该怎么做,能省不少时间。
3.1 网络结构选型
我复现时建议采用编码器-解码器加跳跃连接的结构,也就是类似 U-Net 的形态。编码器可以选用残差网络作为骨干,解码器通过逐步上采样恢复空间分辨率。值得注意的是,解码器中的上采样不建议全部用转置卷积,转置卷积容易带来棋盘格伪影。更好的做法是先用双线性插值放大,再接普通卷积来细化,这样既能减少伪影,也能避免引入额外可学习参数带来的训练难度。
网络分支数量上,建议使用一个共享编码器和两个共享大部分权重的解码分支。严格来说两个解码分支不一定要完全独立,也可以让中间的很多层共享,只在最后一两层分开输出左视差和右视差。这样能进一步降低参数量,但如果你发现训练不收敛或者一致性不够好,再改成两个完全独立的解码分支也不迟。
输入图像在使用前要完成极线矫正,确保左右视图的对极线是水平对齐的。否则视差就不再是单纯的水平位移,左右一致性公式也会失效。所以在整个流程的第一步,需要先确认数据集的标定和矫正信息是否可靠。
3.2 损失函数和权重设置
损失函数由三个大项组成:外观重建项、左右一致性项、平滑项。我复现时常用的权重配置是:外观重建项权重 1.0,左右一致性项权重 1.0,平滑项权重 0.1。多尺度输出时,每个尺度的损失按该尺度的像素数加权,通常低尺度的权重小一些,避免低分辨率上采样后的误差主导总损失。
在训练早期,我建议先把左右一致性损失的权重调到 0 或者很小,等外观重建先收敛出一个粗视差,再逐步打开一致性项。为什么这么做:网络一开始输出的视差是乱的,左右视差完全不匹配,这时候强行让它们一致,相当于让一个还没学会走路的人去跑百米,损失函数会出现剧烈的来回拉扯。先学大致结构,再学一致性,训练更稳。
平滑项在训练后期可以适当调大一点,尤其是当图像里大片平坦区域出现明显噪声时。但平滑项也不是越大越好,过大会把物体边界也抹平,深度边缘会变得模糊,因此需要观察验证集上的边缘误差和整体误差做平衡。
3.3 可微采样、数据增强与常见误区
可微采样是自监督深度估计的底座。主流深度学习框架里都有 grid_sample 可以直接用。需要注意的是坐标映射:输入像素坐标通常要归一化到 [-1, 1],而视差是像素数量级,所以要先把视差除以图像宽度,再乘以 2,最后把归一化坐标减去这个偏移量,传入 grid_sample。
数据增强方面,一个容易踩坑的点是随机水平翻转。翻转会改变左右相机的对应关系,翻转后原来的左图变成了视觉上的“右图”,原来的右图变成了“左图”。因此如果对整对图像做水平翻转,必须同时交换左右图的输入通道顺序,并且更新视差方向相关的坐标公式。如果只翻转一张图而不交换,左右一致性损失会在早期瞬间爆炸,这类问题看训练曲线非常明显,图像对错位导致的重建 loss 很难降下去。
颜色增强也要小心。可以对左右图应用随机亮度、对比度、饱和度调整,但左右两幅图要使用同一个变换参数,不能各自随机。否则重建损失会错误地惩罚光照变化,模型会去学一个不存在的几何畸变。
3.4 显存与训练效率优化
双目自监督方法在显存消耗上比单目监督方法高不少,因为同等输入下要同时预测左右两个视差图,而且两个方向的图像重建和损失计算都必须在计算图中保留。这部分对显卡很不友好。我常用的优化手段依次是:
第一,输入尺寸不要一开始就上 512x1024 这种大图。先用 256x512 跑通流程,确认损失曲线正常,再加大分辨率。第二,使用混合精度训练,在很多现代 GPU 上能减少显存占用并提升速度,只需要注意 loss scale 的设置。第三,如果显存仍然不够,可以把解码器里的中间特征临时释放,用重计算的方式降低峰值显存,虽然会增加一点训练时间,但总比 OOM 好。第四,合理设置 batch size,如果 batch size 只能很小,可以把 batch normalization 换成 group normalization,避免小 batch 下统计量不稳定。
4. 实验结果与消融分析:到底赢在哪里
只看方法设计,可能还感受不到 Left-Right Consistency 的威力。这一节我结合论文中的实验设计,讲讲它到底在哪些地方带来了实打实的提升,以及这套方法的边界在哪里。
4.1 评估指标和无监督方案的水平
单目深度估计常用的指标包括绝对相对误差、均方根误差、对数误差,以及判断预测深度的相对误差小于某个阈值的像素占比。不同指标关注的重点不同。绝对相对误差对近距离误差更敏感,均方根误差对大误差非常敏感,阈值精度更偏稳定性。评估时一般只在有深度真值的像素区域计算,而不是整图平均。
就综合水平而言,这套基于左右一致性的方法明显优于最开始的纯图像重建自监督方案。在公开的双目自动驾驶数据上,它在多个误差指标上都有稳定改善。虽然和监督式深度估计还有差距,但考虑到它完全不需要深度标注,这个性价比已经相当可观。对于很多没法获得高质量深度真值的场景,这种自监督方案是比监督方案更现实的选择。
4.2 消融实验怎么做的
消融实验是理解论文贡献最重要的方式。最基础的对照组是只用外观重建损失加平滑项,即去掉左右一致性损失的那一版。这版已经能训练出可用的单目深度模型,但在很多细节区域,尤其是物体边缘和遮挡区域,预测的视差容易出现拉丝或空洞。
加上左右一致性损失之后,定性观察上最明显的变化是边界更干净了,左右视图的重建错位减少了。定量上,多个误差指标都在下降。这说明左右一致性损失不是一个可有可无的附加项。另一个消融是关闭 SSIM 项,只保留 L1 外观损失,这时的重建在光照变化大的区域会变差,深度图的纹理污染更严重。
把后处理那个左右一致性检查关掉,也就是推理时只输出左视差,不生成掩码,也不做左右平均,误差会略有上升。这个后处理是在训练完之后做的一次性操作,对推理耗时几乎无影响,可以说是性价比最高的提点方式。我做复现时还发现一个有意思的点:后处理对那些置信度较高的平坦区域改善最大,而遮挡区域因为被掩码保护,反而不太容易出现异常的深度跳变。
4.3 边界与遮挡:最直观的收益
单目深度估计在物体边缘一直是个难题,因为一个像素很可能混合了前景和背景的信息,网络很难判断它到底属于哪个平面。立体匹配中通过左右一致性检查可以剔除错误匹配,这个思想在深度学习的深度估计里依然有效。因为这个方法在训练时就让左右视差互相约束,所以它学到的几何特征本身就带有“可交叉验证”的性质。
在遮挡区域,左侧被右侧前景挡住的像素,理论上在右图里根本没有对应点。任何基于图像重建的损失都天然无法约束这些区域。但左右一致性损失在这里的作用并不是强行给出一个正确答案,而是通过掩码方式告诉模型“这里不可信”。到了推理阶段,这些不可信区域会被排除在左右平均之外,只保留左视差的结果。这样虽然不能把遮挡区域的深度恢复到真值水平,但至少不会出现一个明显更差的错误值污染整个平面。
4.4 局限性与适用边界
再好的方法也有适用边界。这套方法最核心的假设就是训练数据来自已知内外参且已经校正的双目相机,一旦相机的基线、焦距发生变化,模型需要重新训练或至少做适应。立体匹配里的最大视差范围也是一个硬约束,如果场景里有特别近的物体,视差很容易超出预设范围,导致预测被截断。自动驾驶场景里的相对较远物体视差很小,而室内近景的视差可能很大,一套参数很难同时吃透两种场景。
动态物体也是麻烦。训练时左右两幅图像拍摄于同一时刻,动态物体不会产生时间上的位移,但如果是单目视频序列扩展而来的数据,物体移动就会打破几何一致性假设。这个工作本身处理的是双目图像对,对动态物体的容忍度比基于视频的方法好一些,但也不能完全解决。此外,反射面、透明物体、强纹理重复区域等,都会让图像重建和左右一致性失去约束,模型只能靠数据先验“猜”。
5. 亲手复现时容易踩的坑
复现这类自监督方法,很多时候不是模型结构难搭,而是各种细节真的太多。我把亲自踩过的几个比较典型的坑,按从最致命到最隐蔽的顺序整理成一个速查表。你在跑通流程前,不妨先检查一下这几个地方。
5.1 视差方向混乱导致重建出错
这是最典型也最隐蔽的错误。左右视差的符号方向一旦写反,训练 loss 可能完全不下降,甚至反复震荡。一个快速检查方式是:训练只迭代几十步,然后拿输出的左视差图把左图 warp 到右图视角,直接把重建的“右图”可视化。如果重建结果和真实右图在主体结构上是对齐的,只是细节有噪声,说明方向是对的;如果物体左右位置错乱,那多半是 p - d_l(p) 和 p + d_l(p) 的问题。
还有一个检查办法:对同一场景,让左图上一个明显的物体点 p,看它的视差 d_l(p) 是正还是负。对于极线矫正后的双目图像,左图中物体到右图中同一物体的水平位移通常是负值,也就是向右偏移,所以 d_l(p) 一般应是负号还是正号取决于你定义的坐标方向。不同代码库的符号约定非常不统一,务必在代码里写清楚注释。
5.2 采样坐标和边界处理
grid_sample 本身很容易用错。它输入的是归一化坐标,范围 [-1, 1]。而视差单位是像素,需要先除以宽度再乘以 2 转换成归一化偏移。如果忘了除以宽度,坐标会跑到图像外,重建图上出现大片黑色或边缘噪声。另一个常见问题是 align_corners 设置不一致。训练和推理必须用同一个设置,否则坐标偏移会差半个像素。虽然半个像素误差对最终深度影响不算致命,但会影响左右一致性检查的 mask 边缘质量。
边界之外的像素怎么处理也要提前想好。当 p - d_l(p) 落在右图之外时,这个位置的像素不可见。通常的做法是在采样时把越界区域标记为无效,并在计算重建损失时忽略这些位置。如果不忽略,边界会出现一圈明显伪影,而且模型会为了强行拟合边界外不存在的信息把视差往奇怪方向推。
5.3 损失不收敛或视差全是零
我见过最头疼的问题是 loss 从第一步开始就不断下降,但等地一步训练完,把视差图可视化出来,整张图全是零或者接近无限大。排除数据问题后,首先检查视差输出的激活函数。如果直接把网络输出当视差,没有任何约束,数值可能瞬间爆炸。如果用了 sigmoid 但初始权重偏置不合适,网络输出接近 0,乘以最大视差后依然接近 0,那就相当于模型根本没有学习梯度。
解决办法是给最后一层卷积的权重做合适初始化,或者将最大视差在训练早期设得大一点,让初始预测不至于全部坍缩到 0。同时可以先把平滑项的权重调小。平滑项只惩罚变化,不惩罚绝对数值,当视差全为零时平滑项为零,模型可能只依赖重建项来恢复数值。此时如果重建项梯度太小,就会出现深度图几乎不变的情况。
5.4 SSIM 和图像预处理视角
SSIM 计算方式和默认参数也值得检查。大多数代码里的 SSIM 是基于高斯窗口计算均值、方差和协方差,窗口大小的选择会影响梯度传播。窗口太小时 SSIM 会退化成类似逐像素的度量,太大时边缘细节又容易被过度平滑。常用的窗口是 11x11 或 7x7,具体可以根据图像分辨率调整。
另一个容易被忽略的是输入图像范围和归一化。SSIM 通常期望输入范围是 0 到 1,且用两个图像窗口的相对亮度信息。如果输入图像先做了减均值除标准差预处理,再直接套 SSIM,结果会完全不对。正确做法是先把图像归一化到 [0,1],再做几何变换和采样,最后在计算 SSIM 时不额外减均值。这个细节是很多从分类任务迁移来的代码会莫名其妙崩掉的根源。
5.5 一致性掩码后处理的正确姿势
推理阶段的左右一致性后处理,正确步骤是:先用左视差图找到右图坐标,再从右视差图里采样一个值,比较这两个值是否小于 1 像素阈值;然后在可信区域取平均,在不可信区域保留左视差。这里容易踩的坑是把采样方向写反,导致最终结果反而比不处理后处理更差。
更隐蔽的坑是,正是因为后处理能带来指标提升,很多人会误以为它是模型训练的一部分,从而把一致性掩码加回到损失函数里。理论上不是不行,但要小心:在训练阶段把硬掩码放进损失,会切断梯度到不可信区域的传播,让模型学会直接忽略这些区域,反而不利于整体几何学习。原论文的做法是只在推理时使用这个掩码,训练时只是用一个软性的左右一致性损失对整个区域做温柔约束。这两个阶段的目标并不完全相同,用途不要混。
6. 一点个人体会
我在实际复现时最深的感受是,这看起来是一个“输出深度图”的模型,但真正核心的工程底盘其实是“可微几何变换”。你把采样方向、坐标偏移、一致性公式、掩码策略理顺了,模型自然就能在纯自监督的条件下收敛。反过来,任何一步坐标错位,最后的结果都可能很糟。
到后面我逐渐意识到,Left-Right Consistency 这个思想已经不只是那篇论文里的一个 loss 项,它成了我理解自监督单目几何估计的一条主线。后来再看到视频深度估计、双目深度估计、光流估计里类似的左右一致性、前后一致性约束,都能很快对上号。如果你正准备复现这个方向,我建议你也别急于调参,先把网络输出的左右视差、重建图像和一致性掩码全部可视化一遍,亲眼确认每一环节符合几何直觉,再谈训练技巧。这个习惯能帮你省下大量排查时间,也会让后续所有相关论文读起来都更轻松。