在 MLX 上使用 Video Depth Anything 进行视频深度估计:架构、推理与源码解析
2026/9/17 21:00:12 网站建设 项目流程

在 MLX 上使用 Video Depth Anything 进行视频深度估计:架构、推理与源码解析

【免费下载链接】mlx-vlmMLX-VLM is a package for inference and fine-tuning of Vision Language Models (VLMs) on your Mac using MLX.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-vlm

本指南以mlx-vlm仓库中的 video_depth_anything 模型文档 为核心,系统讲解如何在 Apple Silicon Mac 上,通过 MLX 运行 ByteDance 提出的 Video Depth Anything 模型(CVPR 2025 highlight),完成对任意长度视频的时序一致的单目深度估计。读完本文,你将掌握该模型的整体架构(DINOv2 骨干 + DPT 时序头)、官方支持的六种检查点与加载方式、两种推理路径(参考滑窗流水线与单片段直调)、输入预处理约束,以及配套的精度验证与单元测试。

模型是什么:输出深度图而非文本的"非典型 VLM"

Video Depth Anything 是 Depth Anything 团队 发布的视频单目深度估计模型,其核心目标是解决单帧深度估计在视频上逐帧独立推理时出现的时间闪烁与不一致问题,为任意长度的视频生成时序连贯的深度序列。

与 mlx-vlm 中绝大多数模型不同,该模型不产出任何文本——它的输出是逐帧的深度图。在mlx-vlm的实现中,这一点在 模型主文件 里被明确标注:Model接收通道在最后的视频帧张量(B, T, H, W, 3),返回形状为(B, T, H, W)的深度图,数值为相对(relative)或公制(metric,当config.metric为真时)的类视差(disparity-like)值。

整体架构由三部分构成:

  1. DINOv2 骨干网络:提供 vits / vitb / vitl 三种规模的特征提取器;
  2. DPT 头(Dense Prediction Transformer head):将多尺度特征融合为稠密深度;
  3. AnimateDiff 风格的时序运动模块(temporal motion modules):沿时间轴做自注意力,把时序一致性"注入"特征融合过程。

其中Model类直接复用了仓库中独立的 DINOv2 实现,并通过self.pretrained.get_intermediate_layers取指定中间层特征,再交给DPTHeadTemporal处理(见 video_depth_anything.py)。

支持的检查点与加载方式

MLX 移植版官方支持以下六种检查点(对应原版 PyTorch 仓库的六个版本),来源与 MLX 仓库一一对应:

| 变体 | MLX 仓库 | 原始来源 | |:-|:-|:-| | Small |mlx-community/Video-Depth-Anything-Small-MLX|depth-anything/Video-Depth-Anything-Small| | Base |mlx-community/Video-Depth-Anything-Base-MLX|depth-anything/Video-Depth-Anything-Base| | Large |mlx-community/Video-Depth-Anything-Large-MLX|depth-anything/Video-Depth-Anything-Large| | Small metric |mlx-community/Metric-Video-Depth-Anything-Small-MLX|depth-anything/Metric-Video-Depth-Anything-Small| | Base metric |mlx-community/Metric-Video-Depth-Anything-Base-MLX|depth-anything/Metric-Video-Depth-Anything-Base| | Large metric |mlx-community/Metric-Video-Depth-Anything-Large-MLX|depth-anything/Metric-Video-Depth-Anything-Large|

三种规模 Small / Base / Large 分别对应vits/vitb/vitl编码器;带metric字样的版本输出绝对尺度的深度(公制深度),推理时不做窗口间的尺度/偏移对齐。

加载时无需任何转换步骤——权重由加载器直接从 Hugging Face Hub 下载。标准的加载入口是mlx_vlm.load

from mlx_vlm import load model, processor = load("mlx-community/Video-Depth-Anything-Small-MLX")

加载后返回的processor实际是 VideoDepthProcessor,它通过preprocessor_config.json自动读取input_sizeensure_multiple_of参数;__init__.py中的install_auto_processor_patch调用则确保框架能够自动为该模型类型装配正确的处理器。

快速上手:滑窗推理流水线

官方推荐的推理路径是使用VideoDepthPredictor,它完整移植了参考实现infer_video_depth的循环逻辑:重叠的 32 帧滑窗、关键帧条件注入、窗口间的尺度/偏移对齐(metric 模型跳过对齐)。对于任意长度的视频,这是保证全局时序一致性的推荐做法。

