# Camera–IMU 时间偏差说明

[English](README_TIMESHIFT_EN.md) | 中文

本文说明 Kalibr 输出的 camera–IMU 时间偏差从哪里产生、曝光时间为什么会改变标定值，以及系统运行时应如何使用该结果。

## 1. Kalibr 输出值的定义

Kalibr 在 `dataset-camchain-imucam.yaml` 中为每个相机输出：

```yaml
timeshift_cam_imu: 0.0027153517330281273
```

对应的时间关系是：

```text
t_imu = t_cam_header + timeshift_cam_imu
```

- `t_cam_header` 是 rosbag 中图像消息的 `header.stamp`。
- `t_imu` 是与该图像观测对应的 IMU 时间。
- 正值表示应在相机时间戳上增加该时移，才能与 IMU 时间对齐。
- `0.002715 s` 等于 `2.715 ms`。

这个值不是单纯的“线路延迟”。它同时包含固定时钟偏差、相机时间戳在曝光周期中的位置，以及传感器或驱动中的其他固定延迟。

## 2. 时间偏差的主要来源

### 2.1 相机与 IMU 的时钟域不同

如果相机、IMU 没有使用同一个硬件时钟，两个时钟的起点和频率会有偏差。即使设备启动时做过一次软件同步，仍可能存在固定偏移和随时间增长的漂移。

### 2.2 图像时间戳不一定代表曝光中点

一帧图像不是在一个瞬间完成的。驱动可能在以下任一时刻写入 `header.stamp`：

| 时间戳位置 | 图像的有效观测时刻相对 `header.stamp` |
|---|---:|
| 曝光开始 | `+ exposure / 2` |
| 曝光中点 | `0` |
| 曝光结束 | `- exposure / 2` |

Kalibr 使用图像中的标定板运动估计观测时刻，因此会把这部分差异一起估计到 `timeshift_cam_imu` 中。

### 2.3 曝光时间变化

如果时间戳记录在曝光开始，图像有效时刻位于曝光中点，则：

```text
timeshift_kalibr(E) = timeshift_fixed + E / 2
```

其中：

- `E` 是曝光时间；
- `timeshift_fixed` 是扣除曝光影响后的固定 camera–IMU 系统偏差。

因此曝光增加 `1 ms` 时，Kalibr 输出通常会增加约 `0.5 ms`。这不是外参发生变化，而是图像有效观测时刻发生了变化。

### 2.4 驱动、ISP、压缩和消息队列

如果图像时间戳来自接收时刻而不是硬件采样时刻，ISP、JPEG 压缩、USB/网络传输和 ROS 队列延迟也会进入标定结果。这些延迟可能随负载变化，表现为抖动，而不只是固定偏移。

IMU 端也可能存在数字滤波群延迟、批量打包和驱动时间戳位置差异。

### 2.5 多相机时间戳不一致

四路相机即使同时触发，也可能因为驱动赋时间戳顺序、队列或独立时钟而出现小差异。当前系统保留每路独立的时移参数，并要求最终四路差值不超过 `100 us`，不在优化器中强制四路完全相等。

## 3. e1 与 e2 数据的实测结果

两次标定使用相同的固定 EUCM 内参和 IMU 参数：

| 数据集 | 曝光 | cam0 | cam1 | cam2 | cam3 | 四路均值 | 四路范围 |
|---|---:|---:|---:|---:|---:|---:|---:|
| e1 | 1000 us | 2.067305 ms | 2.078715 ms | 2.102514 ms | 2.091259 ms | 2.084948 ms | 35.209 us |
| e2 | 2000 us | 2.715352 ms | 2.665352 ms | 2.665352 ms | 2.665659 ms | 2.677929 ms | 50.000 us |

两个数据集的原始均值相差：

```text
2.677929 ms - 2.084948 ms = 0.592980 ms
```

曝光从 `1 ms` 增加到 `2 ms`，若时间戳位于曝光开始，理论上的曝光中点变化是：

