Files
yolov26_3d/eval_tools/docs/ROI_GT_PROCESSING_GUIDE.md

438 lines
10 KiB
Markdown
Raw Normal View History

2026-06-24 09:35:46 +08:00
# 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. 图像尺寸配置是否正确
### 问题3GT数量为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配置