Files
yolov26_3d/评测设计.md
2026-06-24 09:35:46 +08:00

13 KiB
Raw Permalink Blame History

analyze_val_two_roi_badcases.py 评测设计说明

本文描述当前脚本 tools/pdcl_inference/analyze_val_two_roi_badcases.py 的真实输出内容、评测口径和统计方式。相关实现还涉及:

  • tools/pdcl_inference/two_roi_inference.py
  • ultralytics/utils/plotting_3d.py
  • ultralytics/utils/metrics.py
  • ultralytics/utils/metrics_3d.py

1. 脚本定位

这个脚本不是训练期 validator 的简单包装,而是一个离线分析工具。它的目标是:

  • val/train/test split 做逐样本推理
  • 生成 per-ROI 的 2D/3D 指标汇总
  • 输出按类别、距离、框尺寸、可见面 bucket 分解后的统计
  • 导出坏例 CSV
  • 生成坏例可视化与 HTML 报告
  • 可选生成数据画像 portrait

默认会分析两个 ROI

  • roi0
  • roi1

2. 主流程

每个 ROI 的主流程如下:

  1. 读取 split 列表,得到每个样本的 label_path
  2. 根据 label 推导 image/calib 路径。
  3. 构造 ROI 图像和 ROI 标定。
  4. 跑模型,拿到:
    • 2D 检测结果
    • preds_3d
    • preds_edge
    • anchors
    • strides
  5. 同一张图会走两套预测用途:
    • 一套低阈值预测,用于 2D AP/PR/F1 曲线搜索
    • 一套正式阈值预测,用于 3D 匹配、坏例导出和报告
  6. 对 GT 与 Pred 做贪心匹配。
  7. 基于匹配结果生成 object-level record。
  8. 把 record 聚合进 overall / class / breakdown / interval 等 bucket。
  9. 结束后统一写出 CSV、JSON、Markdown、HTML 和可视化。

3. 匹配规则

3.1 主匹配规则

用于 3D 指标、坏例和 2D thresholded 指标的主匹配规则是:

  • 条件:IoU >= 0.5
  • 要求:类别一致
  • 策略:按 IoU 从高到低贪心匹配,保证一对一

对应函数:

  • greedy_match_indices(...)

3.2 2D AP 的 TP 计算

用于 mAP50mAP50-95 的 TP 计算是标准 COCO 风格:

  • IoU 阈值:0.50:0.95,步长 0.05
  • 每个阈值单独做一次贪心匹配
  • 然后调用 ap_per_class(...)

对应函数:

  • compute_2d_tp_matrix(...)
  • summarize_2d_ap_store(...)

3.3 focused confusion 的匹配

focused confusion matrix 用的是“任意类别 IoU 匹配”:

  • 先只按 IoU >= 0.5 做 greedy match
  • 再把“预测类别 vs GT 类别”记到 confusion matrix

这么做的目的不是算检测 AP而是专门看“已经局部对上目标之后的分类混淆”。

对应函数:

  • update_subset_confusion_matrix(...)

4. 2D 指标设计

4.1 AP / mAP

脚本会先以很低阈值收集候选框:

  • threshold_search_conf = min(bundle.spec.conf, 0.001)

然后计算:

  • map50_2d
  • map50_95_2d
  • 每类的 map50/map50_95
  • PR/F1 曲线

同时还会从 mean F1 curve 里挑一个推荐阈值:

  • recommended_confidence_2d

其定义是:

  • 对所有类别的 F1 曲线取均值
  • 先做 smooth(..., 0.1)
  • 取最大值对应的 confidence

4.2 thresholded 2D 指标

recommended_confidence_2d 下,脚本会再算一套 thresholded 2D 指标:

  • gt_total
  • pred_total
  • matched_2d
  • precision_2d = matched_2d / pred_total
  • recall_2d = matched_2d / gt_total
  • f1_2d
  • false_negatives_2d = gt_total - matched_2d
  • false_positives_2d = pred_total - matched_2d

这些会出现在:

  • overall summary
  • class_metrics.csv
  • summary.json

