ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Ultralytics YOLO DepthValidator 源码全解:深度估计验证流程与指标体系剖析

2026/9/8 18:38:57 拓冰建站 浏览量
Ultralytics YOLO DepthValidator 源码全解:深度估计验证流程与指标体系剖析 Ultralytics YOLO DepthValidator 源码全解深度估计验证流程与指标体系剖析【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics单目深度估计任务在 Ultralytics YOLO 中通过DepthValidator完成验证评估。它从DetectionValidator继承而来却彻底去掉了 NMS 与框类统计改以 monocular depth 领域通行的 Eigen 评测协议对逐像素深度图打分。本文以 DepthValidator API 参考页 为主题逐方法解读其在 val.py 中的实现并延伸到底层 DepthMetrics 与 数据集 YAML帮助你完整掌握YOLO 深度模型如何被验证、六个深度指标如何计算、DDP 下如何汇总、验证结果如何落盘。1. DepthValidator 在 YOLO 任务体系中的位置深度估计与检测、分割、姿态、OBB 等并列是 YOLO 的原生任务之一。任务级的路由表定义在 model.py其中depth映射到四件套depth: { model: DepthModel, trainer: yolo.depth.DepthTrainer, validator: yolo.depth.DepthValidator, predictor: yolo.depth.DepthPredictor, },DepthValidator由 depth/init.py 统一导出__all__ DepthPredictor, DepthTrainer, DepthValidator。当你执行model.val(data...)或yolo depth val ...时框架会根据模型的任务类型自动实例化本类因此理解深度验证只需读懂这一个类。它的类文档言简意赅地点出了设计取向Validator for YOLO depth estimation models. Computes standard depth metrics: delta1, abs_rel, rmse, silog. Uses validation loss as the primary training signal.也就是说深度验证不产出 mAP它的好坏由 delta1 / abs_rel / rmse / silog 等深度指标来衡量训练阶段则以验证 loss 作为主监控信号。2. 构造与初始化init与 init_metrics2.1 继承 DetectionValidator 并标记任务class DepthValidator(DetectionValidator): def __init__(self, dataloaderNone, save_dirNone, argsNone, _callbacksNone): super().__init__(dataloader, save_dir, args, _callbacks) self.args.task depth构造器把参数原样交给父类随后强制把self.args.task置为depth。这一步很关键评测时的进度条、结果表头、绘图文件命名、模型出口解析等都依赖 task 标识分发到 depth 专属逻辑。2.2 用数据集深度上限初始化指标累加器def init_metrics(self, model): self.metrics DepthMetrics(max_depthself.data.get(max_depth) or 100.0) self.metrics.clear_stats()每次验证开始时依据数据集 YAML 里的max_depth字段实例化DepthMetricsYAML 未声明时回退到默认值100.0米。随后clear_stats()清零所有累加器保证model.val()可以重复调用而不受上一次结果污染。max_depth同时也是评测协议的有效像素边界在数据集配置中按场景调整。以 depth8.yaml 为例path: depth8-png train: images/train val: images/val nc: 1 names: 0: depth channels: 3 depth_scale: 1000 # PNG value 1000 1 meter其头部注释还给出了各数据集常见约定ARKitScenes 与 NYU Depth V2 用depth_scale: 10001 mm 精度、上限 65.535 mKITTI 用256Virtual KITTI 2 用100。uint16 PNG 数值除以depth_scale才得到米制深度其中编码0表示无效像素。3. 一次验证迭代的数据流preprocess → postprocess → update_metrics3.1 preprocess深度图保持 float32def preprocess(self, batch): batch super().preprocess(batch) # 移入设备 RGB 图像归一化 batch[depth] batch[depth].float() return batch父类负责把图像搬到目标设备并做归一化深度分支在此基础上把真值深度图统一转成float32覆盖部分 loader 输出 uint16/int 的情况保证后续算术的数值行为一致。训练侧 train.py 的preprocess_batch采用了完全相同的深度转 float32约定验证与训练的数据路径保持一致。3.2 postprocess不做 NMS直接返回def postprocess(self, preds): return preds与检测任务不同深度输出是一整张稠密(B,1,H,W)图而非稀疏框因此不存在非极大值抑制。postprocess只是透传模型原始输出——这也是深度任务省掉整个 NMS 链路的源码证据。3.3 update_metrics尺寸对齐后逐图像累计def update_metrics(self, preds, batch): gt_depth batch[depth] if gt_depth.ndim 3: gt_depth gt_depth.unsqueeze(1) if preds.ndim 3: preds preds.unsqueeze(1) if preds.shape[-2:] ! gt_depth.shape[-2:]: preds F.interpolate(preds.float(), sizegt_depth.shape[-2:], modebilinear, align_cornersTrue) self.metrics.update_stats(preds, gt_depth)这里处理两类现实差异通道维度缺失网络或 GT 可能以(B,H,W)输出/存储此处统一unsqueeze(1)成(B,1,H,W)空间尺寸不一致当预测分辨率与真值分辨率不同常见于 val 时imgsz与 GT 尺寸不完全对齐用双线性插值把预测图 resize 到 GT 尺寸align_cornersTrue使角点像素对齐。之后才真正把累计工作交给DepthMetrics.update_stats()。4. 深度指标体系DepthMetrics 源码级拆解指标类定义在 metrics.py 的DepthMetrics第 1892 行起。其类文档点出了几个设计基准指标逐图像先归一、再对验证集平均使每张图权重相同与 Depth Anything V2 / Monodepth2 的 per-sample 平均一致有效 GT 像素少于 10 的图像被整体跳过非有限预测按深度界处理而非剔除该图。4.1 构造参数与累加器def __init__(self, min_depth0.001, max_depth100.0, alignmedian): self.min_depth min_depth self.max_depth max_depth self.align align self.speed {preprocess: 0.0, inference: 0.0, loss: 0.0, postprocess: 0.0} self._totals None self._count 0.0 self._results {}三个核心超参参数默认值语义min_depth0.001最小有效深度米GT 像素 min_depth被忽略max_depth100.0最大有效深度米GT 像素 max_depth被忽略预测值被裁剪到此上限alignmedian逐图像尺度对齐median按median(gt)/median(pred)缩放预测none关闭对齐、按原始输出尺度打分累计器设计刻意选了CPU 上的 float64注释说明 MPS 张量无法承载 float64因此后续 DDP 归约只需先求和、再 all-reduce即可无需精度妥协。4.2 update_statsEigen 协议 逐图 median 对齐def update_stats(self, preds, targets): p preds.squeeze(1) if preds.ndim 4 else preds g targets.squeeze(1) if targets.ndim 4 else targets if p.ndim 2: p, g p[None], g[None] # 单图 (H,W) → (1,H,W)保证对齐始终逐图进行 for pi, gi in zip(p, g): mask (gi self.min_depth) (gi self.max_depth) # Eigen 有效像素 if int(mask.sum()) 10: # 少于 10 个有效像素则跳过 continue pv pi[mask].float() gv gi[mask].float() if self.align median: finite torch.isfinite(pv) if finite.any(): scale torch.median(gv[finite]) / torch.median(pv[finite].clamp_min(self.min_depth)) pv pv * scale pv torch.nan_to_num(pv, nanself.max_depth, posinfself.max_depth, neginfself.min_depth ).clamp(self.min_depth, self.max_depth) thresh torch.maximum(pv / gv, gv / pv) log_diff torch.log(pv) - torch.log(gv) silog (log_diff.pow(2).mean() - log_diff.mean().pow(2)).clamp_min(0.0).sqrt() * 100 image_metrics torch.stack([ (thresh 1.25).float().mean(), # delta1 (thresh 1.25 ** 2).float().mean(), # delta2 (thresh 1.25 ** 3).float().mean(), # delta3 (torch.abs(pv - gv) / gv).mean(), # abs_rel ((pv - gv) ** 2).mean().sqrt(), # rmse silog, ]) ... self._totals image_metrics.cpu().double() self._count 1.0逐行可以读出完整的评测约定Eigen 掩码只有 GT 落在(min_depth, max_depth)开区间内的像素才参与打分区间外像素既不当作误差也不拉低得分10 像素下限有效像素不足 10 的图像直接continue跳过——对极稀疏 GT 做 median 对齐没有意义与 Depth Anything V2 的下限一致median 对齐对 non-finite 外的像素求scale median(gt) / median(pred)分母先clamp_min(min_depth)防除零再整图缩放。这让尺度模糊affine-invariant的相对深度输出也能与米制 GT 公平比较非有限值兜底nan_to_num把 NaN/正无穷记为max_depth、负无穷记为min_depth再裁剪进[min_depth, max_depth]避免单点脏数据毁掉整张图的平均阈值判定thresh max(p/g, g/p)保证误差在两个方向上对称thresh 1.25、1.25²、1.25³分别对应 delta1/2/3 的像素占比silog 公式采用 λ1 的方差形式ZoeDepth/KITTI 风格——sqrt(E[log²] - E[log]²) × 100即 log 域残差的裁剪后非负标准差。由于在单图上先完成再累计silog 也遵循逐样本平均逐图入账image_metrics先求整图均值、再累加进 float64 的_totals_count每图 1——最终取平均时每张图权重相等与每张图有效像素多少无关。4.3 process / keys / fitness六个指标的定义与输出process()用_totals / _count归一后产出带命名空间的字典metrics.py 第 1983-1990 行self._results { metrics/delta1: d1, metrics/delta2: d2, metrics/delta3: d3, metrics/abs_rel: abs_rel, metrics/rmse: rmse, metrics/silog: silog, }对应语义与方向指标含义方向delta1/2/3预测与真值之比双向落在 1.25、1.25²、1.25³ 内的像素占比越高越好abs_rel平均绝对相对误差mean(|p−g|/g)越低越好rmse均方根误差单位米越低越好silog尺度不变对数误差×100越低说明相对结构越一致越低越好DepthMetrics.keys1999-2008 行给出了日志顺序fitness属性2044-2047 行把delta1作为整体适应度higher is better用于超参搜索与 checkpoint 比较curves/curves_results返回空列表——深度任务没有 PR 曲线。summary()2064 行起可将结果压成单行字典便于写入 CSV / JSON。5. 结果汇总get_stats、gather_stats 与 DDP 跨卡归约5.1 get_stats只在 rank 0 上做最终归一def get_stats(self): self.metrics.process() return self.metrics.results_dict注释明确跨 rank 的指标归约由gather_stats()负责它会在所有 rank 上先执行get_stats()只在 rank 0 上运行此时累加器已是全局求和结果。5.2 gather_stats重写父类仅归约深度所需的标量DetectionValidator.gather_stats()会归约检测专用的统计/框属性而DepthMetrics根本没有这些成员。因此DepthValidator必须覆写只 all-reduce 两样东西def gather_stats(self): if RANK -1 or not dist.is_initialized(): return totals self.metrics._totals totals (totals.to(self.device) if totals is not None else torch.zeros(6, dtypetorch.float64, deviceself.device)) count torch.tensor([self.metrics._count], dtypetorch.float64, deviceself.device) dist.all_reduce(totals, opdist.ReduceOp.SUM) dist.all_reduce(count, opdist.ReduceOp.SUM) self.metrics._totals totals self.metrics._count float(count.item())原理如下DDP 验证时数据被ContiguousDistributedSampler分片每个 rank 只持有自己那一份的求和统计。all_reduce(SUM)后rank 0 上得到整个验证集的总和与总图像数随后get_stats()求平均即可得到全量指标——而不是单个分片的近似值。单卡场景RANK -1或分布式未初始化则直接跳过归约。6. 结果输出与可视化print_results、get_desc、plot_predictions6.1 对齐检测风格的日志表格get_desc()与print_results()共同决定了验证结束时的终端输出格式。get_desc()定义表头(%22s %11s * 5) % (Class, Images, delta1, abs_rel, rmse, silog)print_results()用同一套宽度拼行行标签为depth_val深度没有类别概念故不像检测那样打allpf %22s %11i %11.4g * 4 LOGGER.info(pf % (depth_val, n_images, r.get(metrics/delta1, 0.0), r.get(metrics/abs_rel, 0.0), r.get(metrics/rmse, 0.0), r.get(metrics/silog, 0.0)))因此你在验证日志中看到的最终一行大致形如depth_val | 654 | 0.8602 | 0.0761 | 0.3125 | ...各列与第 4 节的指标一一对应。6.2 finalize_metrics回填速度与保存目录def finalize_metrics(self): self.metrics.speed self.speed self.metrics.save_dir self.save_dir把验证阶段统计的 preprocess/inference/postprocess 耗时写回DepthMetrics.speed其 4 个键的初始值见 4.1同时让指标对象知道可视化结果要写到哪个目录。6.3 plot_predictions热力图叠层而非画框def plot_predictions(self, batch, preds, ni): plot_images( labels{depth: preds}, imagesbatch[img], pathsbatch[im_file], fnameself.save_dir / fval_batch{ni}_pred.jpg, namesself.names, on_plotself.on_plot, )深度没有框与类别检测版的画框可视化不可用因此这里复用共享的plot_images路径把预测深度图作为labels{depth: preds}传入生成val_batch{ni}_pred.jpg热力图叠层——与语义分割的可视化风格一致。finalize_metrics中记录的save_dir正是这些 jpg 的落盘目录。7. 实战如何触发与读取一次深度验证验证器在训练过程中由 train.py 的DepthTrainer.get_validator()自动创建并注入使用测试集 loader、训练保存目录与当前 args。训练结束后final_eval()还会对 best/last checkpoint 做一次基于验证集的 log-affine 尺度校准calibrate_checkpoint使保存的权重开箱即输出米制尺度的深度。独立验证则走valmode官方推荐在训练分辨率imgsz768下进行# 验证官方预训练权重Eigen 测试切分 yolo depth val modelyolo26n-depth.pt datanyu-depth.yaml imgsz768 # 验证自己训练的 checkpoint yolo depth val modelpath/to/best.pt datapath/to/data.yamlfrom ultralytics import YOLO model YOLO(yolo26n-depth.pt) # 或 path/to/best.pt metrics model.val(datanyu-depth.yaml, imgsz768) print(metrics.delta1) # 像素级 δ1.25 阈值内占比越高越好 print(metrics.delta2, metrics.delta3) print(metrics.abs_rel) # 越低越好 print(metrics.rmse) # 米制均方根误差 print(metrics.silog) # 尺度不变对数误差验证命令自动选择DepthValidator的注册依据是 checkpoint 元数据中的任务标记对应 model.py 的depth路由与-depth模型文件后缀约定。data必须显式指向评测用数据集 YAML——它的max_depth、depth_scale直接决定哪些 GT 像素计入指标以及米制换算是否正确。8. 小结一图读懂深度验证的完整闭环回顾整个流程DepthValidator的每个覆写点都对应一个明确的工程决策方法覆写动机关键行为__init__固化任务标识args.task depthinit_metrics接入深度专属累加器DepthMetrics(max_depthdata.max_depth or 100)preprocess数据类型统一depth 转float32postprocess无后处理需求直接返回稠密预测update_metrics稠密图而非框双线性插值对齐后逐图累计gather_stats父类归约不适配仅 all-reduce 6 维 totals 与 countget_stats结果字典化process()后返回metrics/results_dictget_desc/print_results表格列适配depth_val行 delta1/abs_rel/rmse/silog 列plot_predictions无框可视化深度热力图叠层val_batch{ni}_pred.jpg从数据流看一次验证是loader 产出 (img, depth)→preprocess归一化 → 前向推理得到(B,1,H,W)预测 →update_metrics插值对齐 →DepthMetrics逐图做 Eigen 掩码 median 对齐 六指标入账 → DDPgather_stats全局求和 → rank 0get_stats平均 → 表格日志 热力图落盘 的完整闭环。无论你是要复现论文中的 delta1/abs_rel还是排查自定义数据集上验证分数偏低先检查depth_scale与max_depth是否与数据约定一致val.py 与 metrics.py 中的这段源码都是理解 YOLO 深度验证行为最直接、最权威的入口。配套可进一步阅读 单目深度估计任务指南 与 深度数据集格式说明以掌握数据集侧 YAML 字段与评测协议的完整对应关系。【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考