# 基于共同匹配集的3D指标对比方案 ## 问题分析 ### 当前问题 两个模型评测时匹配的样本数量不一致: - **Model A**: 匹配 440,408 个 vehicle - **Model B**: 匹配 491,768 个 vehicle - 差异:51,360 个样本(11.7%) **导致的问题**: - 无法确定性能差异是因为**模型质量**还是**匹配了不同的目标** - 比较的不是相同目标的预测质量 ### 解决方案 只比较两个模型**都成功匹配到GT的目标**,确保对比的是相同目标的3D性能差异。 ## 实现方案 ### 方案架构 ``` ┌─────────────────┐ │ Model 1 Eval │ │ + Save Matches │──┐ └─────────────────┘ │ │ ┌──────────────────────┐ ├─→│ Common Match Finder │ │ └──────────────────────┘ ┌─────────────────┐ │ ↓ │ Model 2 Eval │ │ ┌──────────────────────┐ │ + Save Matches │──┘ │ Recompute 3D Stats │ └─────────────────┘ │ (Common Matches Only)│ └──────────────────────┘ ↓ ┌──────────────────────┐ │ Comparison Report │ └──────────────────────┘ ``` ### 数据结构设计 #### 1. 详细匹配数据文件 (`detailed_3d_matches.json`) ```json { "case_019b178d": { "frame_000018": { "vehicle": [ { "gt_id": "hash_of_gt_bbox", // 用于唯一标识GT "gt_bbox": [90.48, 561.98, 241.52, 714.00], "gt_center_3d": [-7.13, 0.91, 8.20], "det_bbox": [91.2, 562.5, 240.8, 713.2], "det_center_3d": [-7.25, 0.89, 8.35], "iou": 0.89, "confidence": 0.87478, "errors": { "lateral": 0.12, "longitudinal": 0.15, "heading": 0.05 }, "distance": { "longitudinal": 8.20, "lateral": -7.13 } } ] } } } ``` #### 2. 共同匹配索引 (`common_matches_index.json`) ```json { "statistics": { "model1_total": 440408, "model2_total": 491768, "common_matches": 425163, "model1_unique": 15245, "model2_unique": 66605, "common_percentage": 96.5 }, "common_matches": { "case_019b178d": { "frame_000018": { "vehicle": [ { "gt_id": "hash_of_gt_bbox", "model1_idx": 0, "model2_idx": 0 } ] } } } } ``` ### 实现步骤 #### 步骤1:扩展评测器保存详细匹配 修改 `eval_tools/evaluator/evaluator.py`: ```python def evaluate_3d(self): # ... 现有代码 ... # 新增:保存详细匹配信息 self.detailed_3d_matches = {} for case_name, case_pairs in cases.items(): self.detailed_3d_matches[case_name] = {} for pair in case_pairs: frame_name = pair['frame'] gts = self.gt_parser.parse_file(...) dets = self.det_parser.parse_file(...) self.detailed_3d_matches[case_name][frame_name] = {} for class_id in Metrics3D.CLASSES_3D: match_result = self.matcher.match(gts, dets, class_id) matches_list = [] for gt_idx, det_idx, iou in match_result['matches']: gt = match_result['gts_filtered'][gt_idx] det = match_result['dets_sorted'][det_idx] if gt['has_3d'] and det['3d_info']: # 计算3D误差 errors = self._compute_3d_errors(gt, det) # 保存匹配详情 matches_list.append({ 'gt_id': self._generate_gt_id(gt), 'gt_bbox': gt['bbox_2d'], 'det_bbox': det['bbox_2d'], 'errors': errors, 'distance': { 'longitudinal': gt['3d_info']['center'][2], 'lateral': gt['3d_info']['center'][0] } }) if matches_list: class_name = self.gt_parser.get_class_name(class_id) self.detailed_3d_matches[case_name][frame_name][class_name] = matches_list ``` #### 步骤2:创建共同匹配查找工具 新建 `eval_tools/find_common_matches.py`: ```python def find_common_matches(model1_matches, model2_matches): """ 找出两个模型共同匹配的GT目标 Args: model1_matches: Model 1的详细匹配数据 model2_matches: Model 2的详细匹配数据 Returns: common_matches: 共同匹配的索引 stats: 统计信息 """ common_matches = {} stats = { 'model1_total': 0, 'model2_total': 0, 'common': 0, 'model1_unique': 0, 'model2_unique': 0 } for case_name in model1_matches: if case_name not in model2_matches: continue common_matches[case_name] = {} for frame_name in model1_matches[case_name]: if frame_name not in model2_matches[case_name]: continue common_matches[case_name][frame_name] = {} for class_name in model1_matches[case_name][frame_name]: if class_name not in model2_matches[case_name][frame_name]: continue m1_list = model1_matches[case_name][frame_name][class_name] m2_list = model2_matches[case_name][frame_name][class_name] # 建立GT ID映射 m1_gt_ids = {m['gt_id']: i for i, m in enumerate(m1_list)} m2_gt_ids = {m['gt_id']: i for i, m in enumerate(m2_list)} # 找共同的GT ID common_gt_ids = set(m1_gt_ids.keys()) & set(m2_gt_ids.keys()) stats['model1_total'] += len(m1_list) stats['model2_total'] += len(m2_list) stats['common'] += len(common_gt_ids) stats['model1_unique'] += len(m1_gt_ids) - len(common_gt_ids) stats['model2_unique'] += len(m2_gt_ids) - len(common_gt_ids) # 保存共同匹配的索引 common_list = [] for gt_id in common_gt_ids: common_list.append({ 'gt_id': gt_id, 'model1_idx': m1_gt_ids[gt_id], 'model2_idx': m2_gt_ids[gt_id] }) if common_list: common_matches[case_name][frame_name][class_name] = common_list return common_matches, stats ``` #### 步骤3:扩展模型比较器 修改 `eval_tools/compare_models.py`: ```python class ModelComparator: def __init__(self, model1_report, model2_report, model1_matches=None, model2_matches=None, use_common_matches=False): """ Args: use_common_matches: 是否只比较共同匹配的样本 """ self.use_common_matches = use_common_matches if use_common_matches: # 找出共同匹配 self.common_matches, self.common_stats = find_common_matches( model1_matches, model2_matches ) # 基于共同匹配重新计算3D统计 self.model1_3d_filtered = self._recompute_3d_stats( model1_matches, 'model1' ) self.model2_3d_filtered = self._recompute_3d_stats( model2_matches, 'model2' ) def _recompute_3d_stats(self, matches_data, model_name): """基于共同匹配重新计算3D统计""" stats = {} for case_name, frames in self.common_matches.items(): for frame_name, classes in frames.items(): for class_name, common_list in classes.items(): if class_name not in stats: stats[class_name] = { 'lateral': [], 'longitudinal': [], 'heading': [] } for match_info in common_list: idx = match_info[f'{model_name}_idx'] match = matches_data[case_name][frame_name][class_name][idx] stats[class_name]['lateral'].append(match['errors']['lateral']) stats[class_name]['longitudinal'].append(match['errors']['longitudinal']) stats[class_name]['heading'].append(match['errors']['heading']) return stats ``` ### 使用流程 #### 1. 评测并保存详细匹配 ```bash # 评测Model 1 python eval_tools/eval.py \ --config eval_tools/configs/eval_config_mono3d.yaml \ --save-detailed-matches # 评测Model 2 python eval_tools/eval.py \ --config eval_tools/configs/eval_config_yolov5.yaml \ --save-detailed-matches ``` #### 2. 比较(基于共同匹配) ```bash python eval_tools/compare_models_with_common_matches.py \ --model1-report eval_results_multiprocess/mono3d/20260203_162537/evaluation_report.json \ --model1-matches eval_results_multiprocess/mono3d/20260203_162537/detailed_3d_matches.json \ --model2-report eval_results_multiprocess/yolov5s/20260203_161644/evaluation_report.json \ --model2-matches eval_results_multiprocess/yolov5s/20260203_161644/detailed_3d_matches.json \ --output-dir comparison_results_common_matches \ --use-common-matches ``` #### 3. 对比报告示例 ``` ================================================================================ 3D METRICS COMPARISON (COMMON MATCHES ONLY) ================================================================================ Match Statistics: Model 1 Total Matches: 440,408 Model 2 Total Matches: 491,768 Common Matches: 425,163 (96.5% of Model 1) Model 1 Unique: 15,245 (3.5%) Model 2 Unique: 66,605 (13.5%) VEHICLE (Common Matches: 420,158): Metric mono3d yolov5s-300w Diff Change % ------------------------------------------------------------------------------------ Lateral (m) 1.3251 1.2103 -0.1148 -8.66% ✓ Longitudinal (m) 2.5892 2.4231 -0.1661 -6.42% ✓ Heading (rad) 0.2256 0.3098 +0.0842 +37.32% ✗ Note: 这些统计基于两个模型都成功匹配的目标,排除了只被一个模型匹配的目标。 ``` ## 优势 1. **公平对比**:确保比较的是相同目标的预测质量 2. **性能分析**:可以单独分析各模型独有匹配的特点 3. **问题诊断**:识别哪些目标一个模型能匹配而另一个不能 ## 可选扩展 ### 1. 分析独有匹配 ```python def analyze_unique_matches(model1_matches, model2_matches, common_matches): """分析每个模型独有匹配的特征""" model1_unique = [] model2_unique = [] # ... 找出独有匹配 ... return { 'model1_unique_analysis': { 'count': len(model1_unique), 'avg_distance': ..., 'avg_iou': ..., 'difficulty': ... # 小目标、遮挡等 }, 'model2_unique_analysis': { ... } } ``` ### 2. 可视化差异 生成可视化图表展示: - 共同匹配 vs 独有匹配的分布 - 不同距离/横向位置的共同匹配率 - 独有匹配的特征分析 ## 实现优先级 1. **P0(必须)**:保存详细匹配信息 2. **P0(必须)**:找出共同匹配并重新计算统计 3. **P1(重要)**:生成基于共同匹配的对比报告 4. **P2(可选)**:分析独有匹配特征 5. **P3(可选)**:可视化工具 ## 兼容性 - **向后兼容**:默认不启用,保持现有行为 - **可选启用**:通过 `--use-common-matches` 标志启用 - **存储开销**:详细匹配文件约为评测报告的 5-10 倍大小