4. 单目深度估计

单目深度估计,说白了就是让计算机只用一张照片,就能判断出场景里每个物体离我们有多远。听起来挺玄乎对吧?人眼天生就有这个能力,但让机器做到,可没那么简单。

我刚开始接触这个方向时,也觉得不可思议——就一张2D图片,怎么能还原出3D信息?后来做多了才发现,这里面其实有很多视觉线索可以利用,比如透视、遮挡、纹理梯度、物体相对大小等等。但问题是,这些线索在单张图片里往往是模糊的、多义的。

4.1 单目深度估计的核心挑战

为什么单目深度估计比双目难这么多?我列几个关键点,你感受一下:

挑战 具体表现 我的经验
尺度模糊 同一张图,可能是近处小物体,也可能是远处大物体 我曾经在室内场景上训练好的模型,拿到室外直接崩了
深度歧义 不同深度组合可能投影出完全相同的2D图像 比如一个杯子放在桌上,你很难判断它离桌沿到底多远
纹理缺失 白墙、天空等无纹理区域,深度信息几乎为零 我试过在纯色背景上做推理,结果深度图像一锅粥
光照变化 阴影、高光会严重干扰深度判断 同一个场景,白天和晚上拍,深度图差异很大
数据标注困难 逐像素深度真值获取成本极高 用激光雷达标数据,一台设备几十万,还只能在室内用
避坑提醒: 我曾经在项目里直接用网上公开的深度数据集训练模型,结果在真实场景里完全不能用。后来才发现,那些数据集大多是室内场景,而且深度范围只有0.5-10米。你想想看,如果拿它去估计户外街景,肯定翻车。

4.2 基于CNN的单目深度估计方法

深度学习兴起后,单目深度估计才有了质的飞跃。核心思路其实很直接:用CNN从图像中提取特征,然后回归出每个像素的深度值。

我按时间线梳理一下几个里程碑式的方法:

  1. Eigen et al. (2014)——开山之作。用了两个尺度的CNN,一个做全局粗估计,一个做局部细调。我记得当时看到结果时挺震撼的,虽然分辨率很低,但至少能看出物体远近关系了。
  2. Laina et al. (2016)——引入残差网络和上采样模块。他们把深度估计当作一个有序回归问题,而不是简单的回归。我个人习惯用这个思路做baseline,效果稳定。
  3. Fu et al. (2018)——把连续深度离散化成多个区间,用分类的思路做回归。这个想法很巧妙,我后来在好几个项目里都借鉴了这种策略。
  4. Ranftl et al. (2020)——MiDaS模型登场。这个我下面单独讲,因为它确实改变了游戏规则。

关键洞察: 单目深度估计本质上是一个ill-posed problem(不适定问题)。CNN能做的,其实是学习大量数据中的统计先验——比如"天空通常比地面远"、"人脸通常比背景近"。说白了,模型是在"猜",但猜得越来越准。

4.3 MiDaS模型深度解析

MiDaS(Mixed Depth and Stereo)是我目前最常用的单目深度估计模型。为什么它这么强?我总结了几点:

4.3.1 核心创新点

  • 多数据集混合训练:MiDaS同时用了10个不同的深度数据集,包括室内、室外、立体匹配、合成数据等。每个数据集的深度尺度都不一样,它通过一个尺度不变损失函数来统一训练。
  • 尺度不变损失:这是MiDaS的精髓。它不要求模型预测的深度值与真值一致,只要求深度之间的相对关系正确。说白了,你预测的深度值乘以一个常数后能和真值对上就行。
  • 多分辨率推理:输入图像可以任意尺寸,模型会自动调整。我试过从320×240到1920×1080,效果都还不错。
我的使用习惯: 在实际项目中,我通常用MiDaS的small版本(1.5M参数)做实时推理,用large版本(105M参数)做离线处理。small版本速度能到30fps以上,精度损失大概在10%左右,性价比很高。

4.3.2 网络架构

