如何在 ComfyUI ControlNet Aux 中用好 Depth Anything V2:深度估计预处理器的完整实现指南
【免费下载链接】comfyui_controlnet_auxComfyUI's ControlNet Auxiliary Preprocessors项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux
一个差点劝退我的深夜调参现场
你大概率也遇到过这个场景:ComfyUI 里拖进一张照片,接上 ControlNet,想用深度图锁住画面构图,结果生成的图要么前景糊成一片、要么背景完全"穿帮"。折腾半天,问题往往不在 ControlNet 本身,而在预处理阶段就埋下了隐患——深度图里花朵和背景混成一个灰块,ControlNet 拿什么去理解"远近关系"?
这类问题的根源,通常是预处理器选型或参数配置不当。在 ComfyUI ControlNet Aux(ComfyUI 的 ControlNet 辅助预处理器工具集)里,Depth Anything V2是当前性价比最高的一把"深度钥匙":它把单目深度估计(Monocular Depth Estimation,即用一张 RGB 图直接推断每个像素离相机多远的技术)做到了开箱即用的水平。本文不打算复述 README,而是带你从节点源码出发,把它的加载流程、推理链路、参数语义和工程化姿势一次讲透。
Depth Anything V2 在生态中的位置
ComfyUI ControlNet Aux 的预处理器主要分几大家族:线条类(Canny、Lineart、Scribble)、姿态类(OpenPose、DWPose)、语义类(OneFormer、Segment Anything)以及深度/法线类。深度家族里同时住着 Midas、Zoe、LeReS、Metric3D、Depth Anything 一代和 V2 等成员,各自的侧重点差异很大:
| 预处理器 | 特点 | 典型用途 |
|---|---|---|
| Midas | 老牌稳健,速度尚可 | 通用场景深度引导 |
| Zoe | 深度+法线双输出 | 需要法线信息的场景 |
| LeReS | 相对深度,边缘清晰 | 强调几何轮廓的构图 |
| Depth Anything V1 | 泛化能力强,依赖 transformers 管线 | 通用深度,适配广 |
| Depth Anything V2 | 精度/速度平衡最佳,ViT 骨干 | 本文主角 |
V2 相比 V1 最直观的差异藏在实现方式上:V1 在src/custom_controlnet_aux/depth_anything/transformers.py里走的是 HuggingFacepipeline(task="depth-estimation")封装,而 V2 在src/custom_controlnet_aux/depth_anything_v2/下自己实现了 DINOv2 骨干 + DPT 解码头,不依赖 transformers 的模型装配,加载路径更可控、推理更轻快。
上图是 Depth Anything V2 预处理器在 ComfyUI 中的典型接法:Load Image → Depth Anything V2 - Relative → Preview Image,右端输出黑白灰的深度图,白色代表近景(如花朵),灰黑渐变代表纵深。
最小可运行示例:十分钟跑通第一张深度图
第一步:安装
如果你还没有把插件放进 ComfyUI,先克隆项目仓库到 ComfyUI 的custom_nodes目录:
git clone https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux然后在项目根目录安装依赖(建议使用与 ComfyUI 相同的 Python 虚拟环境):
cd comfyui_controlnet_aux pip install -r requirements.txt重启 ComfyUI 后,在"添加节点"面板里搜索Depth Anything V2,就能看到Depth Anything V2 - Relative节点,它位于ControlNet Preprocessors/Normal and Depth Estimators分类下。
第二步:搭最小工作流
Load Image加载一张有明确前后景的照片(人像、静物、街景都行)。- 接
Depth Anything V2 - Relative。 - 接
Preview Image直接查看深度图。 - 再接
ControlNet到你的生成主链路。
第三步:不依赖 UI 的命令行验证
如果你更喜欢脚本验证,src/custom_controlnet_aux/depth_anything_v2/__init__.py里的DepthAnythingV2Detector可以直接在 Python 里调用:
import cv2 import numpy as np from PIL import Image from custom_controlnet_aux.depth_anything_v2 import DepthAnythingV2Detector # 1. 加载模型:filename 决定从哪个 HuggingFace 仓库拉权重 detector = DepthAnythingV2Detector.from_pretrained(filename="depth_anything_v2_vitl.pth") # 2. 读取图像(numpy HWC 格式) img = np.array(Image.open("photo.jpg").convert("RGB")) # 3. 推理:detect_resolution 是输出深度图的目标边长,512 起步 depth = detector(img, detect_resolution=512, output_type="np") # 4. 保存结果 Image.fromarray(depth).save("depth_output.png") print("深度图输出形状:", depth.shape)第一次运行会自动从 HuggingFace 下载权重到本地缓存,VitL 权重约 300MB,耐心等一两分钟即可。看到黑白灰的深度图,你的最小闭环就通了。
原理分层拆解:从一次调用到一张深度图
理解 V2 的关键在于把node_wrappers/depth_anything_v2.py的execute方法当作一条流水线的总入口,逐层往下钻。
第一层:节点壳(执行入口)
node_wrappers/depth_anything_v2.py第 20 行开始是节点核心逻辑:
def execute(self, image, ckpt_name="depth_anything_v2_vitl.pth", resolution=512, **kwargs): from custom_controlnet_aux.depth_anything_v2 import DepthAnythingV2Detector model = DepthAnythingV2Detector.from_pretrained(filename=ckpt_name).to(model_management.get_torch_device()) out = common_annotator_call(model, image, resolution=resolution, max_depth=1) del model return (out, )三个关键动作:
from_pretrained(filename=ckpt_name):按节点下拉框选中的权重文件名去拉模型;.to(model_management.get_torch_device()):把模型搬上 ComfyUI 自动选择的设备(CUDA / MPS / CPU);common_annotator_call(...):utils.py中的通用批处理函数,内部把 ComfyUI 的张量图像逐张转成numpy uint8,再调用检测器,最后归一化回[0,1]张量,同时带进度条。注意这里传入了max_depth=1,意味着相对深度版本输出的深度值被缩放到了 1 的范围内。
第二层:模型装配与权重解析
src/custom_controlnet_aux/depth_anything_v2/__init__.py里维护了一张模型配置表:
model_configs = { 'depth_anything_v2_vits.pth': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]}, 'depth_anything_v2_vitb.pth': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]}, 'depth_anything_v2_vitl.pth': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]}, 'depth_anything_v2_vitg.pth': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}, }from_pretrained的加载逻辑很简洁:先用util.py中的DEPTH_ANYTHING_V2_MODEL_NAME_DICT把文件名映射到 HuggingFace 仓库名,再走custom_hf_download下载(支持断点续传与本地缓存),最后load_state_dict载入权重并切到eval()模式:
@classmethod def from_pretrained(cls, pretrained_model_or_path=None, filename="depth_anything_v2_vits.pth"): if pretrained_model_or_path is None: pretrained_model_or_path = DEPTH_ANYTHING_V2_MODEL_NAME_DICT[filename] model_path = custom_hf_download(pretrained_model_or_path, filename) model = DepthAnythingV2(**model_configs[filename]) model.load_state_dict(torch.load(model_path, map_location="cpu")) model = model.eval() return cls(model, filename)第三层:推理数据流(DINOv2 + DPT 双引擎)
src/custom_controlnet_aux/depth_anything_v2/dpt.py中的DepthAnythingV2是真正的推理核心,整条数据流可以画成五段:
原始图 → image2tensor 预处理 → DINOv2 特征提取 → DPTHead 多尺度融合 → 逆归一化输出深度图预处理(image2tensor):无论输入多大,都会等比缩放到边长 518 的方图(input_size=518),并要求边长是 14 的整数倍(ensure_multiple_of=14),随后做 ImageNet 标准归一化(mean=0.485/0.456/0.406)。
骨干特征提取:DINOv2 按intermediate_layer_idx取出 4 层中间特征——V2 官方在dpt.py第 164 行给出了各规模模型的特征层索引:
self.intermediate_layer_idx = { 'vits': [2, 5, 8, 11], 'vitb': [2, 5, 8, 11], 'vitl': [4, 11, 17, 23], 'vitg': [9, 19, 29, 39] }解码融合(DPTHead):4 层特征经过projects(1×1 卷积投影)、resize_layers(转置卷积/池化对齐分辨率),再进入 refinenet 金字塔逐级融合,最终通过output_conv输出单通道深度图。最后把深度图插值回原始分辨率:
depth = self.forward(image, max_depth) depth = F.interpolate(depth[:, None], (h, w), mode="bilinear", align_corners=True)[0, 0]输出归一化:__init__.py第 45 行做了 min-max 归一化到 0~255:
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0值得一提的是 metric(度量)权重(如 Hypersim / VKITTI 训练的版本)会额外执行depth = 255 - depth做亮度反转,以匹配 ControlNet 深度模型的习惯配色。
参数与配置精读:每个旋钮都对应什么
Depth Anything V2 节点暴露的参数不多,但每个都值得较真:
| 参数 | 可选值 / 默认值 | 作用 | 调优建议 |
|---|---|---|---|
ckpt_name | vits / vitb / vitl / vitg,默认vitl | 选择骨干网络规模与对应权重 | 追求速度用vits;精度优先用vitl;vitg参数达 13 亿,仅显存 16G+ 时考虑 |
resolution | 64~16384,步长 64,默认 512 | 输出深度图的目标短边长度 | 与最终生成图分辨率匹配;过小丢失细节,过大会拖慢速度并增加显存 |
max_depth(相对版本固定为 1) | 相对深度为 1,度量深度可达 20 | 深度值缩放上限,影响明暗对比强度 | 相对深度模式下无需手动调;度量版本按场景(室内/室外)选择 10~30 |
环境级配置(config.yaml)
项目根目录的config.example.yaml提供三个与下载和存储相关的开关:
annotator_ckpts_path:权重落地目录,默认./ckpts,建议换成空间充足的绝对路径;USE_SYMLINKS:若你已通过 HuggingFace Hub 下载过相同权重,设True用符号链接复用缓存,能省下大量磁盘空间;custom_temp_path:下载临时目录,路径长度过长的 Windows 用户强烈建议配置。
这三个值也会通过环境变量AUX_ANNOTATOR_CKPTS_PATH、AUX_USE_SYMLINKS、AUX_TEMP_DIR被util.py读取,所以脚本化使用时同样生效。
实战案例:从入门到进阶的完整应用链路
场景一:人像构图锁定(入门)
对一张半身人像,用 V2(vitl + resolution=512)出深度图,再接 ControlNet 深度模型。关键技巧:背景要"退后"。因为相对深度图里人物会呈现为亮色前景、背景为暗色渐变,ControlNet 会把这种亮度关系翻译成"近大远小",重绘时保持人物的空间占位,背景细节则可以自由发挥。
场景二:室内场景布局迁移(进阶)
把 VitL 换成 Metric 权重(V2 代码中预留了depth_anything_v2_metric_hypersim_vitl.pth的映射,可在__init__.py的model_configs与util.py的DEPTH_ANYTHING_V2_MODEL_NAME_DICT中找到对应入口),在__call__里传入max_depth=20,得到带真实尺度信息的深度图。相比相对深度,度量深度能约束"沙发到墙的距离是 3 米而不是 0.3 米",更适合做室内布局改版——比如把客厅改造成电竞房时保持家具相对位置。
场景三:批量离线预处理(脚本化)
结合processor.py的Processor门面或直接循环调用检测器,可以批量生成深度图数据集,用于训练 LoRA 或做数据增强:
import os import numpy as np from PIL import Image from custom_controlnet_aux.depth_anything_v2 import DepthAnythingV2Detector detector = DepthAnythingV2Detector.from_pretrained(filename="depth_anything_v2_vitb.pth") for name in os.listdir("inputs"): path = os.path.join("inputs", name) img = np.array(Image.open(path).convert("RGB")) depth = detector(img, detect_resolution=768, output_type="np") Image.fromarray(depth).save(os.path.join("outputs", name)) print(f"{name} -> {depth.shape}")性能调优与工程化实践
显存与速度:按骨干规模对号入座
四个骨干的体量差异极大,实测经验大致如下(FP32 推理):
| 骨干 | 参数量级 | 单张 512 推理耗时(RTX 4090 量级) | 适用显存 |
|---|---|---|---|
| vits | 约 25M | 最快 | 4G 可跑 |
| vitb | 约 90M | 快 | 6G 可跑 |
| vitl | 约 300M | 中等 | 8G 舒适 |
| vitg | 约 1.3B | 明显变慢 | 建议 16G+ |
工程化建议:生产链路默认 vitb,质量敏感场景上 vitl。vitg 在 ControlNet 深度引导场景下的收益边际递减,但成本翻倍,谨慎选用。
内存三板斧
- 用完即删:节点代码里
del model已经做了第一步;长流程里可再配合torch.cuda.empty_cache()在关键节点回收碎片显存。 - 分辨率自适应:把
resolution与生成图尺寸解耦,深度图短边保持 512~768 即可满足 ControlNet 引导需求,不必跟随 2K 生成分辨率。 - 缓存复用:多轮测试同一权重时,模型加载是一次性的;若重启频繁,考虑把检测器做成模块级单例,避免重复下载与重复
load_state_dict。
下载链路优化
custom_hf_download支持断点续传,但首次拉取 vitg 的 1.3B 权重仍需较长时间。三个实用技巧:
- 提前用
AUX_ANNOTATOR_CKPTS_PATH指向已手动下载过权重的目录,跳过重复下载; - 内网/受限环境可预先把权重放到 ckpts 目录,检测到文件存在时
util.py会直接跳过下载(见if not os.path.exists(model_path)分支); - 多机共享权重时开启
USE_SYMLINKS,让各节点符号链接同一份 HuggingFace 缓存。
并发与批处理
common_annotator_call默认逐张处理(input_batch=False)。高吞吐离线任务可用多进程并行,每个进程持有独立的 vitb 检测器,吞吐接近线性扩展;注意进程数不要超过 CPU/GPU 并发上限,避免显存换页抖动。
常见问题排查:现象、根因、解法对照表
| 问题现象 | 根因 | 解决方案 |
|---|---|---|
| 首次运行时卡在"Downloading",进度条不动 | HuggingFace 网络受限或超时 | 配置代理;或手动下载权重放入annotator_ckpts_path对应子目录,让custom_hf_download命中本地文件 |
| 显存不足(OOM) | 骨干过大或resolution过高 | 换vits/vitb;降低resolution到 384;关闭其他占用显存的后台进程 |
| 深度图整体发灰、层次感弱 | 相对深度被max_depth=1压缩,或输入图本身对比度低 | 确认用的是相对版本;尝试不同分辨率重建;对低对比度输入先做亮度/对比度增强 |
| 输出图出现奇怪的边缘伪影 | 预处理ensure_multiple_of=14的缩放与resize_image_with_pad的补边叠加 | 保持resolution为 64 的倍数(节点默认已约束);避免输入超长条图像 |
| 生成的图不受深度控制 | 深度图与生成分辨率不匹配 | 检查 ControlNet 侧的pixel_perfect选项,参考utils.py中的pixel_perfect_resolution计算逻辑,让深度图短边与生成图对齐 |
| metric 权重结果明暗颠倒 | 度量深度模型输出的是真实距离,近处反而更暗 | 这是正常行为,代码已对 metric 权重自动执行255 - depth反转;若使用自己的脚本接入,记得复刻该逻辑 |
总结与最佳实践清单
Depth Anything V2 是 ComfyUI ControlNet Aux 深度家族里"性价比之王",用 DINOv2 骨干 + DPT 解码头的自研实现替代了 V1 的 transformers 管线封装,加载更快、可控性更强。掌握它的关键就三件事:选对骨干(vitb/vitl)、对齐分辨率(512 起步)、理解相对深度与度量深度的差异。
最后给你一张可直接抄作业的清单:
- ✅ 首次部署前先配置
config.yaml的annotator_ckpts_path,避免默认./ckpts被清空导致重复下载; - ✅ 默认链路选
vitl,生产批量任务降级到vitb,vitg留给离线高精度场景; - ✅
resolution与生成图短边保持一致,无需盲目拉高; - ✅ 深度图与 ControlNet 不匹配时,优先检查
pixel_perfect与预处理分辨率,而不是怀疑模型; - ✅ 离线/内网环境提前手动放置权重,绕过网络下载;
- ✅ 需要真实尺度(如室内改造、3D 应用)时,从
model_configs与DEPTH_ANYTHING_V2_MODEL_NAME_DICT切换到 metric 权重并设置合适的max_depth。
深度图是 ControlNet 构图引导里最"稳"的一类条件输入,而 Depth Anything V2 让这条链路变得又快又准。按本文的路径把节点源码和参数语义过一遍,下次再遇到"深度图糊成一团"的深夜,你就能一眼定位问题所在了。
【免费下载链接】comfyui_controlnet_auxComfyUI's ControlNet Auxiliary Preprocessors项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考