# Ground Truth ROI Processing for Evaluation ## 概述 为了确保评测指标的准确性,评测时需要对Ground Truth(真值标签)进行与训练时相同的ROI过滤和截断处理。这确保了评测时GT和检测结果在同一坐标系下进行比较。 ## 问题背景 ### 训练时的处理 在模型训练时,数据经过以下ROI处理(参考 `utils/dataloaders3d.py`中的`post_process_labels_to_roi`): 1. **ROI计算**:基于标定参数计算灭点(vanishing point),以灭点为中心裁剪ROI区域 2. **标签过滤**:移除完全在ROI外的目标 3. **边界截断**:将部分在ROI内的目标边界框clip到ROI边界 4. **坐标转换**:将标签坐标从原图坐标系转换到ROI相对坐标系 5. **特殊处理**:对cut-in/cut-out目标进行特殊标记 ### 评测时的问题 之前的评测代码中: - **检测结果**:在ROI坐标系中(已经过ROI裁剪和resize) - **GT标签**:在原图坐标系中(未经ROI处理) - **结果**:坐标系不匹配,导致IoU为0,评测指标不准确 ## 解决方案 新增 `ROIProcessor` 模块,在评测时对GT标签进行与训练时相同的ROI处理。 ### 核心模块 #### 1. ROIProcessor 类 位置:`eval_tools/evaluator/roi_processor.py` **主要功能**: - 加载标定参数 - 计算ROI区域(基于灭点) - 对GT标签进行ROI过滤和截断 **关键方法**: ```python class ROIProcessor: def __init__(self, calib_root, roi_config, ori_img_size): """初始化ROI处理器 Args: calib_root: 标定文件根目录 roi_config: ROI配置([width, height]或[x1, y1, x2, y2]) ori_img_size: 原图尺寸 (width, height) """ def compute_roi(self, calib_params): """计算ROI区域 基于灭点计算规则: - crop_center_x = oriW // 2 - crop_center_y = cy - fy * tan(pitch) - ROI以crop_center为中心裁剪 """ def process_annotations_with_roi(self, annotations, roi_bounds): """对标注进行ROI过滤和截断 处理逻辑: 1. 移除完全在ROI外的目标 2. clip边界框到ROI范围 3. 转换坐标到ROI相对坐标系 """ ``` #### 2. Evaluator 更新 位置:`eval_tools/evaluator/evaluator.py` **主要更新**: - 集成 `ROIProcessor` - 在worker函数中对GT进行ROI处理 - 支持多进程评测时的ROI处理 ## 使用方法 ### 1. 配置文件方式(推荐) 创建配置文件(参考 `eval_tools/configs/eval_config_with_roi_gt.yaml`): ```yaml # Dataset paths dataset: det_path: "/path/to/detection_results" gt_path: "/path/to/ground_truth" # Image properties image: width: 1920 height: 1080 # ROI Ground Truth Processing roi_gt: enabled: true # 启用GT的ROI处理 calib_root: "/path/to/calibrations" # 标定文件根目录(包含各case的camera4.json) roi_config: [1920, 960] # ROI配置,必须与训练配置一致! # 其他配置... ``` 运行评测: ```bash python eval_tools/core/eval.py --config eval_tools/configs/eval_config_with_roi_gt.yaml ``` ### 2. ROI配置说明 #### 方式1:尺寸模式(常用) ```yaml roi_config: [1920, 960] # [width, height] ``` - ROI以灭点为中心裁剪 - width和height指定ROI的宽度和高度 - **必须与训练时yaml中的roi配置一致** 训练配置对应(`data/mono3d.yaml`): ```yaml # For ROI0 roi: [1920, 960] # 训练时配置 # For ROI1 # roi: [704, 352] # 训练时配置 ``` #### 方式2:边界模式 ```yaml roi_config: [0, 120, 1920, 1080] # [x1, y1, x2, y2] ``` - 固定的ROI边界 - 适用于已知固定ROI的情况 ### 3. 标定文件要求 ROI处理需要标定参数来计算灭点。标定文件结构: ``` calib_root/ ├── case1/ │ └── camera4.json # 标定文件 ├── case2/ │ └── camera4.json └── ... ``` 标定文件格式(`camera4.json`): ```json { "focal_u": 1450.0, "focal_v": 1450.0, "cu": 960.0, "cv": 540.0, "yaw": 0.0, "pitch": -5.0, "distort_coeffs": [0.1, 0.05, 0.02, 0.01] } ``` **关键参数**: - `focal_u`, `focal_v`: 焦距 - `cu`, `cv`: 主点坐标 - `pitch`: 俯仰角(用于计算灭点) ### 4. 完整评测示例 #### ROI0模型评测 ```bash python eval_tools/core/eval.py \ --config eval_tools/configs/eval_config_with_roi_gt.yaml \ --det-path /data/inference_results/roi0 \ --gt-path /data/ground_truth ``` 配置文件中: ```yaml roi_gt: enabled: true calib_root: "/data/ground_truth" roi_config: [1920, 960] # ROI0配置 ``` #### ROI1模型评测 配置文件中修改: ```yaml roi_gt: enabled: true calib_root: "/data/ground_truth" roi_config: [704, 352] # ROI1配置 ``` ## ROI处理流程 ### 1. GT标签ROI处理流程 ``` 原始GT标签(原图坐标系) ↓ 加载标定参数 ↓ 计算灭点位置 vanish_y = cy - fy * tan(pitch) crop_center = (oriW//2, vanish_y) ↓ 计算ROI区域 roi_x1 = crop_center_x - roi_width/2 roi_y1 = crop_center_y - roi_height/2 roi_x2 = roi_x1 + roi_width roi_y2 = roi_y1 + roi_height ↓ 过滤GT标签 1. 移除完全在ROI外的目标 2. 保留完全在ROI内的目标 3. 保留部分在ROI内的目标 ↓ 截断边界框 clip bbox到ROI边界 ↓ 坐标转换 转换到ROI相对坐标系 ↓ 处理后的GT标签(ROI坐标系) ↓ 与检测结果匹配和评测 ``` ### 2. 核心计算公式 #### 灭点计算 ```python vanish_y = cy - fy * tan(pitch * π/180) crop_center_x = image_width // 2 crop_center_y = vanish_y ``` #### ROI边界计算 ```python roi_x1 = crop_center_x - roi_width / 2 roi_y1 = crop_center_y - roi_height / 2 roi_x2 = roi_x1 + roi_width roi_y2 = roi_y1 + roi_height ``` #### 坐标转换 ```python # 原图坐标 -> ROI相对坐标 new_x1 = x1 - roi_x1 new_y1 = y1 - roi_y1 new_x2 = x2 - roi_x1 new_y2 = y2 - roi_y1 # Clip到ROI边界 new_x1 = clip(new_x1, 0, roi_width-1) new_y1 = clip(new_y1, 0, roi_height-1) new_x2 = clip(new_x2, 0, roi_width-1) new_y2 = clip(new_y2, 0, roi_height-1) ``` ## 实现细节 ### 1. 多进程支持 ROI处理支持多进程评测,每个worker进程独立创建ROI处理器: ```python @staticmethod def _process_frame_3d(pair, ...): from .roi_processor import ROIProcessor # 在worker中创建ROI处理器 if 'roi_processor_config' in pair: roi_processor = ROIProcessor(...) gts, _ = roi_processor.process_case_frame(...) ``` ### 2. 标定缓存 ROI处理器会缓存已加载的标定参数,避免重复读取: ```python self.calib_cache = {} # case_name -> calib_params ``` ### 3. 处理标记 处理后的标注会添加额外标记: ```python { 'bbox_2d': [x1, y1, x2, y2], # ROI相对坐标 'roi_relative': True, # 标记已转换 'roi_bounds': (x1, y1, x2, y2), # 记录ROI边界 'was_clipped': False, # 是否被clip '3d_info': { 'partially_visible': True # 3D目标是否部分可见 } } ``` ## 注意事项 ### 1. ROI配置一致性 **关键**:评测时的ROI配置必须与训练时完全一致! - 训练配置:`data/mono3d.yaml` 中的 `roi: [width, height]` - 评测配置:`eval_config_with_roi_gt.yaml` 中的 `roi_config: [width, height]` 不一致会导致评测结果不准确。 ### 2. 标定文件路径 确保 `calib_root` 指向正确的标定文件目录: ```python calib_root/ ├── case_name1/ │ └── camera4.json ├── case_name2/ │ └── camera4.json ``` ### 3. 坐标系说明 - **原图坐标系**:(0, 0)在左上角,范围[0, 1920]×[0, 1080] - **ROI坐标系**:(0, 0)在ROI左上角,范围[0, roi_width]×[0, roi_height] - **归一化坐标**:YOLO格式,范围[0, 1] ### 4. 边界情况处理 - 完全在ROI外的目标:直接过滤 - 部分在ROI内的目标:保留并clip到边界 - 3D信息处理:部分可见目标标记为 `partially_visible` ## 验证方法 ### 1. 检查ROI处理是否生效 运行评测时查看日志: ``` ROI processor enabled for GT filtering with config: [1920, 960] ``` ### 2. 对比有无ROI处理的结果 ```bash # 不使用ROI处理 python eval_tools/core/eval.py --config eval_config_no_roi.yaml # 使用ROI处理 python eval_tools/core/eval.py --config eval_config_with_roi_gt.yaml ``` 预期:使用ROI处理后,评测指标应该更准确(Precision和Recall不会异常低)。 ### 3. 验证GT数量 - ROI处理前:所有原图中的GT - ROI处理后:仅ROI内的GT(数量应该减少) 可以在代码中添加调试输出: ```python print(f"GT before ROI: {len(gts_before)}") print(f"GT after ROI: {len(gts_after)}") ``` ## 性能考虑 1. **标定缓存**:每个case的标定参数只加载一次 2. **多进程支持**:支持多进程并行评测 3. **内存优化**:按需加载和处理 ## 故障排查 ### 问题1:找不到标定文件 ``` Warning: Calibration file not found for case xxx ``` **解决**:检查 `calib_root` 路径和标定文件名(camera4.json) ### 问题2:评测结果仍然异常 **检查项**: 1. ROI配置是否与训练一致 2. 标定文件是否正确 3. 图像尺寸配置是否正确 ### 问题3:GT数量为0 **原因**:所有GT都在ROI外被过滤 **检查**:ROI配置是否过小或位置偏移 ## 示例脚本 ### Python API使用 ```python from eval_tools.evaluator import Evaluator, ROIProcessor # 创建ROI处理器 roi_processor = ROIProcessor( calib_root="/path/to/calibrations", roi_config=[1920, 960], ori_img_size=(1920, 1080) ) # 处理GT标签 annotations, roi_bounds = roi_processor.process_case_frame( case_name="case1", frame_name="frame001", annotations=gt_annotations ) # 创建评测器(配置版) config = { 'roi_gt': { 'enabled': True, 'calib_root': '/path/to/calibrations', 'roi_config': [1920, 960] } } evaluator = Evaluator(config=config) ``` ## 总结 通过引入 `ROIProcessor` 和更新评测流程,现在评测时的GT标签会经过与训练时相同的ROI处理,确保了: 1. ✅ GT和检测结果在同一坐标系 2. ✅ 完全在ROI外的目标被正确过滤 3. ✅ 部分在ROI内的目标被正确截断 4. ✅ 评测指标准确反映模型性能 **关键要点**: - 评测配置必须与训练配置一致 - 需要提供正确的标定文件 - 支持多进程和不同ROI配置