# RTK-IMU 标定：流程、结果与复现

本文是本次 G90 双天线 RTK 与 HI13 IMU 标定的工程入口。算法代码在 `rtk_imu/`，命令入口在 `tools/`，正式审核结果在 `artifacts/rtk_imu_calibration_v3/`。原始 `.rscap`、统一导出数据、状态快照与 checkpoint 均只保留本地，不提交仓库。

## 一句话结论

RTK-IMU 旋转外参由双天线基线方向与水平静止重力约束获得；平移外参以机械测量为绝对基准，再通过 RTK+IMU 联合状态图、高动态转弯/坡道数据、独立 held-out 数据和旋转扰动测试进行一致性验证。

当前动态数据不足以独立高精度求出完整 XYZ 杆臂，因此它不是 data-only translation calibration。机械测量给出了杆臂的绝对值；在未参与标定的数据、转弯等杆臂敏感运动以及旋转外参扰动测试中，均未发现该机械外参存在明显矛盾或不稳定性。当前结果的正确表述是：**机械杆臂锚定，并经动态数据一致性验证的工程候选**。

## 当前工程候选与状态

坐标约定：`l_I = p_ANT1^I`，即 ANT1（主天线、左侧）相位中心在 IMU 坐标系中的位置。车体/RTK 安装坐标为：+X 为 ANT1（左）到 ANT2（右），+Y 为车辆前进方向且与 IMU +Y 同向，+Z 向上；GGA 参考点为 ANT1 相位中心，离地 `1.916499878 m`。

- 固定旋转来源：`R2G_gravity_level_prior`。
- 固定旋转近似 RPY（X/Y/Z）：`[0.454°, -0.003°, 0.012°]`。
- 当前工程候选杆臂：`l_I = [-0.4518015159, -0.2644749820, 0.7314656115] m`。
- 变换约定：`p_RTK = R_RTK_IMU * p_IMU + t_RTK_IMU`。
- 对应候选 `T_RTK_IMU`（四舍五入到 6 位小数）：

```text
[[ 1.000000, -0.000214, -0.000044,  0.451777],
 [ 0.000214,  0.999969, -0.007929,  0.270363],
 [ 0.000046,  0.007929,  0.999969, -0.729325],
 [ 0.000000,  0.000000,  0.000000,  1.000000]]
```

当前门禁状态必须同时保留：

- `data_only_translation_accepted=false`：数据本身没有提供足够稳定的完整平移可观性。
- `engineering_translation_accepted=false`：独立传播验证仍存在公共加速度偏差，不能声称正式工程放行。
- `independent_extrinsic_sensitive_validation_passed=true`：杆臂敏感的高动态验证未发现机械杆臂冲突。
- `heldout_physical_validation_passed=true`、`rotation_sensitivity_passed=true`：固定候选在 held-out 物理残差和旋转扰动中保持一致。

因此不得描述为“data-only 标定平移”或“动态数据已精确细化机械杆臂”。完整、机器可读的结论见 [engineering_release_decision.json](artifacts/rtk_imu_calibration_v3/engineering_release_decision.json)。

## 求解流程

