Skip to content

历史快照

本专题页写于包拆分与 FAST-LIVO2 / CustomMsg / 图像输入落地之前,反映当时的单包结构与 五前端状态。已知过时点:目录布局(apps/launch 现属 asuka_ros 包)、共享的 config_sensor.json(外参已改为各前端 odometry 配置的 sensor 块)、ROS 层 acc_scale 等描述、前端数量(现六种,新增 fastlivo)、CustomMsg 与图像输入链路。2026-09 下旬以来:lightning / superodom 前端与 optimization_miao 后端已移除 (现五前端:fastlio / batchlio / superlio / fastlivo / srlivo;smallpointlio 已于 2026-10-23 移除,odometry/10-11 为其历史快照); freedom 扩展已整体迁出 asuka(移入 nagisa 包);KeyFrame::stamp 统一为扫描 起始时刻;执行器新增连续失败熔断与 wait_for() 排空检测、扩展与执行器有 ok() 健康查询。

现行事实以主文档 (index / architecture / parameters / odometry 等)与源码为准。


title: "IMU 接入与标定补偿" description: >- insert_imu 将 sensor_msgs::Imu 转为 ImuData,先做时间回环检查,再叠加 imu_time_offset 与 acc_scale 标定系数后交给异步队列;传感器标定类修正被集中收敛在 ROS 封装层,算法层无需感知。


IMU 接入与标定补偿

模块职责

