195 lines
7.0 KiB
Markdown
195 lines
7.0 KiB
Markdown
|
|
# 车辆 3D 指标异常分析与修复
|
|||
|
|
|
|||
|
|
**问题发现日期**:2026-03-09
|
|||
|
|
**涉及文件**:`eval_tools/evaluator/metrics_3d.py`、`eval_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 值:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 修复前
|
|||
|
|
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-6`,`longitudinal_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 2:evaluator.py 与 metrics_3d.py 参考点不一致
|
|||
|
|
|
|||
|
|
**位置**:`evaluator.py::_process_frame_3d()` 的 `save_detailed_matches` 代码块
|
|||
|
|
|
|||
|
|
**问题**:`detailed_3d_matches.json` 中存储的 `gt_center_3d` 和 `distance` 字段使用了整体中心而非面中心,与 `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.py`**(`add_sample()` 方法):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 修复后:使用整体中心 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()` 方法):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 修复后
|
|||
|
|
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()` 方法):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
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()` 方法):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
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.py` 与 `evaluator.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` 即为可见性标志。
|