资讯详情

资讯详情

MonoDepth2核心机制详解:自监督单目深度估计与复现实践

1. 引言为什么单目深度估计值得反复读看到“MonoDepth2”这个名字做过深度估计方向的人应该都会心头一动。这篇论文全称是Digging Into Self-Supervised Monocular Depth Estimation发表在CVPR 2019上是单目深度估计领域绕不开的经典工作。我前前后后把它读了好几遍每次读都有新的收获尤其是当你自己动手复现过、在真实数据上跑过之后才能真正理解作者在设计每一个模块时的用意。这篇博客就把我读完、复现、调参过程中的理解和踩坑经验完整记录下来希望能帮到正在读论文、准备复现、或者想在这个方向进一步探索的同行。先说清楚这篇文章是干什么的MonoDepth2提出了一套自监督的单目深度估计方法核心思想是只用单目视频序列训练深度估计网络不需要任何真值深度标注。它解决的痛点是传统深度估计依赖昂贵的LiDAR或深度传感器数据而作者发现我们可以用视频前后帧之间的几何关系构造自监督信号让网络“自己教自己”学会感知深度。它适合谁看如果你刚入门深度估计方向这篇论文可以作为第一篇精读的经典如果你已经在做相关项目、准备在自动驾驶、机器人避障等领域落地深度估计模型它也值得作为baseline来对比如果你只是对多任务学习、特征融合、损失函数设计感兴趣这篇论文同样能给你很多启发。2. MonoDepth2要解决的问题与总体设计思路2.1 单目深度估计的难点在哪里先聊点背景。单目深度估计的目标是给一张RGB图像输出每个像素对应的深度值。听起来没什么稀奇但你仔细想想人类靠单只眼睛其实很难精确判断远近这是病态问题——同一个2D图像可以对应无数种3D结构。机器更是如此最直接的做法是拿大量“RGB图深度真值图”作为监督数据训练一个CNN做回归这条路本身没有大问题但数据采集是个大坑。高精度深度传感器非常昂贵而且激光雷达点云是稀疏的想要稠密真值深度必须做插值插出来的结果还不够可靠。自动驾驶公司可以烧钱采集大规模数据普通实验室、个人开发者很难获得这种资源。于是大家开始琢磨一个更聪明的问题能不能不依赖深度真值用别的监督信号来训练深度网络MonoDepth2走的就是这条路它完全靠单目视频序列来训练输入一张连续视频帧序列利用帧间几何关系构造自监督约束把深度估计变成像“图像重建”一样的任务通过最小化重构误差不断优化。2.2 基线方法与MonoDepth2的创新点其实用单目视频做自监督深度估计不是MonoDepth2首创在他之前有Monodepth、SfMLearner等一批工作。Monodepth用左右视差图做监督SfMLearner则用单目连续帧训练深度和姿态。MonoDepth2站在前人的肩膀上主要做了两个高价值的改进一是提出了最小化重投影损失minimum reprojection loss二是引入了多尺度估计与全分辨率输出的策略。理解这个设计的关键在于以前的方法往往只是简单地对变换后的帧和原帧计算重建误差忽略了遮挡和动态物体这两大难题。一辆车从你旁边经过它的运动并不是由相机移动引起的如果强行用刚性几何来约束网络就会在这些区域产生严重的深度误差。MonoDepth2的做法很直白——直接去预测多个目标帧的变换结果计算加权后的最小photometric误差让网络自己学会忽略那些“解释不了”的区域相当于给了网络一个自动选择最合适监督信号的机制。2.3 为什么“自监督”能学出真实深度这里有个值得细品的点纯自监督信号没有提供绝对尺度网络为什么还能学到一致的深度关系我认为有两个关键前提。第一相机位姿变化产生的视差和物体本身离相机的距离存在确定性几何关系这是一个物理约束相当于给网络提供了天然标签。第二网络要完成多视角图像重建这个任务就必须真正理解场景的几何结构否则重建不出来。一个很直观的例子你从左边看一个杯子从右边看它杯子的边缘会有细微的位移差位移大小和距离直接相关网络只要学会了这个规律深度也就学出来了。MonoDepth2的核心是“以重建为监督以几何为桥梁”整个方法论不依赖任何深度标注只依赖连续的图像序列这让它的应用扩展性大很多几乎任何车载摄像头或者无人机镜头拍下的视频都可以用来训练。3. 核心机制拆解最小化重投影损失与多尺度架构3.1 图像重建的数学逻辑要理解MonoDepth2必须先理解它计算损失时做了什么。它的基本流程是输入目标帧 It让深度网络预测深度图 Dt输入源帧 Is通常取相邻的前一帧/后一帧或者二者都取让姿态网络预测相对位姿变换 Tt→s然后利用预测的深度、姿态和相机内参把源帧图像反向映射到目标帧坐标系得到一个“重建出来的目标帧” I^s。理论上如果深度和姿态估计都足够准确重建出来的 I^s 应该和 It 几乎一样。更直观地说这有点像一个虚拟相机照片合成过程你站在原位置拍下一张照片然后假设世界是刚性的根据估计的深度和姿态把相邻帧中的像素投到当前帧里看看能不能“拼”回当前的画面。差得越多说明深度和姿态估计越不准损失就越大。这就是核心思想——用可微的图像重建来驱动网络自己学习。3.2 minimum reprojection loss到底解决了什么问题MonoDepth2最大的创新在于损失函数的设计特别是对每一个像素不再是单一地比较某一帧的重建误差而是综合多个候选源帧进行选择。具体来说作者不只用了前一帧或后一帧而是同时把前、后帧都作为候选源帧为每个像素分别计算重建误差然后取最小值作为最终损失。论文里给出的公式形如Lp min_s pe(It, I^s)其中pe是标准的光度误差s遍历所有候选源帧。这个min操作初看很简单实际上作用深远。遮挡场景中某个像素可能只在某一个源帧中可见那么其他帧的重建误差会很大而最小值的操作让网络只关注那个可见帧的信号自动屏蔽了遮挡区域带来的噪声。我还记得第一次在KITTI数据集上对比加min与不加min的效果depth误差在遮挡边缘区域降低了非常多有些区域甚至直接“不见”了这就是让网络“选择性失明”带来的好处。另外在实现时注意读作者开源的代码他们实际上用的不是严格意义的min而是配合了SSIM和L1损失的一个加权组合后面会详细展开。3.3 多尺度估计从粗糙到精细第二个核心设计是多尺度深度图估计。MonoDepth2解码器输出三个尺度的深度图分辨率逐级放大最低尺度大约是输入的1/8中间是1/4最高尺度是1/2。作者并没有像很多工作那样直接对深度图进行上采样到全分辨率来计算损失而是在每个尺度上都计算一遍photometric loss。这个操作迫使网络在低分辨率下就必须学习正确的几何结构解决了自监督训练容易陷入局部最优、收敛缓慢的问题。实践中你会发现仅仅在最高分辨率处算损失网络会更倾向于把深度值平均化或者在图像纹理丰富的区域过度拟合因为低分辨率的误差信号更容易被高分辨率细节覆盖。多尺度损失相当于给了网络一个由粗到细的梯度引导学得更稳。另外每个尺度的深度图上采样回原尺寸时作者提示可以使用双线性插值配合特定的边距处理否则容易在图像边界产生空洞这个细节在复现时非常关键。3.4 编码器-解码器的细节设计网络主体是一个典型的编码器-解码器架构。编码器使用ResNet-18输入是三帧图像目标帧加上上下文帧或者两帧图像但作者设计上只让编码器处理单帧姿态定义由单独的姿态网络负责。解码器的设计并没有特别花哨就是逐级上采样加特征融合使用skip connection把编码器的高分辨率特征连接到对应层级的解码器中每个层级输出一个深度图。我复现时特别注意到了一个小细节作者在解码器中使用了几层带有5x5核的卷积原因是5x5卷积的感受野更大在低分辨率深度图上更有利于捕捉空间上下文信息比堆多个3x3更高效。有一些后来的工作习惯替换成残差模块或者ASPP但baseline实测下来原始设计在自监督深度估计中已经很稳。4. 损失函数与训练技巧的关键细节剖析4.1 光度误差的计算不止是L1MonoDepth2中的photometric loss不是一个单纯的L1误差。作者采用了SSIM和L1相结合的复合损失公式可以写为pe α/2 * (1 - SSIM(It, I^s)) β * |It - I^s|其中α通常取0.85β取0.15。这么设计的动机很朴素L1损失对光照变化和颜色偏移敏感SSIM则更关注局部结构的一致性两者结合可以让网络既保持颜色精度又能保持边缘结构。实际训练时SSIM在纹理丰富的区域给的梯度信号更加平滑配合上L1的强约束网络学出来的深度图边缘更锐利。需要注意实现中对图像做了逐像素的min操作之后才计算均值而不是先算均值再取min。这一点操作顺序搞反会导致梯度传播路径完全不同损失曲线会变得很不稳定我一开始曾经写反过出来的深度图几乎全是模糊一片排查了半天才定位到这个问题。4.2 边缘感知的平滑损失仅仅有photometric loss还不够因为在低纹理区域比如天空、白墙重建误差天然很小网络会倾向于把这些区域预测成任意深度。加入平滑损失来正则化而且不能是全局平滑必须是边缘感知的在图像颜色变化剧烈的地方放宽平滑约束在平坦区域强制平滑。具体实现中平滑损失会对深度图求梯度再乘以一个由输入图像梯度构造的衰减系数公式近似为Ls |∂x d*| * exp(-|∂x I|) |∂y d*| * exp(-|∂y I|)其中d* 是对数深度取对数是因为深度值范围跨度大直接回归绝对深度会让远近物体的权重失衡对数空间下误差的相对意义更合理这也是论文中的一个重要细节。实测中平滑损失的权重设为0.1-0.3之间效果都不错太小了深度图容易出现小噪点太大了会过度平滑把物体的边缘直接糊掉。如果训练时发现深度图“糊成一团、边界全失”大概率是平滑权重设太高了。4.3 深度一致性检查与遮挡处理MonoDepth2虽然用min操作处理了大量遮挡问题但作者还使用了一种mask机制来抑制不可靠的像素信号。具体做法是计算重建损失时比较目标帧到源帧再返回到目标帧的损失或者利用姿态一致性做检查生成一个二值化的掩码只保留那些在源帧中也可见、且深度和姿态估计一致的像素。代码里这一步称为auto-masking。这个mask让我想到一个有意思的类比两个人一起散步一个人负责带路另一个人负责看路。带路的人告诉你“那边能走”但那边其实有个坑看路的人发现不对就把那一步的指令挡掉。mask就是那个“看路人”它会根据几何一致性主动挡住噪声信号让网络主干不被带偏。这个机制在处理动态物体如行人、车辆时有奇效极大地提升了深度估计在动态场景下的鲁棒性。4.4 数据增强与输入预处理这部分论文着墨不多但实操时必须仔细。训练MonoDepth2时常用的是KITTI数据集其原始图像尺寸大约是1242x375作者在读取图像后先做中心裁剪再去掉底部的相机区域。强烈建议按照作者开源代码中的预处理方式来不要自己随意改尺寸因为深度和姿态网络的输入尺寸会影响张量形状一旦尺寸变了photometric loss的边缘像素计算和mask生成逻辑都需要调很麻烦。数据增强上作者只做了随机的水平翻转和亮度抖动没有用复杂的颜色扰动。我试过加入随机尺度缩放和随机裁剪收敛速度没有明显提升反而引入了一些尺度不一致的问题后来老老实实改回默认设置。不过在新场景数据集上训练时适当增加亮度和对比度的抖动范围确实可以提升泛化性。5. 训练实操从数据准备到推理的完整流程5.1 数据集准备与目录结构复现MonoDepth2的第一步是准备数据。以KITTI为例官方原始数据已经包含了raw、odometry、depth等子集。作者网站上提供了整理好的数据文件列表我们只需要按照列表下载对应的raw数据然后通过脚本生成训练和验证的文件列表即可。目录结构上代码会读取一个包含图片绝对路径的文本文件每一行是三列目标帧路径、前一帧路径、后一帧路径。这些路径对应的图像大小必须是预处理后的尺寸。我建议把数据集目录结构直接按照作者Github上的说明设置好以免后续改代码。下载数据时注意一个坑部分raw数据的日期目录中存在校准文件缺失的情况运行数据准备脚本时可能会报错选择一个完整序列或者跳过这些无效文件就行。5.2 训练超参数与学习率策略默认训练配置里面单卡batch size为12输入分辨率640x192初始学习率0.0001训练20个epoch后在最后5个epoch把学习率降到0.00001。硬件方面一张主流显卡就能跑但耗时非常长。一个epoch在KITTI训练集上大概要跑30-40分钟全部训练完成可能要十几个小时甚至一天多。强烈建议用梯度累积来增大等效batch size我试过等效batch 24效果比12稳定不少。学习率策略上作者采用的是阶梯式衰减很多人在复现时会想换成余弦退火但实测下来阶梯式衰减在这个自监督任务上更稳可能是因为深度估计任务需要长期稳定优化后面几个epoch的学习率调整非常关键。如果loss曲线后半段出现震荡先把学习率调低不要急着加正则化。5.3 测试时后处理空洞填充与直方图均衡化论文最后有一个容易被忽视的trick空洞填充空洞填充。深度图中由于遮挡和低纹理区域会出现一些无效像素或者深度空洞作者提出了一种基于深度图本身的合法像素填充方法。做法是先检测出无效区域然后找到这些区域边界上的最小有效深度值用这些最小值向外扩张填充。这种方法不需要额外训练推理时执行一次就够效果非常明显深度图看起来“完整”了许多。另外一个后处理是直方图均衡化histogram equalization这是针对KITTI深度图可视化做的。深度图的长尾分布导致线性拉伸后画面过于黑暗用直方图均衡化可以让深度图人眼看起来更清晰。严格说这是可视化手段不影响定量指标但如果你要展示结果或者写报告这个小技巧能帮你省很多事。5.4 单目相对深度转绝对尺度自监督单目深度估计输出的是相对深度没有真实尺度。如果你的实际应用需要绝对深度有两个思路一是用一段视频和已知的相机轨迹进行在线尺度恢复二是用最小二乘拟合即利用若干已知的绝对深度点例如检测到的地面平面、毫米波雷达的稀疏点来估计一个全局缩放因子。这个全局因子在相机内参固定的条件下通常是一个常数算出后直接乘到深度图上即可。实操里我通常的做法是先跑一次模型得到相对深度图再拿实际场景中的一个已知距离点比如激光测距仪量一下某个物体距离作为锚点把这个点的预测深度调整到真实距离整个深度图乘以这个缩放系数。这个方法在室内短距离场景精度还不错但长距离20米以上误差会累积要谨慎使用。6. 常见问题与排查技巧实录6.1 训练时loss降不下去怎么办训练初期loss不降很多人会开始怀疑模型写错了。其实最大的可能原因是数据路径配错或者数据预处理尺寸和模型输入不一致。先检查一下你的图像在进入网络前是否被resize到了640x192并且通道顺序是RGB。另外如果单卡batch size太小很容易梯度爆炸loss曲线会出现很多尖峰建议开始训练时就使用梯度累积或者降低学习率一个数量级再试。训练过程中loss出现周期性震荡大概率是学习率过高特别是在第三个epoch之后。MonoDepth2的训练对学习率很敏感低于0.0001基本不收敛高于0.0004会导致震荡。我一般先设0.00015观察到loss下降变缓时再手动降到0.00005。6.2 深度图出现条状伪影和横纹这个问题在自监督深度估计中非常常见尤其是在图像底部或者车辆引擎盖区域。通常是两方面的原因一是平滑损失权重不够网络在低纹理区域“放飞自我”二是输入图像中存在固定的结构比如车辆挡风玻璃边框、仪表盘反光导致损失在那些位置出现系统性偏差。解决方式可以把输入图像底部区域直接屏蔽训练或者增大平滑损失权重。另外如果使用的是KITTI raw数据一些序列中存在相机输出格式不一致的现象原始图像底部会有黑边或者阴影这些会影响训练。我建议在预处理时直接做一个中心裁剪只保留中间有效区域用一个固定的mask把无效区域排除在损失计算之外。6.3 推理速度慢怎么办MonoDepth2网络本身并不重ResNet-18编码器加轻量解码器在主流GPU上能跑到实时。但如果你部署到嵌入式设备或者CPU上就需要做一些裁剪。最简单的做法是把输入分辨率降到320x192深度质量会小幅下降但速度提升明显。进一步可以用TensorRT做INT8量化实测在Jetson平台上可以把推理时间从70ms降到30ms左右精度损失大约在2%以内。如果追求更极致的速度可以把最后的深度输出层改为单通道的线性层在上采样时用最近邻插值替代双线性插值这个微调很多人忽略但它可以减少一部分内存带宽开销。6.4 模型在不同传感器上的迁移问题MonoDepth2虽然是针对车载场景提出的但它的框架完全适用于无人机、室内机器人等场景。不过迁移时要注意相机内参必须正确输入否则网络会完全学歪。相机内参在训练时是通过配置文件传入的推理时要用相应相机的内参重新标定一次。而且作者在训练时用的是一个固定的、KITTI平均后的内参如果你换相机最好自己写一个内参归一化的预处理模块不能直接拿默认参数用。另外不同相机的光学畸变尤其是鱼眼镜头会严重影响自监督重建的前提假设建议先用标定工具做畸变校正再输入网络。我曾把直接把鱼眼图喂进去深度图出现了严重的桶形失真校正后才恢复正常。7. 写在最后的一些体会MonoDepth2这篇论文最大的价值不只是提出了一个效果好的模型而是把“自监督单目深度估计”这个方向的核心方法论讲得非常清楚——用重建作为代理任务让深度和姿态在一个可微的几何框架内联合学习。反复读它之后你会发现很多后来的工作比如各种改进的深度估计、场景流估计、多任务学习方法都是在这个框架上添砖加瓦。我个人的实操心得是复现一篇论文不要只看它的最终指标要重点理解它每一步设计到底解决了什么实际问题。比如minimum reprojection loss解决的遮挡问题、多尺度损失解决收敛问题、auto-masking解决动态物体干扰问题这些才是这篇论文真正的财富。跑通模型只是一个开始能在项目里真正用起来、迁移到自己的数据上才算真正吃透了它。如果你正打算在这个方向做实验或者开发应用建议先完整复现一次MonoDepth2的KITTI训练流程把每一个损失函数的实现代码逐行读懂再动手去改进。这个过程会帮你避开很多初学者容易踩的坑也会让你对深度估计的几何原理有更扎实的理解。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →