历史快照
本专题页写于包拆分与 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: "通用预处理接口 cloud/imu preprocess" description: >- cloud_preprocess 与 imu_preprocess 抽象基类;各算法子目录提供同名定制实现,ROS 层只依赖该接口,使算法私有的预处理与主链路解耦。
通用预处理接口 cloud/imu preprocess
模块职责
本页的两个文件是 asuka::core 下的一对纯头文件抽象基类,构成 ROS 封装层与五种里程计算法之间关于"预处理"的稳定接缝:
CloudPreprocess(cloud_preprocess.hpp):把雷达原生点型的原始云(LivoxPoint/RobosensePoint,定义在core/types.hpp)转换为全链路统一的PointCloudT::Ptr。ROS 层只认它,具体"怎么降采样、怎么滤距离、怎么提特征"全部由算法自己说了算。ImuPreprocess(imu_preprocess.hpp):算法私有的 IMU 数据承接与初始化状态查询接口,服务于各算法进入正式估计前的静止初始化/重力对齐阶段。
两个类的共同设计模式值得注意:
- 按需覆盖的虚函数契约:都不是纯虚接口——所有虚函数给出"无害默认体"(点云版返回
nullptr,IMU 版为空操作、返回false或零向量),子类只重写自己关心的方法,未覆盖部分自动退化为安全默认值。 - protected 构造函数(
CloudPreprocessL28、ImuPreprocessL22):禁止绕过子类直接实例化基类,接口本身不可作为对象存在。 - 零编译产物:
core目录的 cpp 只有async_odometry_estimation.cpp、callbacks.cpp、odometry_estimation.cpp三个,两个预处理接口完全以头文件内联实现,随包含方(ROS 层与各算法 so)各自编译,接口本体不产生独立目标文件。
与里程计插件契约的连接
解耦的关键交接点在 OdometryEstimation 基类上:
odometry_estimation.hppL6 直接包含cloud_preprocess.hpp,L29 声明virtual std::shared_ptr<CloudPreprocess> cloud_preprocess() { return nullptr; }——每个算法插件都被要求"可以"交出一个点云预处理器,默认交不出(返回空)。AsukaROS构造函数(asuka_ros.cppL27–L31)在load_module(so_name)动态装载算法后,立即执行cloud_preprocess = odometry->cloud_preprocess()。这是 ROS 层与算法私有预处理之间的唯一交接动作:实例化哪个预处理器、参数怎么配,决策权完全在算法插件内部;ROS 层从始至终只持有基类指针。
点云路径调用链与 IMU 路径对照
flowchart TD
subgraph ASM["装配阶段:AsukaROS 构造函数"]
A["config_odometry 读取 so_name"] --> B["OdometryEstimation::load_module<br/>dlopen 算法插件"]
B --> C["odometry->cloud_preprocess()<br/>向算法索取其私有预处理器"]
C --> D["ROS 层仅持有<br/>CloudPreprocess 基类指针"]
end
subgraph RUN["运行期:ROS 订阅回调线程内"]
E["insert_frame(PointCloud2)"] --> F{"lidar_type 分派"}
F -->|LIVOX| G["fromROSMsg 转为<br/>LivoxPoint 原始云"]
F -->|ROBOSENSE| H["fromROSMsg 转为<br/>RobosensePoint 原始云"]
G --> I["handle_loop_back 检查<br/>叠加 time_offset_lidar_to_imu"]
H --> I
I --> J["cloud_preprocess->preprocess<br/>虚调用进入算法私有实现"]
J --> K{"processed 非空?"}
K -->|是| L["async->insert_frame<br/>写入异步队列"]
K -->|否| M["静默丢弃该帧"]
D -. 虚调用 .-> J
end
subgraph IMU["IMU 路径对照"]
N["insert_imu(ImuData)"] --> O["imu_time_offset / acc_scale 补偿"]
O --> P["async->insert_imu 直达算法<br/>ROS 层不接触 ImuPreprocess"]
end
关键节点解释:
- 装配阶段(A→D):解耦的实现机制一目了然——ROS 层不在编译期链接任何算法的预处理器,而是在运行期通过插件工厂取出实例;换算法只换 so,接口指针照常工作。
lidar_type分派(F):ROS 层只负责把 ROS 消息反序列化为两种原生点型之一,随后调用对应的重载版本的preprocess;点型相关的分发在接口层就通过两个重载固化下来。- 时间补偿先于预处理(I):
preprocess收到的已经是stamp + time_offset_lidar_to_imu的调整后时间戳,预处理层看到的是对齐到 IMU 时基的时间,不应再自行做雷达-IMU 时间偏移。 - 虚调用点 J 与空判定 K:
asuka_ros.cppL93/L101 的cloud_preprocess ? ... : nullptr加if (!processed) return;是本接口的运行期收口——无论"没拿到预处理器"还是"预处理后为空",统一表现为该帧不入队。 - IMU 路径对照(N→P):ROS 的
insert_imu(L59–L70)只做回环检查与标定补偿后直入异步队列,从不调用ImuPreprocess的任何方法。
ImuPreprocess 的真实消费方
ImuPreprocess 的五个方法刻画了典型的 LIO 静止初始化状态机:
| 方法 | 语义 |
|---|---|
insert_imu(imu) |
算法私有地承接 IMU 数据(如存入自己的缓冲) |
initialize(lidar_start_time) |
以首帧雷达时间戳锚定初始化窗口的截止点 |
is_initialized() |
初始化是否完成的门控状态 |
get_mean_acceleration() / get_mean_gyroscope() |
初始化窗口内的加速度/陀螺均值,供重力方向与零偏估计使用 |
由于 ROS 层完全不引用该接口,其消费方只能是各算法的 odometry_estimation 内部——这与目录结构一致:五个算法子目录各自携带同名 imu_preprocess.hpp/cpp。"算法内部消费"这一点是从接口设计与目录布局推出的结论,与各算法主页的内容相互印证。
五种算法实现矩阵
| 算法目录 | cloud_preprocess.cpp | imu_preprocess.cpp | 体量对比反映的取舍 |
|---|---|---|---|
| batchlio | 5319 B | 3202 B | 参数最全:min_distance=0.5、max_distance=300.0、point_filter_num=2、num_scans=6、scan_rate=10,并按点型分派 build_livox_surf / build_robosense_surf 两条构造路径 |
| fastlio | 4198 B | 8693 B | IMU 预处理为各实现中最重,前向传播与时间同步职责下沉于此 |
| lightning | 3015 B | 2897 B | 两边都不重,核心逻辑沉在 thirdparty/lightning 库内 |
| smallpointlio | 6360 B | 709 B | 点云侧最重(特征处理),IMU 侧极简:仅覆盖 insert_imu,持有 circular_buffer 与 last_timestamp_imu |
| superlio | 2806 B | 1219 B | 点云预处理最轻,计算重心在 ESKF 与八叉体素地图 |
关键状态
- 接口层:
ImuPreprocess::is_initialized()(初始化门控)、均值查询返回的Vector3d(重力对齐输入)、initialize的lidar_start_time(初始化窗口锚点)。 - 实现层示例:smallpointlio 的
imu_deque(环形缓冲)与last_timestamp_imu(时序跟踪,初值 -1.0);batchlio 的五项预处理参数(成员默认值,构造时确定)。 - ROS 层:成员
cloud_preprocess(可能为空,全代码以三目运算防护);last_timestamp_lidar/imu(回环检测,先于预处理执行)。
边界条件
- 双重空值静默丢帧:
cloud_preprocess()返回空(算法未覆盖该虚函数)与preprocess返回空(过滤后无点)在 ROS 层表现为同一种行为——该帧不进异步队列,仅表现为workload()不增长,无任何告警。误配置算法时 symptoms 是"队列永远为空"。 - 点型重载不匹配:接口按 Livox/Robosense 提供两个重载;若配置切到某雷达类型而算法未覆盖对应重载,会落到基类默认体返回
nullptr,同样是静默失败。 - 预处理在回调线程同步执行:
preprocess发生在 ROS 订阅回调内、入队之前,其开销计入订阅线程而非里程计 worker;workload()只统计入队后的数据。 - 时间语义:预处理收到的已是叠加偏移后的时间戳;回环检查也在预处理之前完成,预处理层看不到原始 ROS 时间。
- partial override 合法:因为默认体无害(而非
= 0),子类可以只实现一个重载(如 smallpointlio 的 IMU 侧只覆盖insert_imu),编译期不强制完整实现。
扩展点
- 新增算法:在
odometry/<新算法>/下提供cloud_preprocess与imu_preprocess的同名子类实现,在其odometry_estimation中覆盖cloud_preprocess()交出实例,create.cpp导出create_odometry_estimation,改config_odometry的so_name即可切换——ROS 层与主链路零改动、零重编译。 - 预处理内部自由演化:接口只有 2 个(点云)或 5 个(IMU)方法且返回类型稳定,算法内部的滤波、特征提取、缓冲策略调整完全不波及主链路。
- 参数私有:预处理超参(如 batchlio 的五项)是各实现自己的状态,随各自算法配置文件加载,不进入
config_ros.json的 ROS 边界参数。
Sources:
- src/asuka/include/asuka/core/cloud_preprocess.hpp#L1-L32
- src/asuka/include/asuka/core/imu_preprocess.hpp#L1-L26
- src/asuka/include/asuka/core/odometry_estimation.hpp#L1-L40
- src/asuka/src/asuka/asuka_ros.cpp#L1-L186
- src/asuka/include/asuka/odometry/batchlio/cloud_preprocess.hpp#L1-L39
- src/asuka/include/asuka/odometry/smallpointlio/imu_preprocess.hpp#L1-L29
关联源码
src/asuka/include/asuka/core/cloud_preprocess.hppsrc/asuka/include/asuka/core/imu_preprocess.hpp