历史快照
本专题页写于包拆分与 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: "Lightning 预处理与插件注册" description: >- Lightning 版 IMU/点云预处理体量较小(约 3KB 与 3KB),核心逻辑下沉在 thirdparty/lightning 库内;create.cpp 完成插件导出。
Lightning 预处理与插件注册
模块定位
src/asuka/src/asuka/odometry/lightning/ 目录共四个文件:主流程 odometry_estimation.cpp(23.3KB,另页详述)之外,剩下三个薄文件构成本页主题:
| 文件 | 体量 | 职责 |
|---|---|---|
imu_preprocess.cpp |
~2.9KB | 装配 thirdparty 的 ImuProcess,并在运行期做 asuka→lightning 类型桥接 |
cloud_preprocess.cpp |
~3.0KB | 两种雷达的点云滤波流水线,写相对时间到 offset |
create.cpp |
185B | extern "C" 工厂,把算法接入 dlopen 插件体系 |
这一组文件的设计取向非常明确:不实现算法,只做翻译和挂载。IMU 前向传播、去畸变、ESKF 状态估计全部下沉在 thirdparty/lightning/core/lio/(imu_processing.hpp 12KB、eskf.cc 12.7KB 等),asuka 侧只保留配置装配与数据结构转换的胶水代码。
IMU 预处理:构造期装配 + 运行期桥接
构造期:从两份配置装配库内处理器
ImuPreprocess 构造函数(imu_preprocess.cpp#L9-L41)持有 std::shared_ptr<::lightning::ImuProcess>(注意顶层命名空间的 ::lightning 是 thirdparty 库,与 asuka::lightning 区分),并完成三段装配:
- 传感器外参(
imu_preprocess.cpp):读config_sensor,经active_lidar_key()按config_ros.lidar_type选择livox/robosense雷达块;对应块不存在则直接throw std::runtime_error——配置错误在进程启动即暴露,而不是在首帧数据时才失败。外参缺省值为平移零、旋转单位阵,经SetExtrinsic注入。 - 噪声协方差(
imu_preprocess.cpp#L29-L36):gyr_cov/acc_cov缺省 0.1,b_gyr_cov/b_acc_cov缺省 0.0001,四个Vec3d分别注入。 - 库内滤波开关(
imu_preprocess.cpp#L38-L40):读config_odometry的odometry.imu_filter(缺省 false),决定 thirdparty 的imu_filter.h路径是否生效。
运行期:一次结构转换,一次委托
process()(imu_preprocess.cpp#L47-L64)做的事纯粹是类型翻译:
- 把 asuka 侧的
ImuMeasureGroup(lidar_beg_time/lidar_end_time/lidar/imu,见imu_preprocess.hpp#L25-L31)逐字段转成::lightning::MeasureGroup;scan_与scan_raw_共享同一指针,原始扫描副本交给库内自行处理。 - IMU 样本逐条转为
::lightning::IMU(timestamp/角速度/线加速度),随后一次性委托pimu->Process(lmeas, kf_state, pcl_out)。
三个值得注意的接口细节:
process不是基类虚函数(头文件中无override,且签名带::lightning::ESKF&)。它只能被同样依赖 lightning 库的odometry_estimation.cpp调用——ESKF 由主流程持有,预处理只是往里喂数据,这是算法私有接口而非框架契约。- 初始化职责完全在库内:
initialize(lidar_start_time)是空实现(imu_preprocess.cpp#L43-L45),is_initialized()直接委托pimu->IsIMUInited(),get_mean_acceleration()/get_mean_gyroscope()恒返回零向量。对比 FastLIO 那份 8.7KB、需要外部累计均值做重力对齐的 IMU 预处理,这是"逻辑下沉"最直观的证据。 q()暴露库内 12×12 噪声阵pimu->Q_(imu_preprocess.hpp#L45),供主流程配置 ESKF 时直接取用,是 asuka 侧预留的调参窗口。
点云预处理:一套参数、两条路径
构造函数(cloud_preprocess.cpp#L11-L18)从 config_odometry 读五个参数:preprocess.min_distance(缺省 0.1)、preprocess.point_filter_num(缺省 4)、preprocess.scan_line(缺省 32,读入 num_scans 但在所示代码路径中未使用,属配置兼容面)、roi.height_max/height_min(缺省 ±1.0)。
两条 preprocess 重载共享同一套滤波骨架,但细节有差异:
| 环节 | Livox 路径(L20-L63) | Robosense 路径(L65-L100) |
|---|---|---|
| 等间隔降采样 | i % point_filter_num == 0 才保留 |
同左 |
| 高度 ROI | 有 | 有(顺序在盲区之后) |
| 平方盲区 | range² > min_distance² |
同左 |
| 相邻重复点剔除 | 有(相邻三点差 < 1e-7,L41-L45) | 无 |
| offset 时间基准 | points[0].timestamp 作 timebase,(t - timebase)/1e6 |
传入的 stamp 作基准,(t - stamp)*1e3 |
两条路径换算常数方向相反(÷1e6 与 ×1e3),但都归一到毫秒量级写入 offset——这个字段承载每个点的相对时间,正是后续 thirdparty ImuProcess::Process 做运动补偿的输入。两者基准不同(首点时间戳 vs 扫描起始戳)是移植或对齐其他实现时最容易踩的坑;Livox 路径的 stamp 参数甚至被显式 (void) 丢弃。
共同收尾:width/height=1、is_dense=false(下游不应假设稠密)、debug 日志输出前后点数;空点云在入口即早退。另注意 output 是成员缓冲、每次调用 clear() 后复用并返回同一指针——调用方须在下次调用前消费完毕。
create.cpp:185 字节的最小插件样本
extern "C" asuka::OdometryEstimation* create_odometry_estimation() {
return new asuka::lightning::OdometryEstimation();
}
全部内容如上(create.cpp#L1-L6)。OdometryEstimation::load_module 以 dlopen 打开 so 后,按符号名 create_odometry_estimation 解析此工厂,得到向上转型为基类指针的算法实例——五种实现统一遵循此模式,这里是理解插件边界的最小样例。
调用链总览
flowchart TB
subgraph PLUGIN["插件边界 dlopen/dlsym"]
FACT["create.cpp<br/>create_odometry_estimation"] -->|new| OE["asuka::lightning<br/>OdometryEstimation"]
end
ROS["AsukaROS::insert_frame<br/>lidar_type 分发 + time_offset"] --> CP["lightning::CloudPreprocess<br/>降采样/ROI/盲区/去重 + offset 相对时间"]
CP -->|"PointCloudT 入异步环形缓冲"| ASYNC["AsyncOdometryEstimation<br/>单 worker 逐条消费"]
ASYNC -->|"process_once 排空"| OE
OE -->|"ImuMeasureGroup → ::lightning::MeasureGroup"| IP["lightning::ImuPreprocess::process"]
IP --> P["::lightning::ImuProcess::Process<br/>thirdparty/core/lio"]
P --> KF["::lightning::ESKF<br/>eskf.cc"]
KF -->|"状态回流"| OE
CFG1["config_sensor<br/>雷达块/外参/协方差"] -.构造期装配.-> IP
CFG2["config_odometry<br/>preprocess/roi/imu_filter"] -.构造期装配.-> CP
CFG2 -.-> IP
关键节点解读:点云预处理发生在 ROS 层入队之前(AsukaROS 取出插件共享的 cloud_preprocess 先行调用),而 IMU 预处理发生在 worker 线程内、由主流程携带 ESKF 调用——两者位于链路的不同层,这正是 process 签名不对称的原因。虚线为构造期一次性配置流,实线为每帧数据流。
边界条件与扩展点
- 配置 fail-fast:
config_sensor无活动雷达块则构造抛异常;换雷达必须同步改配置块与lidar_key分支。 - Livox timebase 取首点:首点时间戳异常会污染整帧 offset。
scan_line参数悬空:读入未用,删除或启用它都是合理的清理点。- 扩展新雷达类型:
cloud_preprocess加一个重载 +imu_preprocess.cpp#L15-L20加一个else if分支 +config_sensor加配置块,三处缺一不可。 - 调参面:
imu_filter开关切换库内滤波路径;q()直通库内噪声阵;preprocess/roi两组参数控制点云密度与范围。
Sources: src/asuka/src/asuka/odometry/lightning/imu_preprocess.cpp#L1-L80 | src/asuka/src/asuka/odometry/lightning/cloud_preprocess.cpp#L1-L104 | src/asuka/src/asuka/odometry/lightning/create.cpp#L1-L6 | src/asuka/include/asuka/odometry/lightning/imu_preprocess.hpp#L1-L55
关联源码
src/asuka/src/asuka/odometry/lightning/imu_preprocess.cppsrc/asuka/src/asuka/odometry/lightning/cloud_preprocess.cppsrc/asuka/src/asuka/odometry/lightning/create.cpp