# Dataset Profiling — YOLOv5-3D 数据画像统计分析工具

## 概述

`dataset_profiling.py` 是一个针对 YOLOv5-3D 训练和评测数据集的综合统计分析工具，能够自动生成数据集的完整"数据画像"（Data Portrait），包含文本报告、JSON 结构化数据和可视化图表。

---

## 功能模块

| # | 分析模块 | 说明 |
|---|---------|------|
| 1 | **数据集概览** | 图片/标签数量、标签格式分布（50列/18列/6列） |
| 2 | **类别统计** | 每类目标数量、占比、2D/3D 覆盖度 |
| 3 | **2D 框分析** | 尺寸分布、宽高比、位置热力图 |
| 4 | **3D 几何分析** | 深度、三维尺寸（L×H×W）、旋转角分布 |
| 5 | **面可见性分析** | 四面可见率、置信度分布（车辆/三轮车） |
| 6 | **截断类型分析** | cut_in / cut_out / noncut / partial_cut 分布 |
| 7 | **图像密度分析** | 每张图像的目标数、类别共现统计 |
| 8 | **数据质量检查** | NaN 字段、无效深度、异常深度、微小框检测 |
| 9 | **训练/验证对比** | 类别比例、深度分布、尺寸分布的 Train vs Val 对比 |

---

## 快速开始

```bash
# 基于原图的完整分析（默认模式）
python dataset_profiling.py

# 基于 ROI 的分析（匹配训练流水线）
python dataset_profiling.py --analysis-mode roi

# 仅分析验证集，限制 5000 张图
python dataset_profiling.py --split val --max-files 5000

# 同时指定模式和输出目录
python dataset_profiling.py --analysis-mode roi --split both --output-dir my_profiling
```

---

## 命令行参数

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `--data` | str | `data/mono3d.yaml` | 数据集配置 YAML 文件路径 |
| `--split` | str | `both` | 分析哪个数据集划分：`train`、`val` 或 `both` |
| `--max-files` | int | `0` | 每个 split 最多处理的图片数（0 = 全部） |
| `--output-dir` | str | `dataset_profiling_results` | 输出目录（自动追加 `_original` 或 `_roi` 后缀） |
| `--img-size` | int int | 自动 | 像素单位转换尺寸 `[宽 高]`；original 模式默认取 `ori_img_size`，roi 模式默认取 `roi` |
| `--analysis-mode` | str | `original` | 分析模式：`original`（原图坐标系）或 `roi`（ROI 坐标系） |

---

## 分析模式详解

### `original` 模式（默认）

直接使用标签文件中的归一化坐标，基于原始图像尺寸（默认 1920×1080）转换为像素单位进行统计。不做任何 ROI 裁剪或过滤。

适用场景：
- 了解原始标注数据的整体分布
- 检查标注质量和覆盖范围
- 不依赖标定参数的快速分析

### `roi` 模式

模拟训练流水线的 ROI 处理逻辑，从标定文件计算每个相机的实际 ROI 区域，对标签进行过滤和截断：

```
1. 读取相机标定参数（camera4.json）
2. 计算消失点：vanish_y = cv - fy × tan(pitch × π/180)
3. 计算 ROI 边界：以 (oriW/2, vanish_y) 为中心裁剪 roi_size 区域
4. 过滤完全在 ROI 外的目标
5. 截断跨越 ROI 边界的目标（设置 cut_in/cut_out）
6. 将坐标重新归一化到 ROI 空间
```

适用场景：
- 分析**模型实际训练时**看到的数据分布
- 评估 ROI 裁剪对数据量的影响
- 对比原图 vs ROI 的分布差异

#### ROI 计算流程图

```
原始图像 (1920×1080)
       │
       ▼
 读取 camera4.json
 (focal_v, cv, pitch)
       │
       ▼
 vanish_y = cv - fy × tan(pitch × π/180)
       │
       ▼
 ROI 中心 = (960, vanish_y)
 ROI 区域 = [roi_x1, roi_y1, roi_x2, roi_y2]  (1920×960)
       │
       ▼
 ┌─────────────┐
 │  原始标签    │
 │  (归一化坐标) │
 └──────┬──────┘
        │
   ┌────┴────┐
   ▼         ▼
 ROI 内    ROI 外 → 过滤丢弃
   │
   ├─ 完全在内 → 直接重归一化
   │
   └─ 部分跨界 → clip截断 + 设置cut类型
        │
        ▼
   ROI 空间归一化坐标
```

#### 标定文件路径推导

```
图像路径: .../sequence_name/images/xxx.png
标定路径: .../sequence_name/calib/L2_calib/camera4.json
```

同一序列目录下所有图像共享同一份标定，脚本会自动按序列目录缓存，避免重复读取。

---

## 输出文件说明

运行后在输出目录下按 split 组织，结构如下：

