# origincar_birdseye 使用教程

`origincar_birdseye` 将车头斜向下安装的单目相机图像投影到地面鸟瞰平面，并提供两种输出：

- 车体视角：车辆固定在画布中，车辆前后移动时地面图像相对车辆流动。
- 地图视角：通过 TF 查询 `odom_combined -> base_footprint`，把每帧地面图像放到固定世界画布中。

运行时不需要棋盘格。棋盘格只在标定阶段用于得到相机内参以及 `camera_from_base_*` 外参。

## 1. 与 car_usb_cam 的关系

本包不会修改 `car_usb_cam`，也不会代替它控制相机。联合启动文件只是原样包含：

```text
car_usb_cam/launch/hobot_usb_cam.launch.py
```

并只传递该启动文件当前公开的 `camera_config` 参数。因此相机设备、分辨率、帧率、像素格式以及相机自身的标定矫正仍由下面的文件负责：

```text
car_usb_cam/config/usb_camera.yaml
car_usb_cam/config/usb_camera_calibration.yaml
```

当前实车检查到的 `usb_camera.yaml` 配置为：

```yaml
hobot_usb_cam:
  ros__parameters:
    frame_id: default_usb_cam
    framerate: 30
    image_height: 720
    image_width: 1280
    io_method: mmap
    pixel_format: mjpeg
    video_device: /dev/video0
    zero_copy: false
```

`hobot_usb_cam` 根据自己的参数和标定文件产生已矫正图像。当前 `pixel_format: mjpeg` 模式实测在 `/image` 发布 `sensor_msgs/msg/CompressedImage`；本包通过 `image_message_type: compressed` 订阅并解码 MJPEG。若相机改为发布 `sensor_msgs/msg/Image`，只需把该参数改成 `raw`。

本包默认配置 `input_is_rectified: true`，不会再次执行畸变矫正，避免双重矫正。

如果以后订阅的是未经矫正的原始图像，应将 `input_is_rectified` 改为 `false`，此时本包才使用 `fx/fy/cx/cy` 和 `distortion_coefficients` 执行 `cv::undistort`。

## 2. 数据流

```text
/dev/video0
  -> hobot_usb_cam + usb_camera.yaml + usb_camera_calibration.yaml
  -> /image（当前为已矫正 sensor_msgs/msg/CompressedImage）
  -> 本包解码 MJPEG
  -> birdseye_node
       -> /birdseye/vehicle/image
       -> /birdseye/world/image
            ^
            | TF: odom_combined -> base_footprint
```

世界视角缺少 TF 时不会影响车体视角输出；节点会跳过当前世界视角帧并节流打印警告。

## 3. 编译

WSL 开发环境：

```bash
cd /home/hikos/Yiliao2026
source /opt/ros/humble/setup.bash
colcon build --packages-select origincar_birdseye
source install/setup.bash
```

RDKx5 实车环境：

```bash
cd /home/sunrise/yiliao_ws
source /opt/tros/humble/setup.bash
colcon build --packages-select origincar_birdseye
source install/setup.bash
```

## 4. 无摄像头离线验证

离线启动会发布 YAML 指定尺寸的彩色网格图，并关闭世界视角：

```bash
ros2 launch origincar_birdseye offline_validation.launch.py
```

另开终端检查：

```bash
source /opt/ros/humble/setup.bash
source /home/hikos/Yiliao2026/install/setup.bash
ros2 topic info /birdseye/vehicle/image
ros2 topic echo /birdseye/vehicle/image --once --field width
ros2 topic echo /birdseye/vehicle/image --once --field height
```

离线图像发布器的输入话题和尺寸位于 `config/offline_camera.yaml`。它们必须与鸟瞰 YAML 对应参数一致。离线发布器使用原始 `sensor_msgs/msg/Image`，所以 `config/offline_birdseye_overrides.yaml` 会把 `image_message_type` 覆盖为 `raw`，并关闭世界视角。

## 5. 只启动鸟瞰节点

相机已由其他启动文件运行时：

