# 模型输出评测方案设计 ## 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. **可视化**: 提供检测结果和误差分布的可视化,便于分析