```
dataset_profiling_results_roi/
├── train/
│   ├── report.txt                # 文本格式完整报告
│   ├── summary.json              # JSON 格式结构化数据
│   ├── 01_overview.png           # 数据集概览（类别分布 + 格式分布 + 密度统计）
│   ├── 02_bbox2d_analysis.png    # 2D 框分析（尺寸散点 + 面积分布 + 位置热力图）
│   ├── 03_depth_analysis.png     # 深度分布（直方图 + CDF）
│   ├── 04_dimensions_3d.png      # 3D 尺寸分布（L/H/W 直方图）
│   ├── 05_rotation_analysis.png  # 旋转角分布（rot_y + alpha）
│   ├── 06_face_visibility_cut.png# 面可见性 + 截断类型分布
│   ├── 07_bev_scatter.png        # 鸟瞰图散点（X3D vs Z3D）
│   └── 08_depth_vs_area.png      # 深度-面积关系图
├── val/
│   └── ... (同上)
└── 09_train_vs_val.png           # Train vs Val 对比（类别比例 + 深度 + 尺寸）
```

### 可视化图表详解

| 图表 | 内容 | 用途 |
|------|------|------|
| `01_overview` | 类别数量条形图、标签格式饼图、每图目标数统计 | 快速了解数据集规模和组成 |
| `02_bbox2d_analysis` | Top6 类别的 W-H 散点图、面积直方图、中心位置热力图 | 分析目标大小和空间分布 |
| `03_depth_analysis` | 每个 3D 类别的深度直方图（含均值/中位数标线）和 CDF 曲线 | 分析目标距离分布 |
| `04_dimensions_3d` | 每个 3D 类别的 Length/Height/Width 直方图（含均值±标准差） | 分析目标物理尺寸 |
| `05_rotation_analysis` | rot_y 和 alpha 的角度直方图（标注前/后/左/右方位） | 分析朝向分布 |
| `06_face_visibility_cut` | 四面可见率条形图 + cut 类型饼图 | 分析面标注完整度和截断情况 |
| `07_bev_scatter` | X-Z 鸟瞰散点图 | 直观展示目标空间分布 |
| `08_depth_vs_area` | 深度 vs 2D 框面积散点图 | 验证深度-尺寸关系合理性 |
| `09_train_vs_val` | 类别比例、深度、尺寸的 Train/Val 双柱对比 | 检查训练集和验证集分布一致性 |

---

## 标签格式说明

脚本支持三种原始标签格式，统一解析为 **47 维内部表示**：

### 50 列格式（vehicle / tricycle）

带四面 3D 标注的完整格式：

```
cls x y w h x3d y3d z3d l h w rot_y xc yc ? alpha ? face_front(8)... face_rear(8)... face_left(8)... face_right(8)...
```

每个 face 包含 8 个值：`[x3d, y3d, z3d, alpha, xc, yc, score, is_visible]`

### 18 列格式（pedestrian / bicycle / rider）

带 3D 信息但无面标注：

```
cls x y w h x3d y3d z3d l h w rot_y xc yc ? alpha ? ...
```

### 6 列格式（2D-only 类别）

仅有 2D 框信息：

```
cls x y w h conf
```

### 47 维内部表示索引表

| 索引 | 字段 | 说明 |
|------|------|------|
| 0 | cls | 类别 ID |
| 1-4 | x, y, w, h | 2D 框（归一化 xywh） |
| 5-7 | x3d, y3d, z3d | 3D 中心坐标（米） |
| 8-10 | length, height, width | 3D 尺寸（米） |
| 11 | rot_y | 全局旋转角（弧度） |
| 12-13 | xc, yc | 3D 投影中心（归一化） |
| 14 | alpha | 观察角（弧度） |
| 15-22 | face_front | 前面 [x3d, y3d, z3d, alpha, xc, yc, score, is_visible] |
| 23-30 | face_rear | 后面 |
| 31-38 | face_left | 左面 |
| 39-46 | face_right | 右面 |

---

## 支持的类别

| ID | 类别名 | 标签格式 | 3D 信息 |
|----|--------|----------|---------|
| 0 | vehicle | 50 列 | 四面 3D |
| 1 | pedestrian | 18 列 | 整体 3D |
| 2 | bicycle | 18 列 | 整体 3D |
| 3 | rider | 18 列 | 整体 3D |
| 4 | roadblock | 6 列 | 无 |
| 5 | head | 6 列 | 无 |
| 6 | tsr | 6 列 | 无 |
| 7 | guideboard | 6 列 | 无 |
| 8 | plate | 6 列 | 无 |
| 9 | wheel | 6 列 | 无 |
| 10 | tl_border | 6 列 | 无 |
| 11 | tl_wick | 6 列 | 无 |
| 12 | tl_num | 6 列 | 无 |
| 13 | tricycle | 50 列 | 四面 3D |

---