```bash
ros2 launch origincar_birdseye birdseye_runtime.launch.py
```

指定另一份鸟瞰配置：

```bash
ros2 launch origincar_birdseye birdseye_runtime.launch.py \
  birdseye_config:=/absolute/path/to/birdseye.yaml
```

## 6. 联合启动 car_usb_cam 和鸟瞰节点

使用两个包的默认 YAML：

```bash
ros2 launch origincar_birdseye car_usb_birdseye.launch.py
```

使用自定义相机和鸟瞰配置：

```bash
ros2 launch origincar_birdseye car_usb_birdseye.launch.py \
  camera_config:=/absolute/path/to/usb_camera.yaml \
  birdseye_config:=/absolute/path/to/birdseye.yaml
```

`camera_config` 被原样传给 `hobot_usb_cam.launch.py`，本包不会读取或重写它。如果修改了相机输出尺寸，还必须同步修改鸟瞰 YAML 的 `input_width`、`input_height` 和相应内参。

## 7. 鸟瞰 YAML 参数

默认文件：`config/birdseye_defaults.yaml`。

| 参数 | 含义 |
|---|---|
| `image_topic` | 输入图像话题，可在 YAML 中修改 |
| `image_message_type` | `compressed` 订阅 `CompressedImage`；`raw` 订阅 `Image` |
| `input_width` / `input_height` | 标定所对应的输入图像尺寸；每帧会严格校验 |
| `input_is_rectified` | `true` 表示上游已经矫正，本包不再去畸变 |
| `vehicle_output_topic` | 车体视角输出话题 |
| `world_output_topic` | 地图视角输出话题 |
| `base_frame` | 车辆基准坐标系，默认 `base_footprint` |
| `world_frame` | 世界坐标系，默认 `odom_combined` |
| `enable_world_view` | 是否生成地图视角 |
| `fx/fy/cx/cy` | 与实际输入尺寸对应的相机内参 |
| `distortion_coefficients` | 原始图像畸变参数；仅 `input_is_rectified: false` 时使用 |
| `camera_from_base_rotation` | 从车体坐标到相机坐标的 3x3 旋转矩阵，按行展开 |
| `camera_from_base_translation` | 从车体坐标到相机坐标的平移向量，单位米 |
| `output_width/height` | 车体视角输出像素尺寸 |
| `meters_per_pixel` | 每像素代表的米数，两个输出共用 |
| `rear_meters` | 车体原点距车体视角画布下边缘的距离 |
| `left_meters` | 车体视角画布左边界对应的车体左侧距离 |
| `world_origin_x/y` | 世界画布局部原点在 `world_frame` 中的位置 |
| `world_width/height` | 世界视角输出像素尺寸 |
| `world_left_meters` | 世界局部原点距画布左边缘的距离 |
| `world_rear_meters` | 世界局部原点距画布下边缘的距离 |

输入话题和尺寸只需修改 YAML，例如：

```yaml
birdseye_node:
  ros__parameters:
    image_topic: /front_camera/rectified_image
    image_message_type: compressed
    input_width: 1920
    input_height: 1080
    input_is_rectified: true
```

注意：改变话题只会改变鸟瞰节点的订阅目标，不会擅自改变 `car_usb_cam` 的发布话题。上游必须已经发布同名话题，或由系统级 ROS remap 将其映射到该名称。

## 8. 分辨率与标定的约束

下面四项必须一致：

1. `car_usb_cam` 实际发布图像的宽高；
2. `birdseye_defaults.yaml` 的 `input_width/input_height`；
3. `fx/fy/cx/cy` 所属的标定分辨率；
4. `camera_from_base_*` 标定时所使用的成像模式和安装姿态。

收到尺寸不一致的图像时，本包不会继续投影，而是打印类似错误：

```text
input image size mismatch: expected 1280x720, received 800x450
```

默认内参是把项目中原来的 `800x450` 内参按相同 16:9 视场线性缩放到 `1280x720` 的过渡值。它可用于软件联调，但实车最终精度应以同一相机模式下重新确认的矫正后内参为准。

