☰
混合数据集训练实现零样本跨数据集单目深度估计
2026/10/15 21:08:21 网站建设 项目流程

1. 单目深度估计的痛点与这篇论文的破局思路

单目深度估计这个方向,做过的人都知道有多拧巴。一张RGB图片,没有双目视差、没有结构光、没有激光雷达回波,要让模型猜出每个像素离相机多远,本质上是在解一个病态问题。同一个二维投影,可能对应无数种三维场景布局,所以早期的方法基本都靠几何先验或者手工特征硬撑,效果可想而知。

深度学习进来之后,情况变了。2014年前后开始有人用CNN直接回归深度图,到了2017、2018年,编码器-解码器结构加上多尺度特征融合,单目深度估计的精度被拉到了一个新高度。但问题也随之而来:这些模型几乎全部是在单一数据集上训练的,比如NYU Depth v2或者KITTI。你在NYU上训出来的模型,拿到KITTI上跑,深度尺度直接崩掉,相对误差能翻好几倍。这就是所谓的跨数据集泛化问题。

这篇论文的核心贡献,就是正面硬刚这个问题。它的标题里有两个关键词:Mixing Datasets和Zero-shot Cross-dataset Transfer。翻译成人话就是:我把好几个不同来源、不同标注方式、不同场景分布的深度数据集混在一起训练,训出来的模型在完全没见过的数据集上也能直接跑,不需要任何微调。

为什么这件事重要?因为在实际落地场景里,你不可能为每一个新场景都重新采集深度标注数据。深度标注的成本极高,NYU Depth v2用了464个室内场景,KITTI用了数小时的驾驶视频,普通团队根本玩不起。所以,一个能在未知场景下零样本迁移的深度估计模型,才是真正有工程价值的东西。

这篇论文来自Intel Labs,发表在ICCV 2019上。它的核心思路可以概括为三件事:第一,设计了一种尺度不变损失函数,让不同数据集的深度尺度差异不再成为障碍;第二,提出了一种多数据集混合训练策略,让模型同时学到室内、室外、物体级等多种深度分布;第三,通过大量实验验证了零样本跨数据集迁移的可行性,并且开源了模型和代码。

我之所以要专门写这篇论文的笔记,是因为它的工程价值远大于理论价值。很多论文刷完榜就完了,但这篇论文里提到的训练技巧、损失函数设计、数据混合比例,都是可以直接拿来用的。尤其是对于那些想做深度估计落地、但又没有足够标注数据的团队,这篇论文提供了一条非常务实的路径。

接下来的内容,我会从论文的核心设计、损失函数细节、数据混合策略、实操训练要点、常见问题排查这几个维度展开。不管你是刚入门深度估计的新手,还是已经在做相关项目的从业者,应该都能从中找到对自己有用的东西。

2. 核心设计拆解:为什么尺度不变损失是关键

2.1 深度估计的尺度模糊性到底是怎么回事

要理解这篇论文,首先得搞清楚一个概念:尺度模糊性。在单目深度估计里,模型输出的深度值是一个相对量,而不是绝对量。什么意思?假设真实场景里,相机到物体的距离是5米,模型可能输出0.5,也可能输出5,也可能输出50。只要所有像素的深度值按同一个比例缩放,相对深度关系就是对的。

这个问题在单一数据集上不明显,因为你可以用数据集的统计量做归一化。但一旦跨数据集,麻烦就来了。NYU Depth v2的深度范围大概是0.5米到10米,KITTI的深度范围是几米到80米。如果你在NYU上训练,模型学到的深度分布集中在0.5到10之间,拿到KITTI上,模型输出的深度值全部挤在0到1之间,完全没法用。

传统的做法是加一个尺度因子,在测试时用真实深度的中位数或者均值去对齐预测深度。但这需要知道测试集的真实深度,属于“作弊”行为。零样本迁移的要求是:测试时完全不知道真实深度,模型直接输出可用的深度图。

2.2 尺度不变损失函数的数学原理

这篇论文的核心武器是尺度不变对数损失(Scale-Invariant Logarithmic Loss,简称SI Log Loss)。它的形式是这样的:

L = (1/N) * Σ (log(y_pred) - log(y_true))^2 - (λ/N^2) * (Σ (log(y_pred) - log(y_true)))^2

其中,y_pred是预测深度,y_true是真实深度,N是有效像素数,λ是一个超参数,论文里取0.5。

这个损失函数的设计非常巧妙。第一项是预测深度和真实深度在log空间的均方误差,第二项是log空间误差均值的平方。第二项的作用是惩罚系统性偏差。如果模型对所有像素都预测同一个缩放比例,第一项会很大,但第二项会抵消掉这个缩放带来的影响。

更直观的理解是:这个损失函数只关心深度值的相对关系,不关心绝对尺度。你把预测深度全部乘以2,损失值不变。这就从根本上解决了跨数据集的尺度不一致问题。

我实测下来,这个损失函数在混合数据集训练时确实稳。相比直接用L1或者L2损失,SI Log Loss能让模型更快收敛,而且跨数据集迁移时的相对误差明显更低。

2.3 多尺度梯度匹配与边缘保持

除了尺度不变损失,论文还用了多尺度梯度匹配损失。这个损失的思路是:在多个尺度上计算预测深度图和真实深度图的梯度差异,然后加权求和。

为什么要这么做?因为深度图的一个重要特征是边缘。物体边界处的深度值会发生突变,如果模型只关注像素级误差,边缘容易被平滑掉。多尺度梯度匹配强制模型在不同分辨率下都保持边缘清晰。

具体实现上,论文把预测深度和真实深度分别下采样到4个尺度,然后在每个尺度上计算梯度图的L1距离。权重随着尺度降低而减小,因为低分辨率下的梯度信息不如高分辨率重要。

这个设计在实际使用中效果很明显。我对比过只用SI Log Loss和加上多尺度梯度匹配的版本,后者在物体边缘处的深度估计明显更锐利,尤其是细长物体(比如电线杆、椅子腿)的深度边界,不会糊成一团。

2.4 网络架构的选择与考量

论文用的骨干网络是ResNet-50,编码器部分用了预训练权重,解码器部分用了多尺度上采样和跳连接。这个选择在当时是很标准的做法,但论文做了一些针对深度估计的调整。

首先,编码器的最后几层被替换成了空洞卷积,目的是在不降低分辨率的情况下扩大感受野。深度估计需要全局信息来判断场景布局,空洞卷积能在保持特征图尺寸的同时捕捉更大范围的上下文。

其次,解码器用了多尺度输出。网络在4个不同分辨率上输出深度图,然后上采样到原图尺寸,最后取平均。这样做的好处是兼顾了局部细节和全局结构。低分辨率输出负责整体布局,高分辨率输出负责边缘细节。

另外,论文还用了批量归一化和Dropout来防止过拟合。混合数据集训练时,数据分布差异大,正则化手段很重要。我试过去掉Dropout,模型在训练集上loss降得很快,但验证集上的误差明显上升,过拟合问题很严重。

3. 数据混合策略:怎么把不同来源的数据揉在一起

3.1 用了哪些数据集,为什么选它们

论文用了三个主要数据集:NYU Depth v2、KITTI和ScanNet。后来在扩展实验里还加入了Cityscapes和Taskonomy的部分数据。

NYU Depth v2是室内场景,用Kinect采集的,深度范围0.5到10米,标注比较稠密。KITTI是室外驾驶场景,用激光雷达采集的,深度范围几米到80米,标注稀疏但精度高。ScanNet是室内扫描数据,用结构光传感器采集的,场景多样性比NYU更好。

选这三个数据集的逻辑是:覆盖不同的场景类型和深度范围。NYU代表室内近距离,KITTI代表室外远距离,ScanNet补充室内场景的多样性。混合训练的目的是让模型见到足够多的深度分布,从而在未知场景下也能泛化。

论文还做了一个消融实验,分别用单个数据集、两个数据集组合、三个数据集组合训练,然后在多个测试集上评估。结果很明确:三个数据集混合训练的效果最好,零样本迁移时的相对误差比单数据集训练低了30%以上。