4.3 2D 分类准确率

脚本还会统计“已经匹配上的检测对里,类别判对了多少”:

  • classification_summary_2d
  • focused_classification_summary_2d

核心口径是 confusion matrix 的主对角占比:

  • overall_accuracy = trace(matched_matrix) / sum(matched_matrix)

每类还会有:

  • cls_eval_pairs_2d
  • cls_correct_2d
  • cls_acc_2d

4.4 focused confusion 子集

脚本还会单独做一个“近距离、无遮挡、低难度”的 focused confusion

  • GT 条件:
    • difficulty == 0
    • |lateral| < 5m
    • |longitudinal| < 30mroi0
    • |longitudinal| < 80mroi1
  • Pred 条件:
    • 用预测 3D 中心估计空间位置
    • 满足相同的空间范围约束

这一部分主要用于看容易样本的分类混淆情况。

5. 3D object-level record 设计

每个 GT-Pred 匹配对,脚本都会尝试构造一个 record。record 是后续所有 3D 聚合统计的基础。

5.1 基本字段

包含但不限于:

  • 样本标识:sample_index, roi, frame_name
  • GT/Pred 索引:gt_index, pred_index
  • 类别:cls_id, cls_name
  • 2D 匹配:confidence, match_iou
  • 3D 属性:
    • gt_x_m / pred_x_m
    • gt_y_m / pred_y_m
    • gt_z_m / pred_z_m
    • gt_depth_m / pred_depth_m
    • gt_yaw_deg / pred_yaw_deg
    • x_abs_m / y_abs_m / z_abs_m
    • center_error_m
    • yaw_abs_deg

5.2 yaw 误差定义

当前脚本的 yaw 误差不是普通 |pred - gt|,而是 pi 周期误差

  • 先做 [-pi, pi) wrap
  • 再取 min(diff, |pi - diff|)

含义是:

  • 180 度前后翻转不被当成大错
  • 更接近“朝向轴”误差,而不是“严格方向”误差

对应函数:

  • angle_abs_deg(...)

这点非常重要,读报告时必须按这个口径理解所有 yaw 指标。

5.3 position_eligible

不是所有 matched_3d 都会进入位置误差统计。当前实现里:

  • position_eligible = (cls in complete_3d_classes) or (not cut_object)

也就是说:

  • 完整 3D 类,总是参与位置误差
  • face-3D 类如果是 cut object则不参与位置误差

因此:

  • matched_3d 是形状/yaw层面的 3D 成功样本数
  • matched_pos 才是位置误差真正参与统计的样本数

5.4 position_error_basis

位置误差并不总是用 box center 计算。

当前实现优先级是:

  1. 对 face_3d 类,如果 GT 和 Pred 都能解析到同一个可见 face则使用该 face_center
  2. 否则退回 box_center

因此 record 中会有:

  • position_error_basis = face_centerbox_center

这会影响:

  • x_abs_m
  • y_abs_m
  • z_abs_m
  • center_error_m

5.5 yaw compare 相关字段

脚本会同时保存三类 yaw

  1. pred_yaw
    • 来自 41 维 whole 头的直接 yaw 回归
  2. pred_edge_visible_yaw
    • 来自 edge 几何重建
    • 只有 edge_confident=True 时才记为有效
  3. pred_edge_bucket_visible_yaw
    • 记录当前 edge 选择器给出的 yaw
    • 即使它不满足“正式 edge 有效条件”,也会保留,用于 bucket 诊断

同时会记录:

  • direct_visible_yaw_abs_deg
  • edge_visible_yaw_abs_deg
  • direct_minus_edge_visible_yaw_abs_deg

5.6 yaw_compare_eligible

只有满足下列条件,样本才被认为“允许进入 yaw compare 范围”:

  • |gt_x_m| < yaw_compare_max_lateral_dist_m

默认 lateral limit 很大,来自:

  • DEFAULT_YAW_COMPARE_MAX_LATERAL_DIST_M = 30m

但真正进入 yaw_compare_count 的条件更严格,还需要:

  • direct yaw 有效
  • edge yaw 有效

