13 KiB
analyze_val_two_roi_badcases.py 评测设计说明
本文描述当前脚本 tools/pdcl_inference/analyze_val_two_roi_badcases.py 的真实输出内容、评测口径和统计方式。相关实现还涉及:
tools/pdcl_inference/two_roi_inference.pyultralytics/utils/plotting_3d.pyultralytics/utils/metrics.pyultralytics/utils/metrics_3d.py
1. 脚本定位
这个脚本不是训练期 validator 的简单包装,而是一个离线分析工具。它的目标是:
- 对
val/train/testsplit 做逐样本推理 - 生成 per-ROI 的 2D/3D 指标汇总
- 输出按类别、距离、框尺寸、可见面 bucket 分解后的统计
- 导出坏例 CSV
- 生成坏例可视化与 HTML 报告
- 可选生成数据画像
portrait
默认会分析两个 ROI:
roi0roi1
2. 主流程
每个 ROI 的主流程如下:
- 读取 split 列表,得到每个样本的
label_path。 - 根据 label 推导 image/calib 路径。
- 构造 ROI 图像和 ROI 标定。
- 跑模型,拿到:
- 2D 检测结果
preds_3dpreds_edgeanchorsstrides
- 同一张图会走两套预测用途:
- 一套低阈值预测,用于 2D AP/PR/F1 曲线搜索
- 一套正式阈值预测,用于 3D 匹配、坏例导出和报告
- 对 GT 与 Pred 做贪心匹配。
- 基于匹配结果生成 object-level record。
- 把 record 聚合进 overall / class / breakdown / interval 等 bucket。
- 结束后统一写出 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_2dmap50_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_totalpred_totalmatched_2dprecision_2d = matched_2d / pred_totalrecall_2d = matched_2d / gt_totalf1_2dfalse_negatives_2d = gt_total - matched_2dfalse_positives_2d = pred_total - matched_2d
这些会出现在:
- overall summary
class_metrics.csvsummary.json
4.3 2D 分类准确率
脚本还会统计“已经匹配上的检测对里,类别判对了多少”:
classification_summary_2dfocused_classification_summary_2d
核心口径是 confusion matrix 的主对角占比:
overall_accuracy = trace(matched_matrix) / sum(matched_matrix)
每类还会有:
cls_eval_pairs_2dcls_correct_2dcls_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_mgt_y_m / pred_y_mgt_z_m / pred_z_mgt_depth_m / pred_depth_mgt_yaw_deg / pred_yaw_degx_abs_m / y_abs_m / z_abs_mcenter_error_myaw_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 计算。
当前实现优先级是:
- 对 face_3d 类,如果 GT 和 Pred 都能解析到同一个可见 face,则使用该
face_center - 否则退回
box_center
因此 record 中会有:
position_error_basis = face_center或box_center
这会影响:
x_abs_my_abs_mz_abs_mcenter_error_m
5.5 yaw compare 相关字段
脚本会同时保存三类 yaw:
pred_yaw- 来自 41 维 whole 头的直接 yaw 回归
pred_edge_visible_yaw- 来自 edge 几何重建
- 只有
edge_confident=True时才记为有效
pred_edge_bucket_visible_yaw- 记录当前 edge 选择器给出的 yaw
- 即使它不满足“正式 edge 有效条件”,也会保留,用于 bucket 诊断
同时会记录:
direct_visible_yaw_abs_degedge_visible_yaw_abs_degdirect_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_totalpred_totalmatched_2dmatched_3dmatched_posyaw_compare_eligible_countyaw_compare_countlength_compare_count
6.2 2D 派生项
precision_2d = matched_2d / pred_totalrecall_2d = matched_2d / gt_totalf1_2d
6.3 3D 主指标
mean_confidencemean_match_iouyaw_mae_degyaw_p90_degx_abs_mae_my_abs_mae_mz_abs_mae_mcenter_error_mae_m
6.4 direct vs edge 对比指标
direct_regression_yaw_mae_degdirect_regression_yaw_p90_degedge_based_yaw_mae_degedge_based_yaw_p90_degmean_direct_minus_edge_yaw_degmedian_direct_minus_edge_yaw_degyaw_compare_edge_better_count/rateyaw_compare_direct_better_count/rateyaw_compare_tie_count/rate
脚本的判优规则是:
edge_err + eps < direct_err记为 edge betterdirect_err + eps < edge_err记为 direct better- 否则 tie
6.5 长度对比指标
当前还统计了 direct box 与 edge box 的车长误差对比:
direct_regression_length_mae_mside_edge_length_mae_mmean_direct_minus_side_edge_length_mlength_compare_edge_better_count/ratelength_compare_direct_better_count/ratelength_compare_tie_count/rate
6.6 超阈值坏例占比
yaw_bad_rate = yaw_bad_count / matched_3dhorizontal_bad_rate = x_bad_count / matched_posvertical_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_mvertical_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理解为xvertical理解为z/depth
7. breakdown 与分桶统计
7.1 breakdowns.json
脚本会输出 breakdowns.json,当前有 5 类 breakdown:
cut_statuscutnon_cutn/a
face_visibilityfront_rear_onlyside onlytwo-face
distance_bin<20m20-40m40-60m>=60m
bbox_diag_bin<32px32-64px64-128px>=128px
class_groupface_3dcomplete_3d2d_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_onlyside onlytwo-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.csvbad_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.jsonsummary.mdsummary_zh.mdclass_metrics.csvbreakdowns.jsonclass_horizontal_lateral_5m.csvclass_vertical_depth_5m.csvclass_yaw_depth_10m.csvclass_yaw_horizontal_5m.csvyaw_compare_signed_lateral_5m.csvyaw_compare_signed_lateral_5m_by_face_visibility.csvbad_cases_yaw.csvbad_cases_horizontal.csvbad_cases_vertical.csvbad_cases_2d_false_negative.csvbad_cases_2d_false_positive.csvconfusion_matrix_normalized.png2d_confidence_curves/*.pngvisuals/...
9.2 总输出目录
总输出目录会汇总所有 ROI,写出:
summary.jsonsummary.mdsummary_zh.mdreport.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是否继续表示zposition_eligible是否继续排除 cut 的 face-3D 样本