from mlx_vlm import load from mlx_vlm.models.video_depth_anything.generate import ( VideoDepthPredictor, read_video_frames, ) model, processor = load("mlx-community/Video-Depth-Anything-Small-MLX") predictor = VideoDepthPredictor(model, processor) frames, fps = read_video_frames("input.mp4", max_len=300, target_fps=15) depths = predictor.infer(frames) # (T, H, W) float32, input resolution

其中:

  • read_video_frames(video_path, max_len=-1, target_fps=-1)依赖 OpenCV(cv2)读取视频:当target_fps > 0且原视频帧率更高时,按round(original_fps / target_fps)的步长抽帧;max_len限制读取的最大帧数;返回(T, H, W, 3)的 uint8 RGB 帧数组与降采样后的实际帧率(见 generate.py)。
  • predictor.infer(frames, progress=True)接受(T, H, W, 3)uint8 RGB 输入,返回(T, H, W)的 float32 深度图,分辨率与输入视频一致;progress=True时用 tqdm 显示进度条。

直接调用模型:处理单个短视频片段

如果只是处理一个不超过 32 帧的短视频片段,可以跳过滑窗对齐逻辑,直接调用模型本身:

import numpy as np frames = ... # (T, H, W, 3) uint8 RGB pixel_values = processor(frames)["pixel_values"] # (T, H', W', 3) normalized depth = model.predict_depth(pixel_values) # (T, H', W')

processor__call__会依次对每帧执行preprocess_frame:缩放到 14 的倍数(双三次插值)并做 ImageNet 归一化(mean[0.485, 0.456, 0.406],std[0.229, 0.224, 0.225]),见 processing_video_depth_anything.py。

model.predict_depthModel.__call__的单片段便捷封装:内部将(T, H, W, 3)补成 batch 维为 1 的(1, T, H, W, 3),前向计算后取回(T, H, W)的深度图(见 video_depth_anything.py)。前向过程会执行双线性上采样回输入分辨率并施加 ReLU 非线性,确保输出非负。

底层原理:从配置到前向的一次完整追溯

配置预设:三种编码器如何决定模型规模

config.py 中定义了ENCODER_PRESETS,在共享 DINOv2 维度之上为每种编码器指定 DPT 头的宽度与通道数:

| 编码器 | 特征宽度 (features) | 各层输出通道 (out_channels) | 中间层索引 (intermediate_layer_idx) | |:-|:-|:-|:-| |vits| 64 |[48, 96, 192, 384]|[2, 5, 8, 11]| |vitb| 128 |[96, 192, 384, 768]|[2, 5, 8, 11]| |vitl| 256 |[256, 512, 1024, 1024]|[4, 11, 17, 23]|

ModelConfig__post_init__会在构造时根据encoder自动回填所有为None的骨干参数,例如默认vitl会得到embed_dim=1024depth=24。这在 单元测试 test_config_presets 中得到验证。时序模块相关的可配置项包括:num_frames=32(位置表的时间长度)、pe="ape"(位置编码类型,当前仅支持绝对正弦位置编码)、num_attention_heads=8num_transformer_block=1num_attention_blocks=2norm_num_groups=32。此外ModelConfig还通过TextConfig = VisionConfig = ModelConfig别名保持与 mlx-vlm 框架的update_module_configs兼容(见 config.py)。

时序运动模块:沿时间轴的注意力

时序模块 是 AnimateDiff 风格的TemporalModule,核心组件TemporalTransformer3DModel的输入是通道在最后的(B, T, H, W, C)张量。其内部流程为:

  1. 重塑为(B*T, H, W, C)后做 GroupNorm 归一化;
  2. 将空间维度展平并通过proj_in投影到注意力维度;
  3. 经过若干TemporalTransformerBlock——每个 block 内部包含多个TemporalAttention沿时间轴 T 做自注意力,配合 LayerNorm 残差)与一个 GEGLU 前馈网络;
  4. proj_out投影回原通道数并与残差相加。

TemporalAttention采用绝对正弦位置编码sinusoidal_table生成的位置表在 QKV 投影之前加到输入上),注意力通过mx.fast.scaled_dot_product_attention计算(见 motion.py)。测试 test_temporal_module_zero_proj_is_identity 验证了一个有趣的属性:当proj_out权重为零时,时序模块退化为恒等映射。

DPT 头:多尺度特征融合

DPTHeadTemporal 从 DINOv2 的四个中间层取出特征,依次经过 1×1 投影、resize_layers(两个ConvTranspose2d上采样 + 一个Identity+ 一个步长 2 的Conv2d),再送入Scratch融合模块。融合过程中,motion(0)motion(1)分别在 layer_3、layer_4 路径上做时序建模,motion(2)motion(3)作用于 refinenet 路径——这正是时序一致性被注入多尺度空间特征的关键位置。输出头在**全精度(float32)**下运行(与参考实现一致),并支持micro_batch_size=4的分块执行以控制峰值内存。

