4. 单目深度估计
单目深度估计,说白了就是让计算机只用一张照片,就能判断出场景里每个物体离我们有多远。听起来挺玄乎对吧?人眼天生就有这个能力,但让机器做到,可没那么简单。
我刚开始接触这个方向时,也觉得不可思议——就一张2D图片,怎么能还原出3D信息?后来做多了才发现,这里面其实有很多视觉线索可以利用,比如透视、遮挡、纹理梯度、物体相对大小等等。但问题是,这些线索在单张图片里往往是模糊的、多义的。
4.1 单目深度估计的核心挑战
为什么单目深度估计比双目难这么多?我列几个关键点,你感受一下:
| 挑战 | 具体表现 | 我的经验 |
|---|---|---|
| 尺度模糊 | 同一张图,可能是近处小物体,也可能是远处大物体 | 我曾经在室内场景上训练好的模型,拿到室外直接崩了 |
| 深度歧义 | 不同深度组合可能投影出完全相同的2D图像 | 比如一个杯子放在桌上,你很难判断它离桌沿到底多远 |
| 纹理缺失 | 白墙、天空等无纹理区域,深度信息几乎为零 | 我试过在纯色背景上做推理,结果深度图像一锅粥 |
| 光照变化 | 阴影、高光会严重干扰深度判断 | 同一个场景,白天和晚上拍,深度图差异很大 |
| 数据标注困难 | 逐像素深度真值获取成本极高 | 用激光雷达标数据,一台设备几十万,还只能在室内用 |
4.2 基于CNN的单目深度估计方法
深度学习兴起后,单目深度估计才有了质的飞跃。核心思路其实很直接:用CNN从图像中提取特征,然后回归出每个像素的深度值。
我按时间线梳理一下几个里程碑式的方法:
- Eigen et al. (2014)——开山之作。用了两个尺度的CNN,一个做全局粗估计,一个做局部细调。我记得当时看到结果时挺震撼的,虽然分辨率很低,但至少能看出物体远近关系了。
- Laina et al. (2016)——引入残差网络和上采样模块。他们把深度估计当作一个有序回归问题,而不是简单的回归。我个人习惯用这个思路做baseline,效果稳定。
- Fu et al. (2018)——把连续深度离散化成多个区间,用分类的思路做回归。这个想法很巧妙,我后来在好几个项目里都借鉴了这种策略。
- 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,效果都还不错。
4.3.2 网络架构
MiDaS的骨干网络用的是Vision Transformer(ViT)或Swin Transformer,而不是传统CNN。为什么?因为Transformer的全局注意力机制能更好地捕捉场景中的长距离依赖关系——比如判断一个物体是"在远处"还是"被遮挡",需要看整个画面的上下文。
下面这张图展示了MiDaS的整体流程:
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)
4.4 MiDaS在人像模式中的应用
回到我们的课程主题——人像模式。MiDaS输出的深度图可以直接用来做散景渲染吗?嗯,这里要注意几个问题:
| 应用场景 | MiDaS表现 | 我的处理建议 |
|---|---|---|
| 人像前景分割 | 人物边缘较清晰,但头发细节不够 | 结合语义分割模型做mask refinement |
| 渐进式散景 | 深度值连续,适合做渐变模糊 | 直接用深度值控制模糊半径,效果自然 |
| 背景替换 | 深度边界不够锐利 | 用Canny边缘检测辅助做硬分割 |
| 实时处理 | large版本太慢,small版本精度下降 | 用small版本+轻量后处理,平衡速度与质量 |
4.5 小结与思考
单目深度估计这条路,我从2016年跟到现在,看着它从学术玩具变成工业可用工具。MiDaS的出现是一个分水岭——它证明了只要数据够多、模型够大、损失函数设计得巧妙,单目深度估计完全可以达到实用水平。
但说实话,它还不是万能的。极端光照、透明物体、镜面反射这些场景,MiDaS照样会翻车。我现在的做法是:把MiDaS当作一个强大的基础模型,然后针对具体场景做微调或后处理。这样既省去了从头训练的麻烦,又能保证落地效果。