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

599 lines
16 KiB
Markdown
Executable File
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.
# 模型输出评测方案设计
## 1. 评测概述
### 1.1 评测目标
- **2D检测评测**: 评估所有类别的2D边界框检测性能
- **3D检测评测**: 评估3D类别的空间定位和朝向估计性能
### 1.2 评测类别划分
- **3D目标类别** (0-3): vehicle, pedestrian, bike, rider
- **纯2D目标类别** (4-13): roadblock, head, tsr, guideboard, plate, wheel, tl_border, tl_wick, tl_num, tricycle
## 2. 数据格式解析
### 2.1 真值数据格式
#### 2.1.1 3D类别真值格式
**完整3D标注车辆类别- 50个值**:
```
[label, x, y, w, h, # 0-4: 类别和2D框归一化
x3d_ori, y3d_ori, z3d_ori, # 5-7: 原始3D中心点
l3d, h3d, w3d, # 8-10: 3D尺寸
rot_y, # 11: 旋转角
xc_ori, yc_ori, # 12-13: 原始中心点2D投影
xc_ori_d, yc_ori_d, # 14-15: 深度相关中心点
alpha_ori, # 16: 原始alpha角
0, # 17: 占位符
# 前面 (18-25)
x3d_front, y3d_front, z3d_front, alpha_front, xc_front, yc_front, score_front, is_occ_front,
# 后面 (26-33)
x3d_back, y3d_back, z3d_back, alpha_back, xc_back, yc_back, score_back, is_occ_back,
# 左面 (34-41)
x3d_left, y3d_left, z3d_left, alpha_left, xc_left, yc_left, score_left, is_occ_left,
# 右面 (42-49)
x3d_right, y3d_right, z3d_right, alpha_right, xc_right, yc_right, score_right, is_occ_right]
```
**完整3D标注非车辆类别- 18个值**:
```
[label, x, y, w, h, # 0-4: 类别和2D框归一化
x3d_ori, y3d_ori, z3d_ori, # 5-7: 3D中心点
l3d, h3d, w3d, # 8-10: 3D尺寸
rot_y, # 11: 旋转角
xc_ori, yc_ori, # 12-13: 中心点2D投影
xc_ori_d, yc_ori_d, # 14-15: 深度相关中心点
alpha_ori, # 16: alpha角
0] # 17: 占位符
```
**仅2D标注 - 6个值**:
```
[label, x, y, w, h, -1] # 最后一位为-1表示无3D标注
```
#### 2.1.2 纯2D类别真值格式6个值
```
[label, x, y, w, h, -1] # label ∈ {4,5,6,7,8,9,10,11,12,13}
```
### 2.2 检测结果格式
#### 2.2.1 3D类别检测格式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, coord_sys, x3d, y3d, z3d, l3d, h3d, w3d, rot_y, face_type]
```
face_type可以是 front, back, left, right也支持 rear 和 tail 作为 back 的别名
**非车辆类别**:
```
pedestrian 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 whole
[label, conf, x1, y1, x2, y2, coord_sys, x3d, y3d, z3d, l3d, h3d, w3d, rot_y, whole]
```
#### 2.2.2 纯2D类别检测格式5个值
```
plate 0.94246 532.12 203.26 558.73 214.86
[label, conf, x1, y1, x2, y2]
```
## 3. 评测指标设计
### 3.1 2D检测指标
#### 3.1.1 基础指标
- **Precision (精确率)**: TP / (TP + FP)
- **Recall (召回率)**: TP / (TP + FN)
- **AP (Average Precision)**: PR曲线下面积IoU阈值=0.5
- **mAP (mean Average Precision)**: 所有类别AP的平均值
#### 3.1.2 匹配规则
- **IoU阈值**: 0.5
- **匹配策略**:
1. 计算预测框与真值框的IoU
2. 按置信度从高到低排序预测框
3. 每个真值框最多匹配一个预测框
4. IoU >= 0.5 且类别相同视为匹配成功TP
5. 未匹配的预测框为FP未匹配的真值框为FN
#### 3.1.3 分类别评测
- 对每个类别分别计算 Precision, Recall, AP
- 类别包括: vehicle, pedestrian, bike, rider, roadblock, head, tsr, guideboard, plate, wheel, tl_border, tl_wick, tl_num, tricycle
#### 3.1.4 整体评测
- **总Precision**: 所有类别的总TP / (总TP + 总FP)
- **总Recall**: 所有类别的总TP / (总TP + 总FN)
- **mAP**: 所有类别AP的算术平均
### 3.2 3D检测指标
#### 3.2.1 评测范围
仅评测3D类别vehicle, pedestrian, bike, rider
#### 3.2.2 前提条件
只有在2D检测匹配成功IoU >= 0.5且真值包含完整3D标注的情况下才进行3D指标评测
#### 3.2.3 3D评测指标
**车辆类别的测距误差计算**:
车辆类别需要根据预测结果中的最近面信息front/back/left/right选取真值中对应的最近面中心点进行比较
1. 根据预测结果中的`face_type`字段front/back/left/right确定预测的最近面
2. 从真值的4个面信息中选取对应面的中心点坐标
3. 计算预测最近面中心点与真值对应面中心点的误差
```
# 车辆类别
face_mapping = {
'front': [18, 19, 20], # x3d_front, y3d_front, z3d_front 在真值中的索引
'back': [26, 27, 28], # x3d_back, y3d_back, z3d_back
'left': [34, 35, 36], # x3d_left, y3d_left, z3d_left
'right': [42, 43, 44] # x3d_right, y3d_right, z3d_right
}
# 根据预测的face_type选择真值中对应的面中心点
face_type = det_result['face_type'] # 'front', 'back', 'left', 'right'
x3d_gt, y3d_gt, z3d_gt = gt_values[face_mapping[face_type]]
# 获取预测的最近面中心点
x3d_pred, y3d_pred, z3d_pred = det_result['3d_info']['center']
# 计算误差
lateral_error = |x3d_pred - x3d_gt|
longitudinal_error = |z3d_pred - z3d_gt|
```
**非车辆类别的测距误差计算**:
非车辆类别pedestrian, bike, rider直接使用3D框中心点计算误差
```
# 非车辆类别
x3d_gt, y3d_gt, z3d_gt = gt_values[5:8] # x3d_ori, y3d_ori, z3d_ori
x3d_pred, y3d_pred, z3d_pred = det_result['3d_info']['center']
lateral_error = |x3d_pred - x3d_gt|
longitudinal_error = |z3d_pred - z3d_gt|
```
**Heading偏差 (Heading Error)**:
所有3D类别使用相同的方式计算heading误差
```
heading_error = |normalize_angle(rot_y_pred - rot_y_gt)|
```
其中 normalize_angle 将角度差归一化到 [-π, π]
#### 3.2.4 统计指标
对每个3D类别分别统计
- **横向误差**: 平均值、中位数、标准差、90%分位数
- **纵向误差**: 平均值、中位数、标准差、90%分位数
- **Heading误差**: 平均值、中位数、标准差、90%分位数
## 4. 评测流程设计
### 4.1 数据预处理
#### 4.1.1 真值数据解析
```python
def parse_ground_truth(gt_line, img_width, img_height):
"""
解析真值标注
返回: {
'label': int,
'bbox_2d': [x1, y1, x2, y2], # 像素坐标
'has_3d': bool,
'3d_info': {
'center': [x3d, y3d, z3d], # 原始中心点(用于非车辆类别)
'dimensions': [l3d, h3d, w3d],
'rotation': rot_y,
'faces': { # 仅车辆类别有此字段
'front': [x3d, y3d, z3d, alpha, xc, yc, score, is_occ],
'back': [x3d, y3d, z3d, alpha, xc, yc, score, is_occ],
'left': [x3d, y3d, z3d, alpha, xc, yc, score, is_occ],
'right': [x3d, y3d, z3d, alpha, xc, yc, score, is_occ]
} if label == 0 else None
} if has_3d else None
}
"""
```
#### 4.1.2 检测结果解析
```python
def parse_detection(det_line):
"""
解析检测结果
返回: {
'label': str -> int,
'confidence': float,
'bbox_2d': [x1, y1, x2, y2],
'3d_info': {
'center': [x3d, y3d, z3d],
'dimensions': [l3d, h3d, w3d],
'rotation': rot_y,
'face_type': str
} if is_3d_class else None
}
"""
```
### 4.2 2D评测流程
```
对每张图像:
1. 加载真值和检测结果
2. 对每个类别:
a. 筛选出该类别的GT和DET
b. 计算所有配对的IoU矩阵
c. 按置信度排序DET
d. 贪婪匹配Hungarian or Greedy
e. 统计TP, FP, FN
f. 记录置信度和匹配状态
对每个类别:
3. 根据所有图像的统计:
a. 按置信度排序所有预测
b. 计算不同阈值下的Precision-Recall
c. 计算AP (使用插值或积分)
整体统计:
4. 计算总Precision, Recall
5. 计算mAP
```
### 4.3 3D评测流程
```
对每张图像:
1. 基于2D匹配结果
2. 对每对匹配成功的(GT, DET):
a. 检查GT是否有完整3D标注
b. 检查DET是否为3D类别
c. 如果都满足:
- 如果是车辆类别(label=0):
* 根据DET的face_type选择GT中对应面的中心点
* 计算预测最近面与真值对应面的横向/纵向误差
- 如果是非车辆类别(label=1,2,3):
* 直接使用3D框中心点计算横向/纵向误差
- 计算Heading误差所有类别相同
- 按类别记录
对每个3D类别:
3. 统计所有图像的误差:
- 横向: mean, median, std, 90th percentile
- 纵向: mean, median, std, 90th percentile
- Heading: mean, median, std, 90th percentile
```
## 5. 实现架构
### 5.1 模块划分
```
eval_tools/
├── evaluator/
│ ├── __init__.py
│ ├── parser.py # 数据解析模块
│ ├── matcher.py # 2D匹配模块
│ ├── metrics_2d.py # 2D指标计算
│ ├── metrics_3d.py # 3D指标计算
│ ├── evaluator.py # 主评测器
│ └── visualizer.py # 结果可视化
├── configs/
│ └── eval_config.yaml # 评测配置
└── eval.py # 评测入口脚本
```
### 5.2 核心类设计
#### 5.2.1 数据解析器
```python
class GroundTruthParser:
def parse_line(self, line, img_shape)
def is_3d_annotated(self, values)
def get_class_name(self, label_id)
class DetectionParser:
def parse_line(self, line)
def map_class_name(self, name_str)
```
#### 5.2.2 匹配器
```python
class Matcher2D:
def __init__(self, iou_threshold=0.5)
def compute_iou(self, box1, box2)
def match(self, gts, dets) # 返回匹配对列表
```
#### 5.2.3 指标计算器
```python
class Metrics2D:
def __init__(self)
def add_image_results(self, matches, gts, dets, class_id)
def compute_ap(self, class_id)
def compute_map(self), face_type=None)
def compute_statistics(self, class_id)
def get_summary(self)
def _get_vehicle_face_center(self, gt_faces, face_type) # 根据face_type获取对应面中心
class Metrics3D:
def __init__(self)
def add_sample(self, gt_3d, det_3d, class_id)
def compute_statistics(self, class_id)
def get_summary(self)
```
#### 5.2.4 主评测器
```python
class Evaluator:
def __init__(self, config)
def load_ground_truth(self, gt_file)
def load_detections(self, det_file)
def evaluate_2d(self)
def evaluate_3d(self)
def generate_report(self, output_path)
```
### 5.3 配置文件示例
```yaml
# eval_config.yaml
dataset:
gt_path: "path/to/ground_truth"
det_path: "path/to/detections"
image_list: "path/to/image_list.txt"
classes:
3d_classes: [0, 1, 2, 3] # vehicle, pedestrian, bike, rider
2d_classes: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
class_names:
0: "vehicle"
1: "pedestrian"
2: "bike"
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"
matching:
iou_threshold: 0.5
metrics_2d:
enabled: true
confidence_threshold: [0.1, 0.3, 0.5, 0.7, 0.9]
metrics_3d:
enabled: true
distance_ranges: # 可选:分距离段统计
- [0, 30]
- [30, 60]
- [60, 100]
- [100, 999]
output:
save_path: "eval_results"
format: ["json", "csv", "txt"]
visualize: true
```
## 6. 输出报告格式
### 6.1 2D评测报告
```json
{
"2d_evaluation": {
"per_class": {
"vehicle": {
"precision": 0.92,
"recall": 0.88,
"ap": 0.90,
"num_gt": 1500,
"num_det": 1450,
"tp": 1320,
"fp": 130,
"fn": 180
},
"pedestrian": {...},
...
},
"overall": {
"precision": 0.87,
"recall": 0.84,
"map": 0.85,
"num_classes": 14
}
}
}
```
### 6.2 3D评测报告
```json
{
"3d_evaluation": {
"vehicle": {
"lateral_error": {
"mean": 0.25,
"median": 0.18,
"std": 0.15,
"percentile_90": 0.45
},
"longitudinal_error": {
"mean": 1.2,
"median": 0.9,
"std": 0.8,
"percentile_90": 2.1
},
"heading_error": {
"mean": 0.08,
"median": 0.05,
"std": 0.06,
"percentile_90": 0.15
},
"num_samples": 1320
},
"pedestrian": {...},
...
}
}
```
## 7. 关键实现细节
### 7.1 IoU计算
```python
def compute_iou(box1, box2):
"""
box: [x1, y1, x2, y2]
"""
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
if x2 < x1 or y2 < y1:
return 0.0
intersection = (x2 - x1) * (y2 - y1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - intersection
return intersection / union if union > 0 else 0.0
```
### 7.2 AP计算11点插值法
```python
def compute_ap(precisions, recalls):
"""
使用VOC 11点插值法计算AP
"""
ap = 0.0
for t in np.linspace(0, 1, 11):
if np.sum(recalls >= t) == 0:
p = 0
else:
p = np.max(precisions[recalls >= t])
ap += p / 11.0
return ap
```
### 7.3 角度归一化
```python
def normalize_angle(angle):
"""
将角度归一化到[-π, π]
"""
while angle > np.pi:
angle -= 2 * np.pi
while angle < -np.pi:
angle += 2 * np.pi
return angle
```
### 7.4 坐标系转换
```python
def normalized_to_pixel(bbox_norm, img_width, img_height):
"""
归一化坐标转像素坐标
bbox_norm: [x_center, y_center, w, h] (normalized)
返回: [x1, y1, x2, y2] (pixel)
"""
x_center = bbox_norm[0] * img_width
y_center = bbox_norm[1] * img_height
w = bbox_norm[2] * img_width
h = bbox_norm[3] * img_height
x1 = x_center - w / 2
y1 = y_center - h / 2
x2 = x_center + w / 2
y2 = y_center + h / 2
return [x1, y1, x2, y2]
```
## 8. 使用示例
### 8.1 命令行使用
```bash
# 基础评测
python eval_tools/eval.py \
--gt-path /path/to/labels \
--det-path /path/to/predictions \
--output-dir eval_results
# 指定配置文件
python eval_tools/eval.py \
--config eval_tools/configs/eval_config.yaml
# 只评测2D
python eval_tools/eval.py \
--config eval_config.yaml \
--eval-2d-only
# 只评测3D
python eval_tools/eval.py \
--config eval_config.yaml \
--eval-3d-only
```
### 8.2 Python API使用
```python
from eval_tools.evaluator import Evaluator
# 创建评测器
evaluator = Evaluator(config_path='eval_config.yaml')
# 加载数据
evaluator.load_data(
gt_path='/path/to/labels',
det_path='/path/to/predictions'
)
# 执行评测
results_2d = evaluator.evaluate_2d()
results_3d = evaluator.evaluate_3d()
# 生成报告
evaluator.generate_report(
output_dir='eval_results',
formats=['json', 'csv', 'html']
)
```
## 9. 扩展性考虑
### 9.1 多IoU阈值评测
可扩展支持COCO风格的多IoU阈值0.5:0.05:0.95
### 9.2 距离分段评测
对3D指标按不同距离段分别统计近距离、中距离、远距离
### 9.3 场景分类评测
可按不同场景(白天/夜晚、晴天/雨天等)分别评测
### 9.4 时序一致性评测
对视频序列评测跟踪一致性和稳定性
## 10. 注意事项
1. **坐标系统一**: 确保GT和DET使用相同的坐标系和图像尺寸
2. **类别映射**: 注意字符串类别名和数字ID的映射关系
3. **边界情况**: 处理空检测、空真值、图像尺寸不一致等情况
4. **性能优化**: 对于大规模数据,考虑并行处理和内存优化
5. **数值精度**: 3D指标计算注意浮点数精度问题
6. **可视化**: 提供检测结果和误差分布的可视化,便于分析