也就是:

  • yaw_compare_eligible 只是“允许参与”
  • yaw_compare_count 才是“真的成对比较了 direct vs edge”

6. bucket 聚合指标

所有 overall/class/breakdown/lateral-bin 等汇总,最终都通过 summarize_metric_bucket(...) 生成。

6.1 计数项

  • gt_total
  • pred_total
  • matched_2d
  • matched_3d
  • matched_pos
  • yaw_compare_eligible_count
  • yaw_compare_count
  • length_compare_count

6.2 2D 派生项

  • precision_2d = matched_2d / pred_total
  • recall_2d = matched_2d / gt_total
  • f1_2d

6.3 3D 主指标

  • mean_confidence
  • mean_match_iou
  • yaw_mae_deg
  • yaw_p90_deg
  • x_abs_mae_m
  • y_abs_mae_m
  • z_abs_mae_m
  • center_error_mae_m

6.4 direct vs edge 对比指标

  • direct_regression_yaw_mae_deg
  • direct_regression_yaw_p90_deg
  • edge_based_yaw_mae_deg
  • edge_based_yaw_p90_deg
  • mean_direct_minus_edge_yaw_deg
  • median_direct_minus_edge_yaw_deg
  • yaw_compare_edge_better_count/rate
  • yaw_compare_direct_better_count/rate
  • yaw_compare_tie_count/rate

脚本的判优规则是:

  • edge_err + eps < direct_err 记为 edge better
  • direct_err + eps < edge_err 记为 direct better
  • 否则 tie

6.5 长度对比指标

当前还统计了 direct box 与 edge box 的车长误差对比:

  • direct_regression_length_mae_m
  • side_edge_length_mae_m
  • mean_direct_minus_side_edge_length_m
  • length_compare_edge_better_count/rate
  • length_compare_direct_better_count/rate
  • length_compare_tie_count/rate

6.6 超阈值坏例占比

  • yaw_bad_rate = yaw_bad_count / matched_3d
  • horizontal_bad_rate = x_bad_count / matched_pos
  • vertical_bad_rate = z_bad_count / matched_pos

默认阈值:

  • yaw5 deg
  • horizontal0.5 m
  • vertical0.5 m

6.7 当前实现的命名注意事项

这里有一个很容易误解的点:

  • vertical_* 相关统计,当前实现实际上是基于 z_abs_m 做的,也就是纵向/深度误差
  • 不是基于 y_abs_m

具体来说:

  • bad_cases_vertical.csv 的导出条件是 z_abs_m > vertical_bad_threshold_m
  • vertical_abs_mae_m 实际对应 mean(z_abs_m)
  • class_vertical_depth_5m.csv 也是按 position_gt_z_m 分桶

另外:

  • y_abs_m 会被记录
  • y_bad_count 当前实现没有真正累加,因此 y_bad_rate 基本恒为 0 或无实际含义

所以看报告时,建议把:

  • horizontal 理解为 x
  • vertical 理解为 z/depth

7. breakdown 与分桶统计

7.1 breakdowns.json

脚本会输出 breakdowns.json,当前有 5 类 breakdown

  1. cut_status
    • cut
    • non_cut
    • n/a
  2. face_visibility
    • front_rear_only
    • side only
    • two-face
  3. distance_bin
    • <20m
    • 20-40m
    • 40-60m
    • >=60m
  4. bbox_diag_bin
    • <32px
    • 32-64px
    • 64-128px
    • >=128px
  5. class_group
    • face_3d
    • complete_3d
    • 2d_only

7.2 interval CSV

脚本还会输出几类按区间统计的 CSV

class_horizontal_lateral_5m.csv

  • 横轴GT 的 position_gt_x_m
  • 分桶:[-30m, 30m) 内 5m 一桶
  • 指标:x_abs_m
  • 额外输出:
    • mean
    • median
    • p90
    • max
    • rate_gt_0p5

class_vertical_depth_5m.csv

  • 横轴GT 的 position_gt_z_m
  • 分桶5m 一桶
  • 指标:z_abs_m

