438 lines
10 KiB
Markdown
438 lines
10 KiB
Markdown
|
|
# 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配置
|