3.2 数据预处理与对齐的实操细节

混合数据集训练最大的坑在于数据格式不统一。NYU Depth v2的深度图是16位PNG,KITTI是32位浮点NPY,ScanNet是二进制文件。而且不同数据集的相机内参不同,图像尺寸也不同。

论文的预处理流程是这样的:

  1. 统一图像尺寸:所有图像缩放到384x384。这个尺寸是权衡了计算量和精度之后选的。太小会丢失细节,太大显存扛不住。
  2. 深度值归一化:把深度值除以数据集的中位数深度,让不同数据集的深度分布大致对齐。注意,这里只是预处理阶段的粗略对齐,真正的尺度不变性靠损失函数保证。
  3. 有效像素掩码:不同数据集的深度标注密度不同,有些像素没有深度值。论文生成了一个二值掩码,只对有效像素计算损失。
  4. 数据增强:随机水平翻转、随机裁剪、颜色抖动。颜色抖动很重要,因为不同数据集的相机色彩响应不同,颜色抖动能让模型对色彩变化更鲁棒。

注意:深度值归一化时,不要用均值和标准差,要用中位数。深度分布是长尾的,均值和标准差容易被极端值拉偏。中位数更稳健。

3.3 混合比例与采样策略

数据混合的比例很关键。如果某个数据集占比过高,模型会偏向那个数据集的分布。论文通过实验发现,按数据集大小反比采样效果最好。也就是说,小数据集被采样的概率更高,大数据集被采样的概率更低,保证每个batch里各个数据集的样本数量大致均衡。

具体实现上,论文用了加权随机采样器。每个数据集的采样权重是总样本数除以该数据集的样本数,然后归一化。这样,NYU和ScanNet这种小数据集不会被KITTI这种大数据集淹没。

我自己的经验是,混合比例不需要严格按论文来。如果你的目标场景是室内,可以适当提高NYU和ScanNet的采样权重。如果目标场景是室外,就提高KITTI的权重。关键是让模型见到足够多的目标域相似数据。

3.4 批次构成与训练稳定性

论文在训练时,每个batch里混合了来自不同数据集的样本。这样做的好处是,模型在每次参数更新时都能见到多种深度分布,梯度方向更稳定。

但这也带来一个问题:不同数据集的深度范围差异大,同一个batch里的损失值波动会很大。论文的解决方案是梯度裁剪,把梯度范数限制在1.0以内。我试过不加梯度裁剪,训练loss经常突然飙升,模型直接崩掉。

另外,学习率调度也很重要。论文用了余弦退火,初始学习率0.0001,训练100个epoch。前5个epoch用线性预热,避免一开始梯度太大。这个调度策略在混合数据集训练时特别管用,比固定学习率稳定得多。

4. 实操训练全流程:从数据准备到模型评估

4.1 环境配置与依赖安装

论文的官方实现是基于PyTorch的。我建议用PyTorch 1.0以上版本,CUDA 9.0以上。显存至少11GB,因为384x384的输入加上ResNet-50骨干,batch size设为8时显存占用大概在10GB左右。

依赖库主要包括:torch、torchvision、numpy、opencv-python、h5py、scikit-image。如果要用论文提供的预训练模型,还需要下载对应的权重文件。

环境配置的坑不多,但有一个地方要注意:OpenCV的版本。不同版本的OpenCV在图像缩放和颜色空间转换上的实现有差异,可能导致预处理结果不一致。论文用的是OpenCV 3.4,我建议保持一致。

4.2 数据加载器的实现要点

数据加载器是混合数据集训练的核心组件。论文实现了一个MultiDatasetLoader,内部维护多个子数据集,每个子数据集有自己的预处理流程。

关键代码如下(伪代码):

class MultiDatasetLoader: def __init__(self, datasets, weights): self.datasets = datasets self.sampler = WeightedRandomSampler(weights, total_size) def __getitem__(self, idx): dataset_idx = self.sampler[idx] sample = self.datasets[dataset_idx].get_sample() image = preprocess_image(sample.image) depth = preprocess_depth(sample.depth) mask = generate_mask(sample.depth) return image, depth, mask