滑窗推理的对齐细节

VideoDepthPredictor.infer完整复刻参考实现的关键超参(见 generate.py):

| 常量 | 值 | 含义 | |:-|:-|:-| |INFER_LEN| 32 | 每个滑窗的帧数 | |OVERLAP| 10 | 相邻窗口的重叠帧数 | |KEYFRAMES|[0, 12, 24, 25, 26, 27, 28, 29, 30, 31]| 关键帧索引,用于窗口间条件注入与对齐 | |INTERP_LEN| 8 | 重叠区线性插值帧数 |

推理流程为:按frame_step = INFER_LEN - OVERLAP = 22滑动;对于非首个窗口,用前一窗口的关键帧pre_input[:, KEYFRAMES])拼接当前窗口的后续帧作为模型输入(关键帧条件注入);每个窗口的深度图被双线性上采样回原始分辨率。全部窗口推理完后,通过最小二乘compute_scale_and_shift计算相邻窗口在共享关键帧上的尺度/偏移,先对重叠区做插值混合(get_interpolate_frames线性混合),再对后续帧施加同一组尺度/偏移并裁剪到非负(metric 模型使用恒等变换scale=1, shift=0)。最后np.stack(depth_list_aligned[:org_video_len])截断掉为凑齐窗口数而追加的尾部帧(见 generate.py)。

输入约束与数值精度说明

使用该模型时必须注意以下几点(原文档 Notes 部分的完整内容):

  • 张量布局:输入为通道在最后的(B, T, H, W, 3),即每个维度分别是 batch、时间帧数、高、宽、通道;
  • 尺寸约束:H 和 W 必须是 14 的倍数(对应patch_size = 14)。VideoDepthProcessortarget_size方法会保持宽高比并将尺寸吸附到 14 的倍数;当视频长宽比大于 1.78(约 16:9)时,会按input_size * 1.777 / ratio缩小输入尺寸以节省显存(见 processing_video_depth_anything.py);
  • 数值精度
    • 在默认的 GPU 设备上,输出与 PyTorch 参考实现相对误差约 1%(源于 Metal fast-math 矩阵乘法);
    • 在 CPU 上(mx.set_default_device(mx.cpu)),包括滑窗流水线在内的完整推理,相对误差约 1e-5;
  • 未移植功能:参考实现中的流式推理模式(video_depth_stream.py不在本移植范围内

源码级验证:单元测试与形状契约

仓库在 test_models.py 中为 Video Depth Anything 提供了完整的TestVideoDepthAnything测试套件,可帮助理解实现契约:

  • test_vision_backbone:验证 DINOv2 骨干按请求的层索引返回 patch/cls token,且形状正确;
  • test_model_forward_shapes:验证完整模型将(1, 4, H, W, 3)映射为(1, 4, H, W)深度图且输出非负;
  • test_sanitize_conv_layouts:验证Model.sanitize将 PyTorch 权重的(out, in, kh, kw)布局正确转置为 MLX 的通道在最后布局(ConvTranspose2d 例外地转置为(out, kh, kw, in),见 video_depth_anything.py);
  • test_processor_target_size:验证处理器保持宽高比、输出为 14 的倍数,且最短边不小于 294(即 518 的 14 倍数下限)。

小结与延伸阅读

Video Depth Anything 的 MLX 移植版将"视频深度估计"这一视觉任务完整地引入了 Apple Silicon 生态:无需转换即可从 Hub 加载权重,滑窗推理在 GPU 上与原版约 1% 相对误差、CPU 上约 1e-5 相对误差,且输出非负深度图。其实现复用了仓库中独立的 DINOv2 骨干,而时序一致性则由 motion.py 中的 AnimateDiff 风格时序模块提供。

想进一步了解本仓库如何组织模型实现,可参考 models 目录 的通用约定(模型类 + config + processing + generate 的模块划分);对 DINOv2 骨干细节感兴趣的读者可阅读 dinov2 模型文档。若需要将深度图用于下游任务(如 3D 重建、避障、视频编辑),可基于本文的VideoDepthPredictor.infer输出进行后处理——深度图分辨率与输入视频一致,可直接逐帧映射。

【免费下载链接】mlx-vlmMLX-VLM is a package for inference and fine-tuning of Vision Language Models (VLMs) on your Mac using MLX.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-vlm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询