ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单目深度估计实战:Depth Anything V2原理、部署与避坑指南

单目深度估计实战:Depth Anything V2原理、部署与避坑指南

如果你做过几年计算机视觉落地,一定对“单目深度估计”又爱又恨——爱的是它只用一个普通摄像头,就能给整张图每个像素标出远近;恨的是纯几何方法做单目深度估计本质上是病态的,同一棵树正面拍和侧面拍出来的深度图完全不一样,算法根本无从下手。Depth Anything V2就是这个方向的代表作之一:它用海量无标注数据加伪标签把模型喂大,让一张普通RGB照片直接推理出相对深度图,不需要双目相机、结构光也没有激光雷达,处理一张常规尺寸图片在小模型上只要几十毫秒。我最早接触这个项目是给一个做AR的小团队做场景遮挡评估,后来又在短视频特效、机器人避障两个方向复用了同一套推理代码,整体感受是:这个模型的泛化能力和工程易用性,在开源深度模型里属于第一梯队。如果你正准备给产品加深度能力、研究单目深度估计,或者单纯想搞明白这套模型的原理和代码,这篇实战笔记应该能帮你少走不少弯路。

1. 先搞懂:单目深度估计到底难在哪

1.1 一个摄像头如何“看出”远近

要理解Depth Anything V2的价值,得先回到一个最朴素的几何问题:人眼是怎么判断距离的?

最靠谱的机制是双眼视差。两只眼睛看到同一物体的角度不同,这个角度差经过大脑换算,能给出相当精确的距离信息。传统双目视觉就是模仿这个原理:左右两个相机同时拍摄,通过特征匹配找到同一个点的视差,再用三角测量算出深度。这套方案在近距离和中距离精度都不错,缺点也很明显——需要精确标定的双目外参、两路图像同步,而且对无纹理区域几乎无能为力。

另一条路是主动测距。结构光比如iPhone早期的Face ID,向场景投射大量已知图案的红外点阵,再根据图案变形还原深度;ToF则是直接发射光脉冲、测量反射时间。这类方案精度高、不受环境纹理限制,但成本高、功耗大,在室外强光下容易失灵。

单目深度估计就不一样了,它只有一个普通摄像头,没有任何主动传感器。这个问题在数学上被称为“病态问题”——同一个二维图像,理论上可以对应无数个不同的三维场景。一个小球在近处和远处,投影到画面里可能在完全相同的位置;一面纯色的墙,画面上每个像素都一样,你根本没法从这一张图里确切知道哪里离你近。

那深度学习是怎么干成这件事的?

1.2 深度学习如何“骗过”几何限制

深度学习的思路很简单:既然单张图信息不够,那就从海量数据里学“先验”。说白了,模型不需要真正解算出几何关系,它只需要学会“这类场景大概长什么样”。

举个例子:一张室内客厅的照片,即使不看任何额外信息,你也能猜到沙发比茶几低、茶几比吊灯低,远处窗户离你最远。这些知识不是靠某一帧几何推出来的,而是你在生活中见过无数个客厅后形成的先验。深度模型做的也是这样的事——它不去算视差,而是从训练数据里学大量的尺度线索:透视消失点在图片哪个位置、已知大小的物体(人、车、门)像素尺寸有多大、物体之间的遮挡关系、阴影和色温变化等。

所以单目深度模型在“看懂”图像这件事上,更像一个经验丰富的摄影师,而不是一个全站仪。摄影师瞄一眼照片就能说“这个人离我大概三米,背景山有几百米”,精度不高但方向感极强。Depth Anything就是这种思路做到极致的项目:它不像之前的很多方法把眼光局限在带标注的小规模数据集上,而是把大量无标注的真实图片都利用起来,让模型见过的场景足够多,最后的泛化能力自然就上来了。

1.3 Depth Anything V2做了什么升级

Depth Anything系列是开源社区里最受关注的单目深度估计项目之一。第一版出来的时候,已经能碾压当时很多需要额外辅助信息的模型,但它在几个细节点上还不够完美:物体边缘容易出现光晕、细小结构容易糊掉、对打印图片这类“假纹理”会被骗过、推理速度也不够快。

