# 传感器标定数据合同

本文档固定真实车间传感器标定算法读取的数据入口。算法实现者不直接连相机、LiDAR、IMU 或机械臂驱动，只读取 `dataset_index.yaml` 或它转换出的 `data_input.*` ROS 参数。

## 数据索引字段

传感器数据统一写入：

```yaml
data_inputs:
  sensor:
    image_sample_files:
      - sensor/front_camera/images.yaml
    imu_sample_files:
      - sensor/imu/imu_samples.mcap
    pointcloud_sample_files:
      - sensor/lidar_3d/pointclouds.mcap
    laser_scan_sample_files:
      - sensor/lidar_2d/scans.mcap
    target_detection_files:
      - sensor/front_camera/charuco_detections.json
    robot_pose_sample_files:
      - sensor/hand_eye/robot_poses.csv
    synchronized_dataset_files:
      - dataset_index.yaml
```

字段含义：

- `image_sample_files`：相机图像列表、图像目录索引、rosbag 或 mcap。
- `imu_sample_files`：IMU 原始样本，必须能区分静止段和运动段。
- `pointcloud_sample_files`：3D LiDAR 点云样本或点云帧索引。
- `laser_scan_sample_files`：2D LiDAR LaserScan 样本或扫描帧索引。
- `target_detection_files`：棋盘格、ChArUco、AprilTag、LiDAR 平面、角点、球心等检测结果。
- `robot_pose_sample_files`：机械臂、平台或车辆位姿序列，主要用于手眼和联合外参。
- `synchronized_dataset_files`：已经做过时间同步的数据集索引。真实算法推荐优先读取这个入口。

## 文件内部格式

以下格式是现场落地的稳定输入合同。真实采集程序可以直接生成这些文件；如果现场已经有
rosbag、mcap 或厂商二进制文件，应额外生成这里定义的索引文件，把原始数据文件、时间戳、
传感器 ID 和 frame 信息关联起来。

### 图像索引 `images.yaml`

用于 `image_sample_files`。一个文件只描述一个相机的一组图像。

```yaml
schema_version: 1
sensor_id: demo_front_camera
sensor_type: front_camera
frame_id: front_camera_link
topic: /sensor/front_camera/image_raw
encoding: bgr8
image_width: 1920
image_height: 1080
target_board_id: front_camera_checkerboard
frames:
  - sequence: 0
    stamp_us: 1770000000000000
    file: images/front_camera_000000.png
    exposure_us: 8000
    gain: 1.0
    quality_score: 0.95
```

必需字段：

- 顶层：`schema_version`、`sensor_id`、`sensor_type`、`frame_id`、`frames`。
- 每帧：`stamp_us`、`file`。

约定：

- `file` 可以是相对 `dataset_root` 的路径，也可以是绝对路径。
- `sensor_type` 使用 `front_camera` 或 `downward_camera`。
- `quality_score` 范围为 0 到 1；采集端不知道时可以不填。

### 标定目标检测 `target_detections.json`

用于 `target_detection_files`。相机、2D LiDAR、3D LiDAR 的检测结果都放在同一类文件里，通过
`target_type` 和每帧的 `features` 区分。

```json
{
  "schema_version": 1,
  "sensor_id": "demo_front_camera",
  "sensor_type": "front_camera",
  "frame_id": "front_camera_link",
  "target_board_id": "front_camera_checkerboard",
  "target_type": "charuco",
  "detections": [
    {
      "stamp_us": 1770000000000000,
      "source_file": "images/front_camera_000000.png",
      "target_detected": true,
      "quality_score": 0.97,
      "corners": [
        {"id": 0, "u_px": 350.2, "v_px": 210.4},
        {"id": 1, "u_px": 410.1, "v_px": 211.0}
      ],
      "target_pose_in_sensor": {
        "translation_xyz_m": [0.8, 0.0, 0.2],
        "rotation_xyzw": [0.0, 0.0, 0.0, 1.0]
      }
    }
  ]
}
```

必需字段：

- 顶层：`schema_version`、`sensor_id`、`target_type`、`detections`。
- 每帧：`stamp_us`、`target_detected`。

常用 `target_type`：

- `checkerboard`
- `charuco`
- `apriltag`
- `lidar_corner`
- `lidar_plane`
- `sphere`

LiDAR 检测可使用 `features` 字段：

```json
{
  "features": {
    "corners_xy_m": [[1.0, 0.5], [1.0, -0.5]],
    "planes": [
      {"normal_xyz": [1.0, 0.0, 0.0], "offset_m": -1.2, "residual_m": 0.01}
    ],
    "sphere_centers_xyz_m": [[1.0, 0.0, 0.5]]
  }
}
```

### IMU 原始样本 `imu_samples.csv`

用于 `imu_sample_files`。CSV 表头固定：

```csv
stamp_us,sensor_id,frame_id,ax_mps2,ay_mps2,az_mps2,gx_rps,gy_rps,gz_rps,temperature_c
1770000000000000,demo_imu,imu_link,0.01,-0.02,9.81,0.001,0.000,0.002,32.5
```

必需列：

- `stamp_us`
- `sensor_id`
- `frame_id`
- `ax_mps2`
- `ay_mps2`
- `az_mps2`
- `gx_rps`
- `gy_rps`
- `gz_rps`