## 截断类型（Cut Type）

当 ROI 裁剪导致车辆目标被部分截断时，根据旋转角 `rot_y` 判断截断类型：

| 类型 | 判定条件 | 面标注处理 |
|------|---------|-----------|
| **noncut** | 目标完全在 ROI 内，四面标注均有效 | 保持不变 |
| **cut_in** | `rot_y ∈ [-π, 0]`（驶入方向） | 保留前面，其余面置为 -1 |
| **cut_out** | `rot_y ∈ (0, π]`（驶出方向） | 保留后面，其余面置为 -1 |
| **partial_cut** | 其他部分截断情况 | 根据实际可见性处理 |

---

## 数据质量指标

| 指标 | 说明 | 关注阈值 |
|------|------|---------|
| `nan_3d_objects` | 3D 类别目标但 3D 字段为 NaN | 应为 0 |
| `invalid_depth_le0` | 深度 ≤ 0m 的目标 | 应尽量少 |
| `outlier_depth_gt200m` | 深度 > 200m 的目标 | 可能为标注错误 |
| `tiny_box_lt8px` | 2D 框宽或高 < 8px 的目标 | 训练时可能无法学习 |

---

## 数据配置（YAML）

脚本从 YAML 配置文件中读取以下关键字段：

```yaml
# data/mono3d.yaml 示例
path: /mnt/mono3d_nfs/xdzhu_data/Mono3d/Mono3d_4face_2m_g1m3

train:
  - /path/to/train_full.txt
  - /path/to/train_CNCAP.txt
val:
  - /path/to/val_3000.txt

ori_img_size: [1920, 1080]   # 原始图像尺寸 [W, H]
roi: [1920, 960]             # ROI 裁剪尺寸 [W, H]
virtual_fx: 537              # 虚拟焦距（ROI 模式下的参考值）
```

---

## 典型用例

### 1. 全量训练集画像

```bash
python dataset_profiling.py --split train --analysis-mode roi
```

### 2. 快速预览验证集

```bash
python dataset_profiling.py --split val --max-files 3000 --analysis-mode original
```

### 3. 对比 original vs roi 的分布差异

```bash
# 分别运行两种模式
python dataset_profiling.py --split val --analysis-mode original
python dataset_profiling.py --split val --analysis-mode roi

# 输出分别在:
#   dataset_profiling_results_original/val/
#   dataset_profiling_results_roi/val/
```

### 4. 自定义图像尺寸（模型输入分辨率）

```bash
# 按模型输入尺寸 704×352 做像素统计
python dataset_profiling.py --analysis-mode roi --img-size 704 352
```

---

## 代码架构

```
dataset_profiling.py
│
├── 常量定义
│   ├── CLASS_NAMES          # 14 类名称映射
│   ├── FACE_BASED_CLASSES   # 面标注类别 {vehicle, tricycle}
│   ├── ALL_3D_CLASSES       # 所有 3D 类别
│   └── IDX                  # 47 维内部索引表
│
├── 标签解析
│   ├── parse_label_file()         # 解析单个标签文件 → 47 维数组列表
│   ├── image_path_to_label_path() # 图片路径 → 标签路径
│   └── resolve_image_paths()      # 从 YAML 配置解析全部图片路径
│
├── ROI 处理（--analysis-mode roi 时启用）
│   ├── read_calib_for_image()         # 读取相机标定 camera4.json
│   ├── compute_roi_bounds()           # 从标定计算 ROI 边界
│   ├── get_roi_for_image()            # 按序列缓存的 ROI 查询
│   └── filter_clip_labels_to_roi()    # 过滤/截断/重归一化标签
│
├── 统计收集器
│   └── DatasetProfiler                # 核心分析类
│       ├── process_image()            # 处理单张图片
│       └── summarize()                # 汇总统计结果
│
├── 可视化（9 类图表）
│   ├── plot_overview()                # 01: 概览
│   ├── plot_2d_bbox_analysis()        # 02: 2D 框分析
│   ├── plot_3d_depth_analysis()       # 03: 深度分析
│   ├── plot_3d_dimensions()           # 04: 3D 尺寸
│   ├── plot_rotation_analysis()       # 05: 旋转角
│   ├── plot_face_visibility()         # 06: 面可见性
│   ├── plot_lateral_depth_scatter()   # 07: 鸟瞰图
│   ├── plot_depth_vs_box_size()       # 08: 深度-面积
│   └── plot_train_vs_val_comparison() # 09: Train vs Val
│
├── 报告生成
│   └── generate_text_report()         # 生成文本报告
│
└── 主流程
    ├── run_profiling()                # 单 split 分析流程
    └── main()                         # 入口（参数解析 + 调度）
```

---

## 依赖

- Python 3.8+
- NumPy
- Matplotlib
- PyYAML

均为 YOLOv5-3D 项目已有依赖，无需额外安装。