V2的升级方向很明确:把“伪标签”这条路走得更彻底,同时把模型和训练策略做了一次大调整。它的核心理念听起来简单到有点“笨”——既然标注数据不够,那就让一个强教师模型去给海量无标注图片打标签,然后用这些标签去训练学生模型。但真做起来,伪标签里的噪声是最大的坑:如果教师模型在某个区域预测错了,学生模型不仅会学到错误,还会把错误放大。V2给出的解法是让教师模型同时输出置信度,低置信度的区域直接不参与训练,相当于给伪标签做了一次自动化质检。

另外,V2把模型拆成了相对深度和度量深度两条线。相对深度模型做的事是输出一张0到1的深度图,数值大小只表示相对远近,适合做图像编辑、特效、语义理解这类“不需要真实尺寸”的任务;度量深度模型则通过额外监督,输出接近物理距离的深度值,可以直接用于机器人导航、三维重建这类需要实际尺度的任务。这一点非常关键,很多新手没搞懂就直接拿相对深度去重建三维,最后发现比例完全不对。

2. 原理拆解:V2凭什么又稳又快

2.1 模型架构一条线讲清楚

Depth Anything V2的模型结构可以拆成三大块:图像编码器、特征融合解码器、深度输出头。

编码器用的是Vision Transformer(ViT)这类基于注意力机制的骨干网络。ViT的输入方式很有意思:它先把一张图切成固定大小的patch,比如16乘16像素的小块,然后像NLP里处理token一样,把这些patch当作一个序列输入Transformer。这样做的优势在于,模型在很早的阶段就能看到全局信息——每个token在做注意力计算时都会和其他所有token交互,类比来说就是一个人看画,不是从左上角一行一行扫,而是先眯起眼整体看一遍,再聚焦细节。这种全局建模能力对深度估计非常关键,因为判断一个像素的深度经常需要参考远处另一个区域的信息,普通卷积网络要靠很深的堆叠才能把感受野撑到全图,Transformer天然没有这个限制。

解码器沿用了DPT(Dense Prediction Transformer)的思路。它把ViT不同层输出的多尺度特征重新组装起来。这里最值得关注的是“特征融合”的细节:底层特征分辨率高但语义弱,负责边缘细节;高层特征分辨率低但语义强,负责整体结构。DPT不是简单地把这些特征相加,而是用一个个可学习的融合模块,把不同层特征按空间位置对齐、拼接,再通过一系列上采样和卷积恢复到输入分辨率。这就好比拼一张拼图,既有小块的纹理信息,又有一颗颗完整画面的结构信息,两者缺一不可。最后输出头是一个很轻量的卷积层,把融合特征压成单通道,经过sigmoid激活后得到0到1的相对深度图。

整个链路跑下来,一张518乘518分辨率的输入图,在小模型版本上单次前向只需要几百毫秒级别的算力,如果按实际工程里缩放到更低分辨率加fp16推理,在一些中端GPU上能跑到几十毫秒一帧,这个速度对很多交互应用已经够用了。

2.2 训练策略:为什么伪标签这么重要

深度估计最缺的从来不是模型结构,而是标注数据。给一张图打语义分割的标签,标几个人、几辆车还能靠人工硬扛;给每个像素标“离摄像头多少米”,这从物理上就人工难做,行为根本不可能。

Depth Anything系列最聪明的地方,就是绕开了人工标注这个瓶颈。

它的训练分两条线。第一条线,用少量高质量标注数据(比如室内外深度数据集)训练一个教师模型,这个教师模型的精度不一定要多惊艳,关键是它的“判断习惯”要稳定。第二条线,把这个教师模型放到海量无标注的真实图片上跑推理,给每张图生成伪深度标签。公开可用的大规模网络图片集很多,数量动辄上千万甚至上亿张,经过教师模型批量处理后,就得到了一批“量”上极其可观、“质”上参差不齐的训练数据。

这里有个必须处理的隐患:教师模型的伪标签不是全对的。比如一片强光下的玻璃表面,教师模型很可能会预测成近处有物体;一片纯白墙面,它可能给出一个平滑但错误的斜坡。如果学生模型直接把这些错误当标准答案学,最终学出来的分布会被错误主导,反而比在干净小数据集上训练更差。