## 9. 标定到运行的流程

1. 固定相机在车头，铺设地面棋盘格。
2. 用最终运行时相同的 `car_usb_cam` 分辨率、格式和矫正设置采集图像。
3. 获取矫正后图像对应的 `fx/fy/cx/cy`。
4. 运行 `camera_to_base_calibrator`，由棋盘格计算固定的 `T_camera_from_base`。
5. 把结果写入 `birdseye_defaults.yaml`，检查实时鸟瞰图中的直线、比例和车辆位置。
6. 固定结果后移除棋盘格；运行阶段只需要相机图像和地图模式所需的 TF。

`camera_to_base_calibrator` 的职责只是求相机相对于车体/地面的固定外参。它不需要在正常行驶时持续运行；相机安装没有松动时也不需要反复拍棋盘格。

## 10. 两种运行模式

### 车体居中模式

设置：

```yaml
enable_world_view: false
```

使用 `/birdseye/vehicle/image`。车辆矩形始终位于固定像素位置，地面随车辆运动向后或向前流动。

### 地图模式

设置：

```yaml
enable_world_view: true
world_frame: odom_combined
base_frame: base_footprint
```

系统必须提供可查询的 TF：

```bash
ros2 run tf2_ros tf2_echo odom_combined base_footprint
```

节点按每帧图像时间戳查询车辆位置和航向，再把该帧映射到 `/birdseye/world/image`。

## 11. 话题检查

```bash
ros2 topic list
ros2 topic info /image -v
ros2 topic hz /image
ros2 topic info /birdseye/vehicle/image -v
ros2 topic info /birdseye/world/image -v
```

主要话题：

| 默认话题 | 类型 | 方向 |
|---|---|---|
| `/image` | 当前为 `sensor_msgs/msg/CompressedImage` | 输入 |
| `/birdseye/vehicle/image` | `sensor_msgs/msg/Image` | 输出 |
| `/birdseye/world/image` | `sensor_msgs/msg/Image` | 输出 |
| `/tf`、`/tf_static` | `tf2_msgs/msg/TFMessage` | 地图模式输入 |

## 12. 测试

```bash
cd /home/hikos/Yiliao2026
source /opt/ros/humble/setup.bash
colcon build --packages-select origincar_birdseye
colcon test --packages-select origincar_birdseye
colcon test-result --test-result-base build/origincar_birdseye --verbose
```

## 13. 常见问题

### 没有收到图像

确认 `image_topic` 与实际话题完全一致，并用 `ros2 topic info <话题> -v` 检查类型。发布 `sensor_msgs/msg/CompressedImage` 时使用 `image_message_type: compressed`；发布 `sensor_msgs/msg/Image` 时使用 `raw`。WebSocket 数据不能作为输入。

### 报输入尺寸不一致

原始 `Image` 可直接查看 `width/height`；`CompressedImage` 不携带独立宽高字段，本包解码第一帧后会打印 `accepted input frame size=...`。让相机配置、鸟瞰输入尺寸和内参使用同一分辨率，不能只改宽高而保留另一分辨率的内参。

### 鸟瞰图扭曲或地面直线不直

检查是否重复矫正、内参是否对应当前分辨率，以及 `camera_from_base_*` 是否来自相机当前安装姿态。使用 `car_usb_cam` 已矫正输出时应保持 `input_is_rectified: true`。

### 车体视角正常但没有世界视角

检查 `enable_world_view`，然后确认图像时间戳处存在 `odom_combined -> base_footprint` TF。没有 TF 时节点会明确输出 `world view skipped`。

### `/dev/video0` 打不开

通常是另一个相机进程占用了设备。不要同时启动两份 `hobot_usb_cam`；联合启动前先停止旧相机节点。

### 输出大面积黑色

检查外参旋转/平移方向、相机是否真的朝向地面，以及参考地面范围是否落在相机 FOV 内。当前默认外参只是离线近似值，实车必须使用 `camera_to_base_calibrator` 的最终结果。
