# 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配置