V2的解法是:让教师模型给每个像素同时输出一个置信度分数。这个分数是基于教师模型自身在多个增强版本下的预测一致性算出来的——同一张图做了不同翻转、缩放、颜色扰动后再推理,如果结果高度一致,说明这个区域模型有把握;如果结果差异很大,说明这里是“瞎蒙”的,就把它在训练loss里直接mask掉。这招在工程上简单有效,相当于给海量伪标签自动过了质检环节。我后来自己也尝试过类似的课程式策略,先让模型在简单场景上训稳,再逐步放开难样本,整体效果比一次性全部灌进去要好不少。

2.3 相对深度与度量深度的区别

Depth Anything V2出来后,很多人容易被“深度图”三个字带偏,以为拿到手的数值就是真实的“几米几米”。实际上,模型默认输出的是一张相对深度图。

相对深度的含义是:这张图里每个像素的深度值只有“相对大小”的意义,A点比B点近、B点比C点近,但“近多少”没有一个和物理世界的固定换算关系。你可以把整张图的深度值做任意单调变换,比如整体乘以2、加上一个常数,它表达的“远近关系”仍然不变。这在做图像编辑、特效、语义辅助时完全够用,因为这类任务关心的只是层次关系,不是绝对距离。

度量深度则不同。V2专门训练了一个测度深度分支,输出的数值尽可能逼近物理真实距离的单位,比如米。这需要使用带有真实尺度信息的深度数据进行监督训练,模型学到的是“人的头大约离相机1.8米,树的边缘大约在5米”这种绝对尺度感。在实际应用里,这个分支的三维重建效果要实用得多——你可以直接把深度点云转化为世界坐标,而不需要再做一步尺度恢复。

选哪个版本完全看场景:做照片背景虚化、3D视差动画、语义遮挡判断,用相对深度就够,模型体积还更小;做机器人导航、机械臂抓取、室内三维重建,就必须用度量深度版本,否则后续算法会全乱套。

2.4 效果对比:V2相对V1的几个关键进步

我在同一批测试图上把V1和V2都跑了一遍,观感差异还是很明显的。

最直观的是边缘质量。V1生成的深度图在物体轮廓处经常有一圈泛白或者泛黑的光晕,尤其是人的头发、电线这类细长结构,深度跳变不干脆,像PS里羽化过头的感觉。V2在这块提升很大,轮廓要锐利得多,头发丝和电线杆基本能给出明确的深度分界。主要原因就是它换了更强的教师模型和伪标签清洗策略,让模型学到更清晰的深度边界先验。

第二点是细小物体。V1对画面里的文字、栏杆、树叶这类小目标经常直接忽略,把它们平滑成背景深度。V2则明显更“较真”,能把这些小结构的深度从上到下正确区分出来。这对做三维重建和特效来说很关键——如果一棵树被抹平了,后面任何关于树的交互都会出问题。

第三点是鲁棒性。V2对运动模糊、低光照、极端透视这类退化场景的适应能力更好。这一点我猜测和它训练数据里天然包含大量“粗糙真实图”有关——网络图片什么质量都有,模型被迫学会从混乱中抓结构。

第四点是速度。虽然V2模型本身结构没比V1复杂多少,但由于训练策略的改进,小模型版本的收敛质量提升明显,实际工程里不用非得跑大模型才能出好效果。很多场景用Small版就够了,省下来的时间可以直接做后处理和其他模块。

3. 实战准备:环境搭建与模型下载

3.1 硬件与软件要求

先泼一盆冷水:内存不够别硬上大模型,老老实实用Small版本,效果也够用。

我自己的常用配置是这样:一张显存8GB以上的显卡(入门级即可),Python 3.9或3.10,PyTorch 2.0以上版本,CUDA 11.8。如果显卡显存只有4GB,跑Small版完全没问题,Base版勉强能跑,Large版本建议改用半精度并且把输入分辨率降到350左右。下面是我测试过的环境组合,可以参考:

  • 操作系统:Ubuntu 20.04 或 Windows 10/11,都能跑
  • Python:3.9、3.10均可
  • PyTorch:2.0.1、2.1.0都验证过,建议用2.1以上
  • CUDA:11.8,驱动版本保持较新
  • 关键依赖:opencv-python、pillow、numpy、timm