本页覆盖 IMU 数据从 ROS 消息进入 asuka 框架的第一段旅程:AsukaROS::insert_imu(src/asuka/src/asuka/asuka_ros.cpp#L59-L70)。它承担三类职责,且只承担这三类:

  1. 类型转换:把 sensor_msgs::Imu 摊平为框架内部契约 ImuData(src/asuka/include/asuka/core/types.hpp#L20-L27),即 stamp(double 秒)+ linear_acc / angular_vel(Eigen::Vector3d)。
  2. 时间回环检查:调用 handle_loop_back(stamp, FrameId::IMU),对 rosbag 循环播放等时间回退场景发出告警。
  3. 标定补偿:叠加 imu_time_offset(时间偏移)与 acc_scale(加计尺度)两个系数。

关键设计取向是:所有传感器标定类修正被收敛在 ROS 封装层。五种里程计插件通过 OdometryEstimation::insert_imu(ImuData::ConstPtr) 接口收到的数据已经是补偿完毕的干净样本,切换 so_name 更换算法时无需重复实现任何标定逻辑;算法私有的进一步 IMU 处理(滤波、前向传播、时间同步)下沉在各算法自己的 imu_preprocess 中,与本层无关。

调用链

ros::Subscriber (imu_topic, queue=1000)          asuka_rosnode.cpp L19-21 / nodelet L61
        │  sensor_msgs::Imu::ConstPtr
        ▼
AsukaROS::insert_imu(msg)                        asuka_ros.cpp L59
        │ 1. make_shared<ImuData>,stamp = header.stamp.toSec()
        │ 2. handle_loop_back(stamp, FrameId::IMU)   ← 用补偿前的原始时间戳
        │ 3. 填充 linear_acc / angular_vel
        │ 4. stamp += imu_time_offset                ← 标定补偿 ①
        │ 5. linear_acc *= acc_scale                 ← 标定补偿 ②
        ▼
AsyncOdometryEstimation::insert_imu(ConstPtr)    async_odometry_estimation.cpp
        │  锁内检查 running / end_of_sequence,入 imu_queue,cv.notify_one
        ▼
worker 线程逐条取出(先发射 on_insert_imu)→ odometry->insert_imu(imu)
   (IMU/帧按到达序混排投递,复刻上游 ROS 回调执行器语义)

两个 ROS 入口(独立节点 asuka_rosnode.cpp 与 nodelet asuka_nodelet.cpp)都以 lambda 或成员函数直接转发到同一个 insert_imu,IMU 订阅队列长度为 1000(远高于点云的 50,对应 IMU 的高频特性)。

关键状态

状态 位置 作用
acc_scale{1.0} asuka_ros.hpp L52 加速度计尺度因子,构造时从 config_ros.json 的 "ros" 组读取(asuka_ros.cpp L37),默认 1.0 即直通
imu_time_offset{0.0} asuka_ros.hpp L53 IMU 时间偏移(秒),同处读取(L38),正值把时间戳向后推
last_timestamp_imu{-1.0} asuka_ros.hpp L58 回环检测的游标,记录最近一次 IMU 原始时间戳,-1.0 保证首条消息必通过
imu_queue / frame_queue(环形缓冲 1000/50) AsyncOdometryEstimation 互斥锁 + 条件变量保护,worker 按队首传感器时间交错逐条消费

值得注意的状态语义细节:handle_loop_back 在 imu->stamp += imu_time_offset 之前调用(L62 对比 L67),因此回环检测比较的是补偿前的原始传感器时间线,检测语义与消息流严格一致,不受补偿参数影响。

另一个易被忽略的点:acc_scale 只乘在 linear_acc 上(L68),angular_vel 原样传递——陀螺仪没有尺度修正是当前实现的有意取舍。

数据流图

flowchart TD
    A["sensor_msgs::Imu<br/>(imu_topic, queue=1000)"] --> B["AsukaROS::insert_imu<br/>asuka_ros.cpp L59"]
    B --> C["构造 ImuData::Ptr<br/>stamp = header.stamp.toSec()"]
    C --> D["handle_loop_back(stamp, FrameId::IMU)<br/>与 last_timestamp_imu 比较"]
    D --> E{"stamp < last_timestamp_imu ?"}
    E --> "是" --> F["logger->warn 'imu loop back detected'<br/>仅告警,数据不丢弃"]
    E --> "否" --> G["继续"]
    F --> G
    G --> H["填充 linear_acc / angular_vel"]
    H --> I["stamp += imu_time_offset<br/>标定补偿①: 时间偏移"]
    I --> J["linear_acc *= acc_scale<br/>标定补偿②: 加计尺度(陀螺不修正)"]
    J --> K["async->insert_imu(ConstPtr)"]
    K --> L{"running 且<br/>非 end_of_sequence ?"}
    L --> "否" --> M["静默丢弃<br/>async_odometry_estimation.cpp"]
    L --> "是" --> N["imu_queue.push_back<br/>cv.notify_one"]
    N --> O["worker 逐条取出<br/>先发射 on_insert_imu 再喂 odometry->insert_imu"]

图中关键节点:

  • D/E/F(回环闸口):这是一道“哨兵”而非“闸门”——检测到回退只输出 imu loop back detected: {旧} -> {新} 告警(asuka_ros.cpp L177-181),不丢帧、不重排、不重置队列,真正的乱序容错交给下游算法层。
  • I/J(标定补偿):紧邻数据从 ROS 类型转为内部类型的边界,在数据进入共享契约 ImuData 后、进入异步队列前完成,是全链路中唯一叠加这两个系数的位置。
  • L/M(停机保护):补偿后的数据交给异步层时才做生命周期检查;save()/析构触发 stop() 后到达的消息被静默丢弃,保证不与退出时序竞争。

主要文件

  • src/asuka/src/asuka/asuka_ros.cpp:insert_imu(L59-70)、handle_loop_back(L171-183)、参数读取(L37-38)全部在此,是本页的唯一核心实现文件。
  • src/asuka/include/asuka/asuka_ros.hpp:声明 acc_scale / imu_time_offset / last_timestamp_imu 三个成员状态(L52-58),类型即文档。
  • src/asuka/include/asuka/core/types.hpp:ImuData 结构(L20-27)是 ROS 层与算法插件之间的数据契约,只有时间戳加两个三维向量,刻意保持精简。
  • src/asuka/src/asuka/core/async_odometry_estimation.cpp:队列接收端与消费循环(逐条取出、先发射输入槽再喂算法)。
  • src/asuka/apps/asuka_rosnode.cpp、src/asuka/apps/asuka_nodelet.cpp:两种运行形态的订阅转发端。

边界条件

  1. 参数缺省即直通:acc_scale 默认 1.0、imu_time_offset 默认 0.0,配置缺失时本层退化为纯类型转换,不会引入意外畸变。
  2. 回环检测不拦截:告警后数据照常入队。若下游算法对时间戳单调性有硬假设,需在算法自身的 imu_preprocess 中处理,本层不兜底。
  3. 检测游标用原始时间戳:如上所述,last_timestamp_imu 记录的是补偿前的值;若把补偿挪到检测之前,rosbag 循环场景下的告警阈值语义会随参数漂移。
  4. 停机后消息静默丢弃:running=false 或 end_of_sequence=true 时队列端直接 return,这是退出时序安全的一部分。
  5. 无滤波、无外点剔除:本层不做任何信号质量判断,那些职责全部归属各算法目录下体量差异巨大的 imu_preprocess 实现(FastLIO 版 8.7KB 最重,SmallPointLIO 版仅 0.7KB),ROS 层保持薄封装。
  6. 共享所有权交接:ImuData::Ptr 以 ConstPtr 形式进入队列与算法,算法层只能读不能改,补偿结果不可被下游篡改。

扩展点

  • 新增标定项:若要引入陀螺尺度、轴间非正交、温度漂移等修正,按现有模式应在 insert_imu 中 handle_loop_back 之后、async->insert_imu 之前(即 L64-68 区间)插入,参数经 config_ros.json 的 "ros" 组传入并在构造函数(L34-40 附近)读取。
  • 上游改造不影响下游:由于算法只依赖 ImuData::ConstPtr 契约,未来把 IMU 接入改成 nodelet 内聚合、或多 IMU 融合,只要最终产出补偿后的 ImuData,五种算法插件零改动。
  • 反向扩展需动契约:若有算法需要未补偿的原始数据(例如自管时间同步),当前“ROS 层交付已补偿数据”的约定不满足,需要扩展 ImuData 字段或调整 OdometryEstimation 接口,属于跨层契约变更而非局部修改。

Sources: - src/asuka/src/asuka/asuka_ros.cpp#L59-L70 - src/asuka/include/asuka/asuka_ros.hpp#L29-L64 - src/asuka/include/asuka/core/types.hpp#L18-L57 - src/asuka/src/asuka/core/async_odometry_estimation.cpp#L17-L83 - src/asuka/apps/asuka_rosnode.cpp#L14-L28 - src/asuka/apps/asuka_nodelet.cpp#L61-L76

关联源码

  • src/asuka/src/asuka/asuka_ros.cpp