```text
(2 ms - 1 ms) / 2 = 0.500 ms
```

实测变化与这个模型基本一致。剩余约 `92.98 us` 来自两次采集的统计误差、运动轨迹差异、图像检测误差和系统时间抖动。

## 4. 扣除曝光后的固定时移

当前两组数据初步符合“图像时间戳位于曝光开始”的模型。在这个假设下使用：

```text
timeshift_fixed = timeshift_kalibr - exposure / 2
```

得到：

| 数据集 | 原始均值 | 减去半曝光 | 固定时移估计 |
|---|---:|---:|---:|
| e1 | 2.084948 ms | 0.500 ms | 1.584948 ms |
| e2 | 2.677929 ms | 1.000 ms | 1.677929 ms |

两次结果的简单均值约为：

```text
timeshift_fixed ≈ 1.631 ms
```

这可以作为当前设备的固定时移参考值。由于目前只有两个曝光档位，建议继续用更多曝光时间和重复数据验证，再确定量产配置值。

> 注意：只有在确认 `header.stamp` 表示曝光开始时，才能减去半曝光。如果驱动已经把时间戳修正到曝光中点，则不能再次减半曝光，否则会重复补偿。

## 5. 运行时如何使用

### 方案 A：保持原始曝光开始时间戳

如果图像 `header.stamp` 仍表示曝光开始，且每帧都能获得 `exposure_us`：

```text
t_image_effective = t_cam_header + exposure_us * 1e-6 / 2
t_imu_aligned     = t_image_effective + timeshift_fixed
```

使用当前参考值时：

```text
t_imu_aligned = t_cam_header + exposure_us * 1e-6 / 2 + 0.001631
```

### 方案 B：驱动直接发布曝光中点时间戳

推荐在驱动层把图像时间戳修正到曝光中点：

```text
t_cam_mid = t_cam_exposure_start + exposure_us * 1e-6 / 2
```

此时运行时只需要补偿固定偏差：

```text
t_imu_aligned = t_cam_mid + timeshift_fixed
```

### 方案 C：直接使用某次 Kalibr 原始结果

如果设备运行时的曝光时间与标定时完全一致，也可以直接使用对应 YAML 中的原始 `timeshift_cam_imu`：

```text
t_imu_aligned = t_cam_header + timeshift_cam_imu
```

这种方法不能直接跨曝光档位复用。曝光改变后需要重新标定，或者改用方案 A/B。

## 6. 如何进一步确认时间戳位置

使用至少 4 个曝光档位采集相同类型的充分旋转运动，分别进行标定，然后拟合：

```text
timeshift_kalibr(E) = a + k * E
```

- `k ≈ +0.5`：时间戳接近曝光开始；
- `k ≈ 0`：时间戳接近曝光中点；
- `k ≈ -0.5`：时间戳接近曝光结束；
- 斜率明显不稳定：可能存在软件接收时间戳、队列抖动或运动覆盖不足。

截距 `a` 是曝光归一化后的固定系统时移估计。

## 7. 结果验收建议

每次 camera–IMU 标定至少检查：

1. 四路 `timeshift_cam_imu` 最大差值不超过 `100 us`。
2. 时间偏差没有触及优化边界。
3. 不同曝光数据扣除正确的曝光项后，固定时移应保持稳定。
4. camera–IMU 旋转和平移在重复数据之间保持一致。
5. 重投影、陀螺仪和加速度计残差没有明显恶化。
6. rosbag 内四路图像使用同一曝光设置，并记录准确的 `exposure_us`。

本次 e1/e2 的空间外参重复性小于约 `0.1 deg / 0.9 mm`，四路时移范围分别为 `35.2 us` 和 `50.0 us`，因此结果整体有效。时间偏差随曝光变化的现象应在运行时按上述公式处理，而不应解释为相机与 IMU 的物理安装外参发生变化。