依赖安装没什么玄学,用pip直接装就行。如果你要走transformers接口,还要装transformers库。我自己实际更推荐直接用项目源码里的原生推理脚本,少一层封装,排查起来也更直接。

3.2 模型获取方式

Depth Anything V2的模型权重是直接公开下载的,不用自己训练,这是它能广泛落地的重要原因。模型分四个规模:Small、Base、Large、Giant。Small适合边缘部署和实时推理,Base是平衡点,Large和Giant适合离线高质量生成。下载checkpoint时注意看文件名,里面带metric字样的是度量深度版本,没有的是相对深度版本,别下错。

因为权重托管在海外平台,国内下载经常很慢。我用的办法很朴素:找一台网络通畅的机器先把权重拉下来,再走内网或者移动硬盘传到目标机器上。这类操作在模型文件上非常常见,先把模型文件转存到国内可直连的对象存储,再从对象存储上下载,速度能提升一个数量级。整个过程和任何网络工具无关,纯粹是工程上的文件搬运。

3.3 目录结构与推理入口

项目代码拿到手后先别急着跑,把目录结构理清楚。核心就两个文件:一个负责模型定义,一个负责推理入口。模型定义文件里会看到ViT编码器和DPT解码器的类,推理入口则完成图像加载、预处理、前向、后处理这些环节。我建议第一次跑的时候,不要自己写一堆花活,先用官方脚本跑通,输出一张深度图,确认环境没问题,再逐步改成自己的接口。

4. 推理Demo:一张RGB图输出深度图

4.1 最直接的方式:Python脚本推理

这里给一个我实测过的最小可运行版本,用transformers接口加载模型,几行代码出深度图:

import cv2 import torch import numpy as np from PIL import Image from transformers import AutoImageProcessor, AutoModelForDepthEstimation # 模型标识按你下载的具体checkpoint路径来 processor = AutoImageProcessor.from_pretrained("your_local_path/processor") model = AutoModelForDepthEstimation.from_pretrained("your_local_path/model") # 读图 image = Image.open("input.jpg").convert("RGB") # 预处理 inputs = processor(images=image, return_tensors="pt") # 前向推理 with torch.no_grad(): outputs = model(**inputs) depth = outputs.predicted_depth # 深度图后处理 depth = depth.squeeze().cpu().numpy() depth_min = depth.min() depth_max = depth.max() normalized_depth = (depth - depth_min) / (depth_max - depth_min) # 保存为伪彩色深度图 depth_colored = cv2.applyColorMap( (normalized_depth * 255).astype(np.uint8), cv2.COLORMAP_JET ) cv2.imwrite("output_depth.png", depth_colored)

代码逻辑很简单:加载模型 -> 预处理图片 -> 前向推理 -> 把原始深度值归一化 -> 映射成伪彩色图保存。如果你直接用项目源码跑的接口也一样,本质没区别。

很多人第一次跑出来看到的是一张黑乎乎或者灰蒙蒙的图,然后就觉得模型坏了。其实不是模型坏了,是直接保存原始深度值时,数值范围太窄,肉眼很难分辨。深度图的“可视化”和“原始数据”是两码事:原始深度值是单通道浮点,适合丢给后续算法;伪彩色JET图只是给人看的展示层。这两个一定要分清楚,否则后面做应用很容易被误导。

4.2 源码关键片段解读

要深入理解这份代码,有几个关键点是绕不开的。

预处理阶段,processor会做两件事:把图像缩放到模型输入要求的长边,同时做归一化。这里的核心是保持长宽比而不是直接拉伸,因为直接拉伸会破坏物体的相对几何关系,模型输出的深度分布会被扭曲。另外,图像会被除以255并减去均值和方差——这些归一化参数是模型在训练时的标准设定,推理时保持一致才能让数据分布对齐。

前向传播阶段,model内部执行的过程可以概括为:输入图像经过ViT编码器切成patch并加上位置编码,逐层做自注意力计算;DPT解码器收集不同层输出,经过reassemble操作把特征恢复到空间网格格式,再逐级上采样融合;最后卷积层输出单通道logit,经过sigmoid得到0到1的深度值。