class_yaw_depth_10m.csv

  • 横轴GT 的 gt_depth_m
  • 分桶10m 一桶
  • 指标:yaw_abs_deg

class_yaw_horizontal_5m.csv

  • 横轴GT 的 gt_x_m
  • 分桶:[-30m, 30m) 内 5m 一桶
  • 指标:yaw_abs_deg

yaw_compare_signed_lateral_5m.csv

这是专门给 direct vs edge yaw 对比用的:

  • 横轴GT 的 gt_x_m
  • 范围:[-yaw_compare_max_lateral_dist_m, yaw_compare_max_lateral_dist_m)
  • 分桶5m
  • 额外还有一个纵向过滤条件:
    • 0 <= gt_z_m < 50m

桶内不是简单均值列表,而是整套 summarize_metric_bucket(...) 输出。

yaw_compare_signed_lateral_5m_by_face_visibility.csv

与上面类似,但再按 face_visibility_bucket 细分:

  • front_rear_only
  • side only
  • two-face

7.3 large vehicle 专项统计

脚本对大车类单独做了一个 two-face yaw 对比视角:

  • 类别集合:5, 6, 7
  • 即:
    • bus
    • truck/tanker/large_truck/construction_vehicle
    • special_vehicle

输出在:

  • summary.jsonlarge_vehicle_compare

8. 坏例导出规则

8.1 CSV 全量导出

脚本会把所有超阈值样本写入 CSV

  • bad_cases_yaw.csv
    • 条件:yaw_abs_deg >= yaw_bad_threshold_deg
  • bad_cases_horizontal.csv
    • 条件:position_eligible and x_abs_m > horizontal_bad_threshold_m
  • bad_cases_vertical.csv
    • 条件:position_eligible and z_abs_m > vertical_bad_threshold_m
  • bad_cases_2d_false_negative.csv
  • bad_cases_2d_false_positive.csv

8.2 可视化导出

可视化不是把所有坏例都画出来,而是做 reservoir sampling

  • overall top-k
  • per-class top-k
  • per-error-bin top-k

其中按误差区间导出时:

  • yaw1 deg 一桶
  • horizontal/vertical0.5 m 一桶
  • 每桶先保留最多 error_bin_badcases
  • 再按 frame 聚合,最终每桶最多导出 error_bin_samples_per_bin 张图

9. 输出文件组织

9.1 每个 ROI 目录

每个 ROI 目录下会有:

  • summary.json
  • summary.md
  • summary_zh.md
  • class_metrics.csv
  • breakdowns.json
  • class_horizontal_lateral_5m.csv
  • class_vertical_depth_5m.csv
  • class_yaw_depth_10m.csv
  • class_yaw_horizontal_5m.csv
  • yaw_compare_signed_lateral_5m.csv
  • yaw_compare_signed_lateral_5m_by_face_visibility.csv
  • bad_cases_yaw.csv
  • bad_cases_horizontal.csv
  • bad_cases_vertical.csv
  • bad_cases_2d_false_negative.csv
  • bad_cases_2d_false_positive.csv
  • confusion_matrix_normalized.png
  • 2d_confidence_curves/*.png
  • visuals/...

9.2 总输出目录

总输出目录会汇总所有 ROI写出

  • summary.json
  • summary.md
  • summary_zh.md
  • report.html

如果启用了 data portrait还会额外有

  • <split>_portrait/summary.json
  • portrait 相关 CSV

10. 一句话总结

analyze_val_two_roi_badcases.py 当前实现的核心是:

  • IoU>=0.5 + 类别一致 的贪心匹配构造 object-level 记录
  • 以 record 为中心,统一聚合 2D、3D、direct-vs-edge、分桶和坏例统计
  • 2D AP 与 thresholded 2D 指标分开算
  • yaw 误差采用 pi 周期口径
  • 报告里名为 vertical 的主统计,当前实际上对应 z/depth 误差

如果后续要和训练期 validator 对齐,最值得先确认的就是:

  • yaw 误差口径是否仍然保持 pi 周期
  • vertical 是否继续表示 z
  • position_eligible 是否继续排除 cut 的 face-3D 样本