### IMU 分段 `imu_segments.yaml`

用于 `imu_sample_files`。同一字段可以同时登记 `imu_samples.csv` 和 `imu_segments.yaml`。

```yaml
schema_version: 1
sensor_id: demo_imu
frame_id: imu_link
source_file: imu/imu_samples.csv
segments:
  - segment_id: static_001
    segment_type: static
    start_us: 1770000000000000
    end_us: 1770000005000000
  - segment_id: motion_001
    segment_type: motion
    start_us: 1770000006000000
    end_us: 1770000012000000
```

`segment_type` 只允许 `static` 或 `motion`。

### 3D 点云索引 `pointclouds.yaml`

用于 `pointcloud_sample_files`。

```yaml
schema_version: 1
sensor_id: demo_lidar_3d
sensor_type: lidar_3d
frame_id: lidar_3d_link
topic: /sensor/lidar_3d/pointcloud
pointcloud_format: pcd
frames:
  - sequence: 0
    stamp_us: 1770000000000000
    file: lidar_3d/cloud_000000.pcd
    point_count: 120000
    quality_score: 0.92
```

必需字段：

- 顶层：`schema_version`、`sensor_id`、`frame_id`、`frames`。
- 每帧：`stamp_us`、`file`。

### 2D 激光扫描索引 `laser_scans.yaml`

用于 `laser_scan_sample_files`。

```yaml
schema_version: 1
sensor_id: demo_lidar_2d
sensor_type: lidar_2d
frame_id: lidar_2d_link
topic: /sensor/lidar_2d/scan
scan_format: csv
scans:
  - sequence: 0
    stamp_us: 1770000000000000
    file: lidar_2d/scan_000000.csv
    angle_min_rad: -3.14159
    angle_increment_rad: 0.00436
    range_min_m: 0.05
    range_max_m: 30.0
```

单帧扫描 CSV 推荐表头：

```csv
angle_rad,range_m,intensity
-1.57,2.31,120.0
```

### 机械臂或平台位姿 `robot_poses.csv`

用于 `robot_pose_sample_files`。CSV 表头固定：

```csv
stamp_us,pose_id,parent_frame_id,child_frame_id,x_m,y_m,z_m,qx,qy,qz,qw,source,quality_score
1770000000000000,pose_000,base_link,tool0,0.3,0.0,0.5,0.0,0.0,0.0,1.0,robot_controller,1.0
```

必需列：

- `stamp_us`
- `parent_frame_id`
- `child_frame_id`
- `x_m`
- `y_m`
- `z_m`
- `qx`
- `qy`
- `qz`
- `qw`

### 同步数据集 `synchronized_sensor_dataset.yaml`

用于 `synchronized_dataset_files`。它把同一时间窗口内的相机、IMU、LiDAR、位姿和检测结果关联起来。

```yaml
schema_version: 1
session_id: session_001
vehicle_id: agv_001
dataset_root: /data/agv_calib/site_a/session_001
time_base: unix_us
data_window_start_timestamp_us: 1770000000000000
data_window_end_timestamp_us: 1770000120000000
time_sync:
  max_sensor_time_offset_ms: 5.0
  source: ptp
files:
  image_sample_files:
    - sensor/front_camera/images.yaml
  imu_sample_files:
    - sensor/imu/imu_samples.csv
    - sensor/imu/imu_segments.yaml
  pointcloud_sample_files:
    - sensor/lidar_3d/pointclouds.yaml
  laser_scan_sample_files:
    - sensor/lidar_2d/laser_scans.yaml
  target_detection_files:
    - sensor/front_camera/charuco_detections.json
  robot_pose_sample_files:
    - sensor/hand_eye/robot_poses.csv
```

## 任务到数据要求

相机内参：

- 必需：`image_sample_files` 或 `synchronized_dataset_files`。
- 推荐：`target_detection_files`。
- 输出：`camera_intrinsics`。

IMU 内参：

- 必需：`imu_sample_files` 或 `synchronized_dataset_files`。
- 必须能区分静止段、运动段、采样频率和时间戳。
- 输出：`imu_intrinsics`。

传感器到 `base_link` 外参：

- 相机外参需要图像样本和标定目标检测结果。
- 2D LiDAR 外参需要 `laser_scan_sample_files` 和角点/平面/球心检测结果。
- 3D LiDAR 外参需要 `pointcloud_sample_files` 和平面/点云配准输入。
- IMU 外参通常需要 `imu_sample_files`，并需要底盘运动或外部真值轨迹辅助。
- 输出：`sensor_extrinsics`。

手眼标定：

- 必需：图像或检测结果，以及 `robot_pose_sample_files`。
- 输出：`hand_eye`。

## 输出参数文件

算法输出固定为：

```bash
<session_dir>/sensor/estimated_params.yaml
```

格式模板：

```bash
src/site_deployment/workshop_sensor_calibration_real/config/sensor_estimated_params_template.yaml
```

只有 `quality.data_quality_passed=true`、`quality.suitable_for_commit=true`，并且 `quality.validation_summary.auto_acceptance_passed=true` 的结果，才允许进入待提交包。