深度图的数值语义要说明一下:0不一定代表“最近”,1也不一定代表“最远”,它取决于模型的训练设置和输入图像的内容。相对深度模型输出的只是单调的远近关系,没有绝对单位。拿到深度数组后,如果要做可视化,建议做一次min-max归一化再乘255映射到colormap,这样每一张图都能把对比度拉开。我自己习惯固定一个可视化的深度范围,而不是每次动态归一化,尤其是做视频逐帧处理时,动态归一化会导致深度图的亮度不断闪烁,看着非常掉价。

4.3 关键参数怎么调

第一次跑通后,很快会面临一个实际问题:怎么让效果更好、速度更快。

输入分辨率是第一个要调的参数。V2官方在训练时用的分辨率是518,但在实际工程里你可以调低来换速度,也可以调高来换细节。我自己的经验是:场景里小物体多、边缘要求高的,调到560或600会明显更稳;只做大场景层次判断的,350到400也够用;视频实时处理用320附近的分辨率配合fp16,在很多消费级显卡上都能跑实时。

第二个参数是模型置信度阈值。如果你按官方训练代码微调自己的数据,会看到loss里有个置信度mask的开关。推理阶段一般用不到这个参数,但在训练阶段它很重要:把低置信度区域直接踢出loss,能有效防伪标签噪声带偏模型。调这个阈值的核心思路是先看训练集里伪标签的错误率大概多高,错误多就把阈值调严,错误少就放松点。

第三个是输出后处理。如果你的下游是三维点云或深度滤波,建议对深度图做一次中值滤波或引导滤波,能很好去掉孤立噪声点和边缘震荡。不要用高斯模糊,它会抹掉深度边缘,让物体和背景的层次糊在一起。

5. 常见问题与避坑实录

5.1 显存不够、推理太慢怎么办

先检查是不是模型规模选大了。Small版在4GB显存下能跑,Large版在8GB下都可能费劲。解决办法按性价比排序:换小模型 -> 开fp16/半精度 -> 降低输入分辨率 -> 转ONNX或TensorRT加速。

半精度这个操作要注意:开fp16后,如果输出深度图出现异常的黑块或白点,说明某些层的数值范围超过了fp16的表达能力,这时可以把关键层强制切成fp32混合精度,或者干脆换回fp32,牺牲一点速度换稳定。

ONNX导出是另一个常用路线。PyTorch模型转ONNX很简单,用torch.onnx.export就行,但有几个坑:动态输入尺寸一定要设置好,否则模型只能在固定分辨率下跑;归一化操作最好也并进计算图里,省的在外面反复写预处理。转完ONNX后,CPU上跑会比较吃力,真正提速还是得配TensorRT或者OpenVINO,根据目标平台的推理引擎来选。

5.2 深度图“发灰”或“太暗”

这个几乎是所有新手都会遇到的问题。原因我在前面提过:直接保存0到1的原始深度值,虽然数据没毛病,但人类肉眼对低对比度的灰度图不敏感,于是看到的就是一片灰蒙蒙。

解决办法分两层。展示层:用applyColorMap转成JET伪彩色,或者按最大值做亮度拉伸。数据层:如果你后续要做深度估计精度评估,不要拿可视化的图去算误差,一定要用原始浮点深度数组。我见过有同事把JET图当深度输入去和点云做配准,结果自然是错得离谱。记住,可视化只是给人看的,算法只认原始张量。

5.3 相对深度无法直接用于三维重建

这是最容易被忽略的坑。相对深度只能保证远近关系,数值大小和物理距离没有固定映射。你用相对深度生成点云,如果不做尺度恢复,得到的形状会像被横向压扁或者拉长的橡皮泥——比例不对。

正确做法是直接用度量深度版本。如果你手头已经有其他传感器能提供参照距离(比如TOF的稀疏点、地面平面方程),也可以通过最小二乘拟合求一个全局缩放因子,把相对深度硬调到物理尺度上。这个方法在室内场景很常用,室外会因为深度范围太大而不太稳。

5.4 边缘和细小物体丢失

