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

556 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `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 计算
用于 `mAP50``mAP50-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| < 30m``roi0`
- `|longitudinal| < 80m``roi1`
- 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_center``box_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`
默认阈值:
- yaw`5 deg`
- horizontal`0.5 m`
- vertical`0.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.json``large_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
其中按误差区间导出时:
- yaw`1 deg` 一桶
- horizontal/vertical`0.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 样本