Files
yolov26_3d/eval_tools/docs/VEHICLE_3D_METRICS_BUG_ANALYSIS.md
2026-06-24 09:35:46 +08:00

7.0 KiB
Executable File
Raw Permalink Blame History

车辆 3D 指标异常分析与修复

问题发现日期2026-03-09
涉及文件eval_tools/evaluator/metrics_3d.pyeval_tools/evaluator/evaluator.py


一、问题现象

在 deeplearning vs deploy 模型对比评测CNCAP 数据集ROI1中发现以下异常

指标 deeplearning deploy 相对差异
车辆纵向相对误差Overall 0.1209 0.0711 -41.20%

然而,将 overall 分解为各纵向分段后,两模型指标几乎相同(差异 < 1%

区间 LongRel (dl) LongRel (dep) 差异
0-10m 0.2883 0.2842 -1.41%
10-20m 0.0328 0.0328 -0.07%
20-30m 0.0408 0.0410 +0.27%

典型特征overall 与各分段差异巨大,但各分段两模型差距极小。


二、问题根因:三个 Bug

Bug 1主因面中心 z 坐标用于相对误差计算和分段路由

位置metrics_3d.py::add_sample()evaluator.py::_process_frame_3d()

问题:对于车辆,gt_center 使用的是 GT 面中心坐标front/back/left/right face。在计算纵向相对误差和分段路由时直接用该 z 值:

# 修复前
longitudinal_distance = gt_center[2]          # 面中心 z可能趋近 0 或为负)
gt_depth = max(abs(gt_center[2]), 1e-6)       # 面中心 z 作为分母
longitudinal_relative_error = longitudinal_error / gt_depth

后果

  • 近距离侧面/后面标注的面中心 z 可能 ≤ 0面中心在相机平面附近甚至后方
  • face_z ≈ 0 时,gt_depth = 1e-6longitudinal_relative_error 爆炸(可达数万)
  • face_z ≤ 0 时,_get_longitudinal_distance_range() 返回 None,样本不进任何 long_* 分段,但仍留在 overall

量化影响(修复前):

模型 overall 样本数 long_* 总样本数 未分段数 overall 均值 long_* 加权均值
deeplearning 5744 5737 7 0.0711 0.0574
deploy 5755 5731 24 0.1207 0.0577

deploy 命中更多面中心 z 异常样本24 vs 7被误判为性能更差。


Bug 2evaluator.py 与 metrics_3d.py 参考点不一致

位置evaluator.py::_process_frame_3d()save_detailed_matches 代码块

问题detailed_3d_matches.json 中存储的 gt_center_3ddistance 字段使用了整体中心而非面中心,与 metrics_3d.py 不一致。

后果compare_models_with_common_matches.sh 整条流水线基于 detailed_3d_matches.json,修复 metrics_3d.py 对这条路径完全无效。


Bug 3次要面中心哨兵值 [-1.0, -1.0, -1.0] 未过滤

位置metrics_3d.py::_get_vehicle_face_center()evaluator.py 中相应逻辑

问题背景GT 标注中,当某面不可见时(is_visible_from_camera = 0),该面的三维坐标被设为哨兵值 -1.0

"3d_right": [-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0]
              x3d   y3d   z3d  alpha   xc    yc  score  is_visible=0

代码只检查 face_data is not None,不检查可见性标志,导致:

  • gt_center = [-1.0, -1.0, -1.0] 被当成真实坐标
  • longitudinal_error = |det_z - (-1.0)| ≈ 8~18m(虚假巨大误差)
  • lateral_error = |det_x - (-1.0)| ≈ 2~5m(虚假巨大误差)

现象:修复 Bug 1 后0-10m 分段出现新的异常——deeplearning 比 deploy 差 24%纵向误差但该差异仅来自哨兵值命中数量不同dl: 15个deploy: 5个


三、修复方案

修复 Bug 1 + Bug 2使用整体中心 z 进行分段路由和相对误差计算

metrics_3d.pyadd_sample() 方法):

# 修复后:使用整体中心 z 作为分段路由和相对误差分母
gt_whole_z = gt['3d_info']['center'][2]
gt_depth = max(abs(gt_whole_z), 1e-6)
longitudinal_relative_error = longitudinal_error / gt_depth

longitudinal_distance = gt['3d_info']['center'][2]  # 整体中心 z

evaluator.py_process_frame_3d() 方法):

# 修复后
gt_whole_z = gt['3d_info']['center'][2]
gt_depth = max(abs(gt_whole_z), 1e-6)
longitudinal_relative_error = longitudinal_error / gt_depth

'distance': {
    'longitudinal': float(gt_whole_z),   # 用整体中心 z 做分段路由
    'lateral': float(gt_center[0])
}

修复 Bug 3过滤哨兵值面数据

metrics_3d.py_get_vehicle_face_center() 方法):

face_data = gt_3d_info['faces'].get(normalized_face_type)
# 检查 is_visible_from_camera (face_data[7]) 和哨兵值
if face_data is None or (len(face_data) >= 8 and face_data[7] == 0) or face_data[2] <= 0:
    return gt_3d_info['center']  # fallback 到整体中心
return face_data[:3]

evaluator.py_process_frame_3d() 方法):

face_valid = (
    face_data is not None and
    face_data[2] > 0 and
    not (len(face_data) >= 8 and face_data[7] == 0)
)
gt_center = face_data[:3] if face_valid else gt['3d_info']['center']

四、修复效果

Overall 指标对比

版本 deeplearning deploy 差异 说明
原始(有 Bug 0.1209 0.0711 -41.20% 假性差异
修复 Bug1+Bug2 0.0594 0.0535 -9.91% 改善但仍偏
修复全部 Bug 0.0547 0.0521 -4.72% 合理差异

0-10m 分段指标对比

版本 Long 差异 Lat 差异 说明
修复 Bug1+Bug2 后 -25.14% -24.42% 哨兵值污染
修复全部 Bug 后 -14.0% -3.85% 合理Lat 持平)

五、设计建议

  1. 参考点一致性原则metrics_3d.pyevaluator.py 生成的 detailed_3d_matches.json 必须使用相同的参考点逻辑,否则两条评测路径产生不同结果。

  2. 分段路由 vs 误差计算解耦:分段路由应始终使用整体目标中心 z稳定、不受面可见性影响误差计算可使用面中心业务语义正确

  3. 哨兵值防御:任何使用 GT 面数据的地方,必须先检查 is_visible_from_camera(第 8 维)和 face_z > 0,防止哨兵值 -1.0 污染计算。

  4. compare_models_with_common_matches.sh 的数据流:该脚本 不经过 metrics_3d.py,而是完全基于 detailed_3d_matches.json 重新聚合。因此 evaluator.py 中写入 JSON 的字段计算逻辑必须与 metrics_3d.py 保持严格一致。


六、GT 面数据格式说明

face_data = [x3d, y3d, z3d, alpha, xc, yc, score, is_visible_from_camera]
索引:          0    1    2     3    4    5    6            7

is_visible_from_camera:
  1 = 该面在相机视野中可见,(x3d, y3d, z3d) 为有效坐标
  0 = 该面不可见,(x3d, y3d, z3d) 填充哨兵值 -1.0

GT 面数据来源参考 CLAUDE.md → "3D Label Format (47 dimensions per line)"

  • dim15~22: front face
  • dim23~30: rear face
  • dim31~38: left face
  • dim39~46: right face

每组最后一维 is_visible_from_camera 即为可见性标志。