1. **统一原始数据导出。** 使用 G90/HI13 设备时间作为主时间轴；host receive time 仅用于诊断。G90 保留 GGA 质量、GNHPR 基线及质量、BESTNAVA Doppler velocity，必要时 PVTSLNA；HI13 保留 system time、gyro、accel、姿态/四元数和 host receive time。
2. **R0 连续性与质量控制。** 校验 checksum、RTK Fixed、设备时间单调性、IMU 覆盖、baseline jump 与测量间隔。轨迹连续性由 IMU 设备时间和预积分覆盖决定；孤立 HPR 缺失/Q5 只禁用或降权 HPR factor，不切断 IMU 轨迹。
3. **旋转外参。** R1b 从基线与 IMU 动态估计 ANT1→ANT2 在 IMU 中的 2DoF 方向；R2V 用基线与高质量 Doppler 速度作独立诊断；R2G 使用水平静止场地中的基线＋重力＋地面水平先验补齐完整旋转。R2G 是本次正式固定旋转来源，旧 GNHPR 三轴手眼只作诊断。
4. **节点状态图。** 每个 GNSS node 包含 `R,p,v,bg,ba` 的 15DoF 状态；相邻 node 由 covariance-whitened IMU preintegration 和 bias random walk 连接。BEST 约束 XYZ，GGA 仅在 BEST 缺失时约束 XY，Doppler 约束速度，HPR 是可选姿态/基线因子。
5. **机械锚定的平移验证。** 杆臂以机械值为基准，分别比较无先验 free、固定机械杆臂和软先验解。free 解只用于观测性诊断，不能因为数值收敛就替代机械值。
6. **独立验证。** 使用 circle、left-right、slope 的高动态非重叠窗口；再使用剩余 held-out 窗口、旋转 ±扰动敏感性、独立 innovation 和 propagation-bias root-cause audit 复核。

## 证据与限制

47 个非重叠标定窗口上的 free/fixed/prior 对比表明机械先验与数据拟合相容，但 posterior/prior 方差比没有显示足够的数据驱动细化，因此 `translation_refined_by_data=false`。剩余 267 个 frozen held-out 窗口的物理验证通过；然而独立传播创新在低速、低角速度区间同时出现位置和 Doppler 的同向偏差，等效为约 `0.20 m/s²` 的公共传播加速度误差。

该误差在 `|omega|` 很小时不能优先归因于杆臂速度项 `R*(omega × l)`，因此它不单独否决高动态杆臂敏感验证；但在传播模型根因关闭前，也不能把候选杆臂标为正式工程已放行。

主要审核证据：

- [47 窗口机械分支](artifacts/rtk_imu_calibration_v3/mechanical_prior_engineering_47_window.json)
- [held-out 验证](artifacts/rtk_imu_calibration_v3/mechanical_prior_engineering_heldout.json)
- [独立创新审计](artifacts/rtk_imu_calibration_v3/heldout_independent_innovation.json)
- [传播偏差根因审计](artifacts/rtk_imu_calibration_v3/propagation_bias_root_cause_audit.json)
- [旋转敏感性](artifacts/rtk_imu_calibration_v3/mechanical_prior_rotation_sensitivity.json)

## 如何复现

### 1. 准备环境和原始数据

```powershell
cd <repository-root>
python -m pip install -e ".[dev]"
```

将三批 G90/HI13 原始 `.rscap` 会话放到本机的数据位置。数据位置不写入仓库；`tools/export_rtk_imu_unified.py` 中的会话配对清单必须与实际采集文件一致。

```powershell
$OUT = "artifacts\rtk_imu_calibration_v3\reproduce"
python tools\export_rtk_imu_unified.py --output-root "$OUT\unified" --overwrite
$MANIFEST = "$OUT\unified\manifest.json"
```

导出后应检查每个会话目录中的 `export_summary.json`，并确认 `manifest.json` 中记录的传感器时间轴没有被 host receive time 替换。

### 2. 复现固定旋转

`<flat-static-session-id>` 必须是已确认地面水平、车辆静止的会话；可多次传入 `--level-static`。R2V 只作独立诊断，不替代 R2G。

```powershell
python tools\run_rtk_imu_multisource.py `
  --manifest $MANIFEST `
  --level-static <flat-static-session-id> `
  --output "$OUT\r2g_multisource.json"
```

检查输出的 R2G 旋转与本 README 的近似 RPY 一致后，将该旋转固定为后续 node graph 的 `--rotation-rpy-deg 0.454 -0.003 0.012`。若 R2G 不一致，应停止，先复核天线方向、场地水平和时间/轴定义，不应继续求杆臂。

### 3. 复现窗口选择与无先验诊断

从 circle、left-right、slope 三类会话中选择高质量窗口；窗口不可共享 IMU/GNSS/HPR 样本。仓库提交的 `lever_information_window_selection*.json` 是本次审核所用选择结果，可用于对照。

```powershell
python tools\select_rtk_imu_windows_by_lever_information.py `
  --manifest $MANIFEST `
  --circle-session <circle-session-id> `
  --left-right-session <left-right-session-id> `
  --slope-session <slope-session-id> `
  --output "$OUT\selection.json" `
  --rotation-rpy-deg 0.454 -0.003 0.012