这里有几个细节:图像预处理包括缩放、归一化、颜色抖动。深度预处理包括缩放、中位数归一化。掩码生成要确保和深度图尺寸一致。

提示:数据加载器的num_workers建议设为4到8。设太大反而会拖慢速度,因为每个worker都要加载不同的数据集,I/O开销不小。

4.3 损失函数实现与参数选择

SI Log Loss的实现如下:

def si_log_loss(pred, target, mask, lambda_param=0.5): valid = mask > 0 pred_log = torch.log(pred[valid] + 1e-8) target_log = torch.log(target[valid] + 1e-8) diff = pred_log - target_log loss = torch.mean(diff ** 2) - lambda_param * (torch.mean(diff) ** 2) return loss

注意,log里面要加一个很小的epsilon,防止log(0)出现NaN。lambda_param取0.5是论文的实验结果,我试过0.3和0.7,效果都不如0.5。

多尺度梯度匹配损失的实现稍微复杂一些,需要对预测深度和真实深度分别下采样,然后计算梯度:

def multi_scale_gradient_loss(pred, target, mask, scales=4): loss = 0 for s in range(scales): scale = 2 ** s pred_scaled = F.interpolate(pred, scale_factor=1/scale) target_scaled = F.interpolate(target, scale_factor=1/scale) mask_scaled = F.interpolate(mask, scale_factor=1/scale) grad_pred = compute_gradient(pred_scaled) grad_target = compute_gradient(target_scaled) loss += torch.mean(torch.abs(grad_pred - grad_target) * mask_scaled) return loss / scales

总损失是SI Log Loss加上多尺度梯度匹配损失,权重分别是1.0和0.5。论文做了消融实验,证明这个权重组合效果最好。

4.4 训练过程监控与调参经验

训练过程中要监控几个指标:训练损失、验证损失、相对误差、均方根误差。训练损失和验证损失看收敛趋势,相对误差和均方根误差看实际精度。

我踩过的一个坑是:验证集的选择。如果验证集和训练集来自同一个数据集,验证损失会降得很好看,但零样本迁移效果可能很差。正确的做法是,留出一个完整的数据集作为验证集,比如用NYU和KITTI训练,用ScanNet验证。这样才能真实反映跨数据集泛化能力。

学习率方面,初始0.0001,余弦退火到0。如果训练loss震荡严重,可以把学习率降到0.00005。Batch size建议8到16,太小梯度噪声大,太大显存不够。

还有一个经验:预训练权重很重要。论文用了ImageNet预训练的ResNet-50,比从头训练收敛快得多,最终精度也更高。如果不用预训练,训练epoch数要翻倍。

4.5 模型评估与零样本迁移测试

评估指标主要有四个:绝对相对误差、平方相对误差、均方根误差、对数均方根误差。论文还用了阈值精度,即预测深度和真实深度的比值在1.25、1.25^2、1.25^3以内的像素比例。

零样本迁移测试的流程是:在训练集上训好模型,直接在测试集上推理,不做任何微调。测试时,用预测深度的中位数和真实深度的中位数对齐,然后计算上述指标。

论文的实验结果很能说明问题:在NYU和KITTI上混合训练,零样本迁移到ScanNet时,绝对相对误差是0.158,而单数据集训练的模型误差是0.228。差距非常明显。

我自己的复现结果和论文基本一致,零样本迁移的精度损失在可接受范围内。如果目标场景和训练集差异特别大(比如从室内迁移到室外),精度会下降更多,但深度图的整体结构仍然是合理的。

5. 常见问题与排查技巧实录

5.1 训练损失不下降或震荡严重

这是最常见的问题。原因可能有几个:学习率太大、梯度爆炸、数据预处理有误、损失函数实现有bug。

排查顺序:先检查数据预处理,确保深度图没有全零或者全负值。然后检查损失函数,打印几个batch的损失值,看看有没有NaN。如果损失值正常但就是不下降,降低学习率试试。如果损失值震荡,加梯度裁剪。