V2比V1强很多,但遇到细长物体偶尔还是会把它们的后方背景直接填平。我处理这类问题最有效的方法是:在深度图上跑一次快速引导滤波,引导图用原图,tolerance参数调到60到80,能明显把边缘“粘”回来,同时保留层次。如果想更精细,可以在分割模型输出的人像或物体mask基础上,把mask边界内的深度做全局一致性约束,这个属于定制化操作了,但效果很值得。

5.5 视频场景深度闪烁

单帧推理在视频上很容易出现深度不稳定,同一个静止物体在相邻帧里深度跳变,闪烁很明显。解决思路有三个:一是直接换更大的模型,大模型单帧稳定性天然更好;二是对深度图做时间域平滑,但注意别把运动物体的真实深度变化也抹掉了,建议用带权重的前后帧加权;三是结合光流做深度传播,让深度图跟随光流运动,这个方案效果好但工程复杂度也上去了。

6. 应用场景与扩展方向

6.1 落地场景盘点

Depth Anything V2这类单目深度模型,最大的价值不是替代高精度传感器,而是给大量“没有深度能力”的现有设备低成本地补上深度感知。

最典型的是手机人像虚化。很多手机没有双摄或者深感镜头,但照片虚化效果又是个强需求。拿单目深度图做一个粗略的“人-环境-前景”分层,再配合分割模型得到精确的人物轮廓,就能做出非常自然的景深模拟。深度图越平滑、边缘越干净,虚化效果越像真的。

短视频特效也是一个快速落地场景。一张静态图片加一张深度图,就能生成视差动画:根据深度值给不同图层设置不同位移量,模拟3D照片的旋转效果。这个玩法在社交App里一直很受欢迎,而单目深度模型的优势就是无需任何额外采集设备,用户随便传张照片就能用。

机器人避障是另一个方向。V2的度量深度版本在结构化室内环境里,可以给移动机器人提供一个成本极低的深度感知通道,辅助超声波或者激光雷达做补充判断。单目的精度达不到激光雷达的水平,但胜在便宜、全面、没有盲区,作为感知冗余非常合适。

智能驾驶的视觉冗余也一样。视觉方案本来就依赖多摄像头,单目深度模型可以做车道级的障碍物距离粗估,给即使传感器失效的场景补一套兜底判断。当然这只适合做辅助,真正决策还是得靠多传感器融合。

AR场景理解更是天然适配。AR里最头疼的两件事是遮挡和碰撞:虚拟物体要被人挡住,虚拟物体要和真实桌面碰撞。单目深度图能给出像素级远近关系,AR引擎拿它做遮挡渲染和物理碰撞的粗估计,效果立竿见影。

6.2 后面还能怎么扩展

目前我看到的进阶玩法主要有几个方向。

第一是把深度估计和语义分割做联合。深度图和分割图互相促进:深度图提供几何边界辅助分割,分割mask反过来约束深度图在物体内部保持一致。两者融合后,能得到更干净的物体层次,这也是很多产品做“物体级深度”的路线。

第二是结合光流扩展到动态场景。单帧深度是静止假设下的产物,遇到运动物体和相机运动同时出现,深度会乱。结合光流后,可以把深度和运动解耦,得到更稳定的动态深度序列,这对视频类应用价值很大。

第三是轻量化部署。把大模型蒸馏成小模型,或者用TensorRT、OpenVINO在边缘设备上做推理优化。现在一些嵌入式设备上的深度估计已经能做到10毫秒级别,这让工业检测、扫地机器人这类实时场景真正可以使用单目深度。

如果你对“深度图怎么转点云”“怎么把深度图接进三维重建管线”这类问题感兴趣,我后面可以再写一篇专门的技术笔记,把这些坑一次性讲透。不过在那之前,把你手头的模型跑通、看懂输出、调好参数,已经足够领先大多数人一个身位了。

我在实际项目里的体会是:Depth Anything V2这套模型,真正值钱的不是它有多大的参数量,而是它在“海量数据 + 伪标签 + 清洗策略”这套组合拳上的工程执行力。方法说出来都懂,但把每个环节都打磨到能落地的程度,才让这个模型从“能用”变成“好用”。最后分享一个小技巧:保存深度图做展示时,给JET colormap固定一个深度范围参数,不要每次动态归一化,这样在连续多帧测试时,深度图的亮度变化会平滑得多,排查问题的时候也更容易看出是模型输出问题还是后处理问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进