python tools\run_rtk_imu_node_graph_free_selected.py `
  --manifest $MANIFEST `
  --selection "$OUT\selection.json" `
  --output "$OUT\free_baseline.json" `
  --start-name all `
  --rotation-rpy-deg 0.454 -0.003 0.012
```

free solve 的作用是输出边缘化杆臂信息、协方差、最弱方向和多初值稳定性；本次数据若仍未达到完整 XYZ 可观性，不得扩大无先验求解规模来强行放行。

### 4. 复现机械杆臂分支和 held-out 验证

固定工程候选杆臂并使用同一批非重叠标定窗口。`states.npz` 和 checkpoint-dir 是本地过程产物，应保持被 `.gitignore` 排除。

```powershell
python tools\run_rtk_imu_mechanical_prior_branch.py `
  --manifest $MANIFEST `
  --selection "$OUT\selection.json" `
  --free-baseline "$OUT\free_baseline.json" `
  --output "$OUT\mechanical_47_window.json" `
  --state-output "$OUT\states.npz" `
  --rotation-rpy-deg 0.454 -0.003 0.012

python tools\run_rtk_imu_mechanical_prior_heldout.py `
  --manifest $MANIFEST `
  --calibration-selection "$OUT\selection.json" `
  --all-selection <all-nonoverlapping-selection.json> `
  --engineering-result "$OUT\mechanical_47_window.json" `
  --output "$OUT\heldout.json" `
  --checkpoint-dir "$OUT\heldout_checkpoints" `
  --rotation-rpy-deg 0.454 -0.003 0.012
```

随后运行独立 innovation、传播根因审计和旋转敏感性。它们不重新优化杆臂，不应被用于调 covariance、R2G 或机械先验。

```powershell
python tools\audit_rtk_imu_heldout_innovation.py `
  --manifest $MANIFEST `
  --calibration-selection "$OUT\selection.json" `
  --all-selection <all-nonoverlapping-selection.json> `
  --engineering-result "$OUT\mechanical_47_window.json" `
  --output "$OUT\heldout_innovation.json" `
  --rotation-rpy-deg 0.454 -0.003 0.012

python tools\audit_rtk_imu_propagation_bias_root_cause.py `
  --manifest $MANIFEST `
  --calibration-selection "$OUT\selection.json" `
  --all-selection <all-nonoverlapping-selection.json> `
  --engineering-result "$OUT\mechanical_47_window.json" `
  --output "$OUT\propagation_bias_root_cause.json" `
  --rotation-rpy-deg 0.454 -0.003 0.012
```

最终仅汇总已生成的结果，不在 release 阶段重新拟合：

```powershell
python tools\finalize_rtk_imu_engineering_release.py `
  --calibration "$OUT\mechanical_47_window.json" `
  --heldout-postfit "$OUT\heldout.json" `
  --innovation "$OUT\heldout_innovation.json" `
  --sensitivity <rotation_sensitivity.json> `
  --convergence-retry <heldout_retry.json> `
  --propagation-root-cause "$OUT\propagation_bias_root_cause.json" `
  --output "$OUT\engineering_release_decision.json"
```

## 相关文件

- [RTK-IMU 代码包](rtk_imu/README.md)
- [多源导出与旋转 V3 说明](docs/rtk_imu_multisource_v3.md)
- [engineering 6DoF 说明](docs/rtk_imu_engineering_6dof.md)
- [历史链路审计](docs/rtk_imu_calibration.md)
- [正式结果索引](artifacts/rtk_imu_calibration_v3/README.md)