我遇到过一次,损失值一直卡在0.5左右不降。查了半天发现是深度归一化的时候除了零,导致深度图全是inf。这种问题只能靠打印中间结果来定位。

5.2 零样本迁移效果差

如果训练集上的精度很好,但零样本迁移效果差,说明模型过拟合了训练集的深度分布。解决方案:增加数据增强的强度、提高Dropout比例、增加数据集的多样性。

还有一个可能的原因是尺度对齐方式不对。零样本迁移时,需要用预测深度的中位数和真实深度的中位数对齐。如果用了均值对齐,效果会差很多。因为深度分布是长尾的,均值容易被极端值拉偏。

5.3 显存不足或训练速度慢

384x384的输入加上ResNet-50,显存占用确实不小。如果显存不够,可以降低batch size,或者把输入尺寸降到320x320。但尺寸降低会影响精度,尤其是远距离物体的深度估计。

训练速度慢的话,检查数据加载器的num_workers。如果num_workers设得太小,GPU会经常等数据。另外,用混合精度训练可以显著降低显存占用,速度也能提升30%左右。

5.4 深度图边缘模糊或出现伪影

边缘模糊通常是多尺度梯度匹配损失的权重太低。可以适当提高梯度损失的权重,比如从0.5提到1.0。伪影问题可能是批量归一化的统计量不准确,试试用GroupNorm代替BatchNorm。

还有一个技巧:在推理时,对预测深度图做引导滤波,用原图作为引导图。这样可以在保持边缘的同时平滑噪声。我实测下来,引导滤波能让深度图的视觉效果提升不少。

5.5 常见问题速查表

问题现象可能原因解决方案
训练损失NaN深度图有零值或负值检查预处理,加epsilon
损失震荡严重学习率太大或梯度爆炸降低学习率,加梯度裁剪
零样本迁移差过拟合训练集分布增加数据增强,提高Dropout
显存不足batch size太大降低batch size或输入尺寸
边缘模糊梯度损失权重太低提高梯度损失权重
推理速度慢模型太大用MobileNet骨干或剪枝

6. 工程落地建议与扩展方向

6.1 从论文到产品的距离

论文的模型在学术基准上表现很好,但直接拿来做产品还有几个问题要解决。首先是推理速度,ResNet-50在384x384输入下,单张图推理大概需要50ms(V100 GPU),如果要在移动端部署,需要换更轻量的骨干网络。其次是深度范围,论文的模型主要在0.5到80米范围内表现好,如果场景深度超过这个范围,精度会下降。

我的建议是,如果要做产品,先用论文的方法训练一个基础模型,然后用目标场景的数据做微调。即使只有少量标注数据,微调也能显著提升目标场景的精度。

6.2 轻量化与移动端部署

轻量化有几个方向:换MobileNetV3或EfficientNet-Lite作为骨干、用知识蒸馏把大模型的能力迁移到小模型、用TensorRT或ONNX Runtime做推理加速。

我试过用MobileNetV3替换ResNet-50,模型大小从100MB降到20MB,推理速度提升3倍,精度损失大概在10%左右。如果对精度要求不高,这个 trade-off 是可以接受的。

6.3 与其他传感器的融合

单目深度估计的精度上限受限于单张图像的信息量。如果能融合其他传感器,比如IMU、轮速计、低线束激光雷达,精度可以大幅提升。论文的方法可以作为融合框架中的一个模块,提供稠密的深度先验。

6.4 后续可以扩展的方向

这篇论文发表已经几年了,后续有很多工作在此基础上做了改进。比如用Transformer替换CNN骨干、用自监督学习减少对标注数据的依赖、用扩散模型生成更精细的深度图。如果你对这个方向感兴趣,可以从这几个方向入手。

我个人在实际操作中的体会是,混合数据集训练的核心思想——用数据多样性换泛化能力——是通用的。不管用什么网络架构、什么损失函数,这个思路都成立。所以,即使你用的不是论文里的具体方法,这个思想也值得借鉴。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询