MiDaS的骨干网络用的是Vision Transformer(ViT)或Swin Transformer,而不是传统CNN。为什么?因为Transformer的全局注意力机制能更好地捕捉场景中的长距离依赖关系——比如判断一个物体是"在远处"还是"被遮挡",需要看整个画面的上下文。

下面这张图展示了MiDaS的整体流程:

MiDaS 单目深度估计流程 输入图像 ViT / Swin Transformer骨干 特征金字塔 多尺度融合 深度图 尺度不变损失函数 L = (1/N) Σ (d_i - d_j - (ĝ_i - ĝ_j))² 10个深度数据集 室内/室外/合成/立体 💡 关键设计理念 • 多数据集混合训练解决了数据量不足和尺度不一致的问题 • 尺度不变损失让模型专注于学习深度相对关系,而不是数值

4.3.3 代码实战:使用MiDaS进行深度估计

下面是我在实际项目中常用的MiDaS调用方式。注意,这里用的是PyTorch Hub直接加载预训练模型,非常方便:

import torch
import cv2
import numpy as np

# 加载MiDaS模型(我习惯用large版本,精度最高)
model_type = "DPT_Large"  # 可选: DPT_Large, DPT_Hybrid, MiDaS_small
midas = torch.hub.load("intel-isl/MiDaS", model_type)

# 设备选择
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
midas.to(device)
midas.eval()

# 加载图像
img = cv2.imread("portrait.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 预处理
transform = torch.hub.load("intel-isl/MiDaS", "transforms")
if model_type in ["DPT_Large", "DPT_Hybrid"]:
    transform = transform.dpt_transform
else:
    transform = transform.small_transform

input_batch = transform(img).to(device)

# 推理
with torch.no_grad():
    prediction = midas(input_batch)
    prediction = torch.nn.functional.interpolate(
        prediction.unsqueeze(1),
        size=img.shape[:2],
        mode="bicubic",
        align_corners=False,
    ).squeeze()

# 归一化到0-255显示
depth_map = prediction.cpu().numpy()
depth_map = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min())
depth_map = (depth_map * 255).astype(np.uint8)

cv2.imwrite("depth_output.png", depth_map)
print("深度图已保存,尺寸:", depth_map.shape)
经验分享: 我第一次用MiDaS处理人像照片时,发现头发丝和背景的边界处理得不够好。后来我加了一个后处理步骤——用引导滤波(guided filter)对深度图做边缘保持平滑,效果提升很明显。代码就几行,但能显著改善散景效果。

4.4 MiDaS在人像模式中的应用

回到我们的课程主题——人像模式。MiDaS输出的深度图可以直接用来做散景渲染吗?嗯,这里要注意几个问题:

应用场景 MiDaS表现 我的处理建议
人像前景分割 人物边缘较清晰,但头发细节不够 结合语义分割模型做mask refinement
渐进式散景 深度值连续,适合做渐变模糊 直接用深度值控制模糊半径,效果自然
背景替换 深度边界不够锐利 用Canny边缘检测辅助做硬分割
实时处理 large版本太慢,small版本精度下降 用small版本+轻量后处理,平衡速度与质量
注意: 我曾经在一个项目里直接用MiDaS的原始深度图做散景,结果背景里的树枝出现了严重的"深度泄漏"——树枝间的空隙被错误地判断为近景。后来我加了一个中值滤波,才把这种噪声压下去。所以,后处理不是可有可无的,而是必须的。

4.5 小结与思考

单目深度估计这条路,我从2016年跟到现在,看着它从学术玩具变成工业可用工具。MiDaS的出现是一个分水岭——它证明了只要数据够多、模型够大、损失函数设计得巧妙,单目深度估计完全可以达到实用水平。

但说实话,它还不是万能的。极端光照、透明物体、镜面反射这些场景,MiDaS照样会翻车。我现在的做法是:把MiDaS当作一个强大的基础模型,然后针对具体场景做微调或后处理。这样既省去了从头训练的麻烦,又能保证落地效果。