# 模型评测框架使用指南

> 入口脚本：`eval_tools/core/run_eval_with_config.sh`

---

## 目录

1. [框架概览](#1-框架概览)
2. [目录结构](#2-目录结构)
3. [核心流程](#3-核心流程)
4. [配置文件说明](#4-配置文件说明)
5. [命令行参数说明](#5-命令行参数说明)
6. [数据格式](#6-数据格式)
7. [评测指标](#7-评测指标)
8. [ROI GT 处理](#8-roi-gt-处理)
9. [模型对比（共同匹配集）](#9-模型对比共同匹配集)
10. [Heading 误差分析](#10-heading-误差分析)
11. [输出结果](#11-输出结果)
12. [使用示例](#12-使用示例)
13. [常见问题](#13-常见问题)

---

## 1. 框架概览

本框架用于评测 YOLOv5-3D 模型的 2D 检测与 3D 定位性能，支持：

- **2D 检测评测**：全部 14 个类别的 Precision / Recall / AP / mAP
- **3D 检测评测**：vehicle / pedestrian / bicycle / rider 的横向误差、纵向误差、heading 误差
- **ROI GT 过滤**：评测坐标系与训练时保持一致
- **距离区间分析**：按纵向距离（z轴）和横向距离（x轴）分段统计 3D 指标
- **多模型对比**：基于共同匹配集（Common Matches）公平比较两个模型的 3D 性能
- **Heading 误差分析**：提取、可视化、分析方向预测误差

### 架构图

```
eval_tools/core/run_eval_with_config.sh
            │
            ▼
eval_tools/core/eval.py            ← 主入口 Python 脚本
            │
            ├─── eval_tools/configs/*.yaml   ← 配置文件
            │
            └─── eval_tools/evaluator/       ← 核心评测引擎
                      ├── evaluator.py       ← 主控流程、并行调度
                      ├── parser.py          ← GT / 检测结果解析
                      ├── matcher.py         ← 2D IoU 匹配
                      ├── metrics_2d.py      ← 2D 指标计算
                      ├── metrics_3d.py      ← 3D 指标计算
                      └── roi_processor.py   ← ROI 坐标系对齐
```

---

## 2. 目录结构

```
eval_tools/
├── core/
│   ├── eval.py                         # 主评测脚本
│   ├── run_eval_with_config.sh         # 单模型评测入口（当前默认入口）
│   ├── run_eval.sh                     # 早期版本入口（无配置文件）
│   ├── run_eval_2d_only.sh             # 仅 2D 评测
│   ├── run_eval_3d_only.sh             # 仅 3D 评测
│   └── run_evaluation_example.sh       # 示例脚本
│
├── configs/
│   ├── eval_config_mono3d-roi0.yaml        # mono3d 模型 ROI0 配置
│   ├── eval_config_mono3d-roi1.yaml        # mono3d 模型 ROI1 配置
│   ├── eval_config_yolov5s-roi0.yaml       # yolov5s 模型 ROI0 配置
│   ├── eval_config_yolov5s-roi1.yaml       # yolov5s 模型 ROI1 配置
│   ├── eval_config_yolov5s_cncap-roi0.yaml # yolov5s CNCAP 测试集 ROI0（当前默认）
│   └── eval_config_yolov5s_cncap-roi1.yaml # yolov5s CNCAP 测试集 ROI1
│
├── evaluator/
│   ├── __init__.py
│   ├── evaluator.py       # 主评测器（数据加载、调度、报告生成）
│   ├── parser.py          # 真值 / 检测结果解析器
│   ├── matcher.py         # 2D IoU 匹配器
│   ├── metrics_2d.py      # 2D 指标计算（P/R/AP/mAP）
│   ├── metrics_3d.py      # 3D 指标计算（Lat/Long/Heading）
│   └── roi_processor.py   # ROI GT 处理器
│
├── model_comparison/
│   ├── find_common_matches.py           # 找出两模型共同匹配的 GT
│   ├── compare_models.py                # 生成双模型对比报告
│   ├── compare_models_with_common_matches.sh  # 一键双模型对比脚本
│   ├── generate_eval_report.py          # 从 JSON 生成 Markdown 评测报告
│   ├── generate_comparison_report.py    # 生成对比 Markdown 报告
│   └── ...
│
├── heading_analysis/
│   ├── extract_bad_heading_cases.py     # 提取 heading 误差大的案例
│   ├── visualize_heading_errors.py      # 可视化 heading 误差（BEV/图像/极坐标）
│   └── ...
│
├── visualization/
│   └── test_val_visualize_from_paths.py # 从路径可视化检测结果
│
├── utilities/
│   ├── check_img_exists.py              # 检查图像文件是否存在
│   └── extract_video_frames.py          # 视频帧提取
│
└── docs/                                # 详细设计文档（本文件所在目录）
```

---

## 3. 核心流程

### 单模型评测（`run_eval_with_config.sh`）

```
┌─────────────────────────────────────────────────────┐
│  run_eval_with_config.sh                            │
│                                                     │
│  1. 设置 PYTHONPATH                                  │
│  2. 指定配置文件路径 (MODEL_CONFIG)                   │
│  3. 指定输出目录  (OUTPUT_BASE/MODEL_NAME/TIMESTAMP) │
│  4. 调用 eval_tools/core/eval.py                    │
└─────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────┐
│  eval.py                                            │
│                                                     │
│  1. 解析 YAML 配置 + 命令行参数                       │
│  2. 初始化 Evaluator（含 ROIProcessor）              │
│  3. 加载检测结果 & 真值文件对                         │
│  4. 并行处理每张图 (IoU 匹配 + 指标统计)              │
│  5. 汇总指标并生成报告                                │
└─────────────────────────────────────────────────────┘
                        │
           ┌────────────┴─────────────┐
           ▼                          ▼
  evaluation_report.json      detailed_3d_matches.json
  evaluation_report.txt       (--save-detailed-matches)
  per_case_reports/
```

### 双模型对比（`compare_models_with_common_matches.sh`）

```
Model 1 评测 → detailed_3d_matches.json
                                        ┐
                                        ├─ find_common_matches.py → common_matches.json
                                        │
Model 2 评测 → detailed_3d_matches.json ┘
                                        │
                                        └─ compare_models.py → 对比报告
```

---

## 4. 配置文件说明

配置文件位于 `eval_tools/configs/`，YAML 格式，分以下几个部分：

### 4.1 数据集路径 (`dataset`)

```yaml
dataset:
  det_path: "/data1/.../evalset_roi0"  # 检测结果根目录（含 case 文件夹）
  gt_path:  "/data1/xdzhu/Testdata"    # 真值标签根目录（含 case 文件夹）
  path_depth: 1       # 目录层级：1 = det_path/case/txt_results
                      #           2 = det_path/level1/case/txt_results
  det_format: "json"  # 检测结果格式：auto / json / txt
  gt_format:  "json"  # 真值格式：auto / json / txt
```

**目录结构要求（`path_depth: 1`）**：
```
det_path/
└── case_019b178d/
    └── json_results/   ← det_format="json"
        ├── 000000.json
        └── 000001.json

gt_path/
└── case_019b178d/
    └── labels_json/    ← gt_format="json"
        ├── 000000.json
        └── 000001.json
```

### 4.2 图像尺寸 (`image`)

```yaml
image:
  width:  1920
  height: 1080
```

### 4.3 模型配置与 GT 过滤 (`model`)

```yaml
model:
  input_size: 704               # 模型输入宽度（像素）
  min_box_size_at_input_scale: 8  # 模型输入分辨率下的最小框尺寸
  # GT 过滤阈值自动计算：min_box_size = 8 * roi_width / 704
  # ROI0 (1920→704): ≈ 21.8 像素；ROI1 (704→704): 8 像素
```

### 4.4 并行性能 (`performance`)

```yaml
performance:
  num_workers: 32   # 并行 worker 数（null = auto-detect CPU 核数）
```

### 4.5 ROI GT 处理 (`roi_gt`)

```yaml
roi_gt:
  enabled: true
  calib_root: "/data1/xdzhu/Testdata"  # 标定文件根目录（含 camera4.json）
  roi_config: [1920, 960]              # ROI 配置，与训练保持一致
  # ROI0: [1920, 960]
  # ROI1: [704, 352]
```

> **重要**：`roi_config` 必须与训练时的 ROI 配置完全一致，否则坐标系不对齐，IoU 计算将不准确。

### 4.6 类别定义 (`classes`)

```yaml
classes:
  3d_classes: [0, 1, 2, 3]    # 参与 3D 评测的类别
  2d_classes: [4, 5, ..., 13]  # 仅参与 2D 评测的类别
  class_names:
    0: "vehicle"
    1: "pedestrian"
    ...
    13: "tricycle"
```

### 4.7 匹配参数 (`matching`)

```yaml
matching:
  iou_threshold: 0.5   # 2D 匹配 IoU 阈值
```

### 4.8 2D 指标 (`metrics_2d`)

```yaml
metrics_2d:
  enabled: true
  conf_threshold: 0.4      # Precision/Recall 置信度阈值
  ap_method: "voc2010"     # AP 计算方法：voc2010（11点插值）/ coco（全点插值）
```

### 4.9 3D 指标 (`metrics_3d`)

```yaml
metrics_3d:
  enabled: true
  heading_tolerance: "both"  # strict / relaxed / both
  distance_ranges:           # 纵向距离区间（米）
    - [0, 10]
    - [10, 20]
    - ...
    - [100, 999]
  lateral_distance_ranges:   # 横向距离区间（米，x 轴）
    - [-50, -40]
    - ...
    - [40, 50]
```

**`heading_tolerance` 说明**：
| 值 | 含义 |
|---|---|
| `strict` | 标准计算（默认）|
| `relaxed` | 对称物体允许 180° 对称：`error = min(error, π - error)` |
| `both` | 同时计算并输出 strict 和 relaxed 两套结果 |

### 4.10 输出配置 (`output`)

```yaml
output:
  save_path: "evaluation_results/my_model/{timestamp}"
  formats: ["json", "txt"]
  print_details: true
  per_case_reports: true
```

---

## 5. 命令行参数说明

`eval_tools/core/eval.py` 支持以下参数（命令行参数优先级高于配置文件）：

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `--config` | str | — | YAML 配置文件路径 |
| `--det-path` | str | — | 检测结果根目录 |
| `--gt-path` | str | — | 真值标签根目录 |
| `--path-depth` | int | 1 | 目录层级（1 或 2）|
| `--det-format` | str | auto | 检测结果格式（auto/json/txt）|
| `--gt-format` | str | auto | 真值格式（auto/json/txt）|
| `--output-dir` | str | eval_results | 输出目录 |
| `--img-width` | int | 1920 | 图像宽度（像素）|
| `--img-height` | int | 1080 | 图像高度（像素）|
| `--iou-threshold` | float | 0.5 | IoU 匹配阈值 |
| `--conf-threshold` | float | 0.5 | 置信度阈值 |
| `--ap-method` | str | voc2010 | AP 计算方法（voc2010/coco）|
| `--heading-tolerance` | str | strict | heading 容忍模式（strict/relaxed/both）|
| `--num-workers` | int | auto | 并行 worker 数 |
| `--eval-2d-only` | flag | — | 仅评测 2D |
| `--eval-3d-only` | flag | — | 仅评测 3D |
| `--save-detailed-matches` | flag | — | 保存详细 3D 匹配信息（供模型对比使用）|

---

## 6. 数据格式

### 6.1 真值（Ground Truth）格式

**车辆类别（50 个值）**：
```
[class, x, y, w, h,                          # 0-4: 类别 + 归一化 2D 框
 x3d, y3d, z3d,                              # 5-7: 3D 中心点（相机坐标系）
 l, h, w,                                   # 8-10: 3D 尺寸（长高宽）
 rot_y,                                      # 11: 绕 Y 轴旋转角
 xc, yc,                                     # 12-13: 3D 中心点的图像投影
 xc_d, yc_d,                                 # 14-15: 深度相关投影
 alpha,                                      # 16: 观测角
 0,                                          # 17: 占位符
 前面(x3d,y3d,z3d,alpha,xc,yc,score,occ),    # 18-25
 后面(x3d,y3d,z3d,alpha,xc,yc,score,occ),    # 26-33
 左面(x3d,y3d,z3d,alpha,xc,yc,score,occ),    # 34-41
 右面(x3d,y3d,z3d,alpha,xc,yc,score,occ)]    # 42-49
```

**行人/自行车/骑手（18 个值）**：
上述 0-17，无四面信息。

**仅 2D 标注（6 个值）**：
```
[class, x, y, w, h, -1]
```

### 6.2 检测结果格式

**车辆（15 个字段）**：
```
vehicle  0.95  368.08 574.17 437.89 617.20  cam  -30.14 1.43 68.55  5.52 2.50 2.31  2.70  left
label    conf  x1     y1     x2     y2       cs   x3d    y3d  z3d    l    h    w     rot_y face_type
```
`face_type` 取值：`front` / `back`（或 `rear`/`tail`）/ `left` / `right`

**行人/骑手等（15 个字段）**：同上，`face_type` 固定为 `whole`。

**纯 2D 类别（6 个字段）**：
```
plate  0.94246  532.12 203.26 558.73 214.86
label  conf     x1     y1     x2     y2
```

---

## 7. 评测指标

### 7.1 2D 检测指标

- **Precision**：TP / (TP + FP)，按 `conf_threshold` 过滤
- **Recall**：TP / (TP + FN)
- **F1-Score**：2 × P × R / (P + R)
- **AP**：PR 曲线下面积（IoU 阈值 0.5）
- **mAP**：所有类别 AP 的算术平均

**匹配规则**：
1. 按置信度从高到低排序预测框
2. 与真值框 IoU ≥ 0.5 且类别相同 → TP（每个真值框只匹配一次）
3. 未匹配预测框 → FP；未被匹配真值框 → FN

**类别划分（14 类）**：

| ID | 类别 | 3D 评测 |
|----|------|---------|
| 0 | vehicle | ✓ |
| 1 | pedestrian | ✓ |
| 2 | bicycle | ✓ |
| 3 | rider | ✓ |
| 4 | roadblock | — |
| 5 | head | — |
| 6 | tsr | — |
| 7 | guideboard | — |
| 8 | plate | — |
| 9 | wheel | — |
| 10 | tl_border | — |
| 11 | tl_wick | — |
| 12 | tl_num | — |
| 13 | tricycle | — |

### 7.2 3D 检测指标

> 前提：2D IoU 匹配成功（≥ 0.5）且真值包含完整 3D 标注。

**车辆类别**（基于最近面中心点对比）：

根据检测结果的 `face_type` 字段，在真值中选取对应面的中心点进行比较：
```
face_type → 真值面中心点索引
  front  →  [18, 19, 20]  (x3d, y3d, z3d)
  back   →  [26, 27, 28]
  left   →  [34, 35, 36]
  right  →  [42, 43, 44]
```

**行人/骑手/自行车**：使用整体中心点 `[5, 6, 7]` 进行比较。

**误差指标**：
- **横向误差（Lateral Error）**：`|x3d_pred - x3d_gt|`（米）
- **纵向误差（Longitudinal Error）**：`|z3d_pred - z3d_gt|`（米）
- **Heading 误差**：`|rot_y_pred - rot_y_gt|`（弧度）

每个指标输出：均值（Mean）、中位数（Median）、标准差（Std）、90 百分位数（P90）。

---

## 8. ROI GT 处理

### 动机

训练时图像经过 ROI 裁剪和缩放（坐标系改变），但历史评测代码中检测结果在 ROI 坐标系，GT 在原图坐标系，导致 IoU 计算不准确。

### 解决方案

`ROIProcessor`（`eval_tools/evaluator/roi_processor.py`）在评测时对 GT 标签执行与训练完全相同的 ROI 处理：

1. 读取标定文件（`case_root/camera4.json`）中的 `focal_u, focal_v, cu, cv, pitch`
2. 计算裁剪中心：`cx = oriW // 2`，`cy = cv - focal_v × tan(pitch × π/180)`
3. 以 `(cx, cy)` 为中心按 `roi_config` 裁剪 ROI
4. 过滤完全在 ROI 外的目标；将部分在 ROI 内的框 clip 到边界
5. 将坐标转换到 ROI 相对坐标系 → 再缩放到模型输入分辨率

**配置对应关系**：
| 训练 ROI 配置 | `roi_config` 值 |
|---|---|
| ROI0（大 crop） | `[1920, 960]` |
| ROI1（小 crop） | `[704, 352]` |

---

## 9. 模型对比（共同匹配集）

### 问题背景

直接比较两个模型的 3D 指标时，若双方匹配到的 GT 对象数量不同（如相差 11.7%），差异可能来自**匹配集差异**而非模型质量。

### 解决方案

只统计两个模型**都成功匹配**的 GT 对象（Common Matches）的 3D 指标。

### 使用方式

#### 方式一：一键脚本（推荐）

```bash
bash eval_tools/model_comparison/compare_models_with_common_matches.sh
```

该脚本自动完成：
1. 评测 Model 1，保存 `detailed_3d_matches.json`
2. 评测 Model 2，保存 `detailed_3d_matches.json`
3. 调用 `find_common_matches.py` 得到 `common_matches.json`
4. 调用 `compare_models.py` 生成对比报告
5. 调用 `generate_eval_report.py` 为各模型生成 Markdown 报告

#### 方式二：手动分步执行

```bash
# Step 1: 评测两个模型
python eval_tools/core/eval.py \
  --config eval_tools/configs/eval_config_mono3d-roi1.yaml \
  --output-dir eval_results/model1 \
  --heading-tolerance both \
  --save-detailed-matches

python eval_tools/core/eval.py \
  --config eval_tools/configs/eval_config_yolov5s_cncap-roi1.yaml \
  --output-dir eval_results/model2 \
  --heading-tolerance both \
  --save-detailed-matches

# Step 2: 找出共同匹配
python eval_tools/model_comparison/find_common_matches.py \
  --model1-matches eval_results/model1/detailed_3d_matches.json \
  --model2-matches eval_results/model2/detailed_3d_matches.json \
  --output eval_results/common_matches.json \
  --model1-name "mono3d" \
  --model2-name "yolov5s-300w"

# Step 3: 生成对比报告
python eval_tools/model_comparison/compare_models.py \
  --model1 eval_results/model1/evaluation_report.json \
  --model2 eval_results/model2/evaluation_report.json \
  --common-matches eval_results/common_matches.json \
  --output-dir eval_results/comparison \
  --model1-name "mono3d" \
  --model2-name "yolov5s-300w"
```

### 生成 Markdown 报告

```bash
python eval_tools/model_comparison/generate_eval_report.py \
  eval_results/model1/evaluation_report.json \
  --model "yolov5s-300w-cncap" \
  --date 2026-03-03
```

输出：`eval_results/model1/EVALUATION_REPORT.md`

---

## 10. Heading 误差分析

对于 heading 预测较差的案例，可使用以下工具进行深入分析：

### Step 1：提取 bad cases

```bash
python eval_tools/heading_analysis/extract_bad_heading_cases.py \
  --input eval_results/model1/detailed_3d_matches.json \
  --threshold 1.5 \      # heading 误差阈值（弧度）
  --top-k 100 \
  --output bad_heading.json \
  --stats
```

### Step 2：可视化

```bash
python eval_tools/heading_analysis/visualize_heading_errors.py \
  --input bad_heading.json \
  --image-root /path/to/images \
  --output runs/heading_viz \
  --viz-types bev image angle combined
```

可视化类型：
- `bev`：鸟瞰图（GT / Pred 的 3D 框 + 方向箭头）
- `image`：原图视图（2D 框 + 误差标注）
- `angle`：角度分析（极坐标 + 柱状图）
- `combined`：四宫格综合视图

---

## 11. 输出结果

### 11.1 文件列表

| 文件 | 说明 |
|------|------|
| `evaluation_report.json` | 完整评测报告（JSON 格式，含所有类别和距离区间）|
| `evaluation_report.txt` | 人类可读的文本报告 |
| `detailed_3d_matches.json` | 每帧每目标的 3D 匹配详情（`--save-detailed-matches`）|
| `per_case_reports/*.json` | 每个 case 的独立报告（`per_case_reports: true`）|
| `EVALUATION_REPORT.md` | Markdown 格式摘要报告（由 `generate_eval_report.py` 生成）|

### 11.2 JSON 报告结构

```json
{
  "evaluation_config": {...},
  "2d_evaluation": {
    "overall": {
      "precision": 0.85,
      "recall": 0.78,
      "f1_score": 0.81,
      "map": 0.72
    },
    "per_class": {
      "vehicle": {"precision": ..., "recall": ..., "ap": ...},
      ...
    }
  },
  "3d_evaluation": {
    "vehicle": {
      "overall": {
        "n_samples": 440408,
        "lateral_mean": 0.647,
        "longitudinal_mean": 1.680,
        "heading_mean_strict": 0.258
      },
      "by_distance": {
        "[0-10m]": {...},
        ...
      }
    }
  }
}
```

### 11.3 控制台输出示例

```
3D Metrics:
  vehicle [overall]: Lat=0.647m, Long=1.680m, Head=0.258rad (n=440408)
    [0-10m]:   Lat=0.423m, Long=0.891m, Head=0.153rad (n=98241)
    [10-20m]:  Lat=0.501m, Long=1.203m, Head=0.177rad (n=142038)
    [30-60m]:  Lat=1.149m, Long=5.074m, Head=0.607rad (n=18432)
    [100-999m]:Lat=2.341m, Long=9.221m, Head=1.036rad (n=871)
```

---

## 12. 使用示例

### 例 1：单模型评测（当前默认入口）

```bash
cd /deeplearning_team/ydong/dongying/projects/yolov5-3d
conda activate slot

bash eval_tools/core/run_eval_with_config.sh
```

脚本使用 `eval_tools/configs/eval_config_yolov5s_cncap-roi0.yaml`，结果保存至：
```
evaluation_results/eval_results_common_match_comparison_cncap_yolov5s_20260303_roi0/
└── yolov5s-300w-newdata-cncap-test/
    └── 20260303_HHMMSS/
        ├── evaluation_report.json
        ├── evaluation_report.txt
        └── detailed_3d_matches.json
```

### 例 2：命令行直接评测（不使用配置文件）

```bash
python eval_tools/core/eval.py \
  --det-path /data1/.../inference_results/model/evalset_roi0 \
  --gt-path  /data1/xdzhu/Testdata \
  --output-dir evaluation_results/my_test \
  --heading-tolerance both \
  --conf-threshold 0.4 \
  --num-workers 16
```

### 例 3：仅 2D 评测

```bash
python eval_tools/core/eval.py \
  --config eval_tools/configs/eval_config_yolov5s_cncap-roi0.yaml \
  --eval-2d-only \
  --output-dir evaluation_results/2d_only_test
```

### 例 4：双模型对比

```bash
bash eval_tools/model_comparison/compare_models_with_common_matches.sh
```

### 例 5：修改配置直接运行

仅修改 `run_eval_with_config.sh` 中的以下三个变量即可适配新模型：

```bash
MODEL_CONFIG="eval_tools/configs/eval_config_yolov5s_cncap-roi0.yaml"
OUTPUT_BASE="evaluation_results/eval_results_my_model"
MODEL_NAME="my-model-name"
```

---

## 13. 常见问题

### Q1：No image pairs found

**原因**：`det_path` 与 `gt_path` 下的 case 名称不匹配，或 `det_format` / `gt_format` 设置错误。

**排查**：
```bash
ls ${det_path}/        # 查看 case 目录名
ls ${det_path}/case_xxx/  # 查看是 json_results/ 还是 txt_results/
```

### Q2：IoU 始终为 0，评测指标异常

**原因**：`roi_gt` 配置未启用，或 `roi_config` 与训练时不匹配，导致坐标系不对齐。

**排查**：检查配置中 `roi_gt.enabled: true` 且 `roi_config` 与训练 YAML 中的 `roi:` 字段一致。

### Q3：3D 指标样本数 (n=0)

**原因**：真值中该类别无完整 3D 标注（仅有 6 维的 2D-only 标注），或 2D 匹配 IoU < 0.5。

### Q4：如何添加新模型配置

复制现有配置文件，仅修改 `dataset.det_path` 和 `dataset.gt_path` 即可：
```bash
cp eval_tools/configs/eval_config_yolov5s_cncap-roi0.yaml \
   eval_tools/configs/eval_config_my_model-roi0.yaml
# 编辑新文件中的 det_path 和 output.save_path
```

### Q5：如何加速评测

调大 `performance.num_workers`（建议不超过 CPU 核数 × 2）：
```bash
python eval_tools/core/eval.py --config ... --num-workers 64
```

---

*文档生成日期：2026-03-03*
