历史快照
本专题页写于包拆分与 FAST-LIVO2 / CustomMsg / 图像输入落地之前,反映当时的单包结构与
五前端状态。已知过时点:目录布局(apps/launch 现属 asuka_ros 包)、共享的
config_sensor.json(外参已改为各前端 odometry 配置的 sensor 块)、ROS 层 acc_scale
等描述、前端数量(现六种,新增 fastlivo)、CustomMsg 与图像输入链路、thirdparty 收纳范围(2026-09-17 起 faster_ivox / small_ivox / tsl 已移至各算法模块的 include 目录)。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: "项目总览与架构地图" description: >- 以 AsukaROS 为中枢梳理整体骨架:ROS 入口订阅 IMU 与点云后进入异步里程计队列,五种里程计算法以 so_name 动态装载为插件,配置与扩展模块在其外围。本页给出阅读整个仓库的主线索与模块分布。
项目总览与架构地图
AsukaROS 是一个以 ROS 封装层为中枢、算法插件为外沿 的激光-惯导里程计(LIO)框架。它的骨架可以概括为一句话:ROS 节点订阅 IMU 与点云两类话题,经标定补偿与回环检查后写入异步环形缓冲队列;由独立 worker 线程按到达序逐条投喂给通过 so_name 动态装载(dlopen)的五种里程计算法之一;配置系统、扩展模块与第三方库在其外围供装配。理解了这个中枢,整个仓库的其余页面都是围绕它的展开。
架构总图
flowchart TB
subgraph entry["入口层 apps/(三种进程形态,最终汇聚同一 AsukaROS)"]
direction LR
RN["asuka_rosnode<br/>独立节点"]
NL["asuka_nodelet"]
RB["asuka_rosbag<br/>离线回放"]
end
AR["AsukaROS(asuka_ros.cpp)<br/>话题接入 · IMU 标定补偿 · 雷达类型分发 · 时间回环检测"]
subgraph core["核心层 core/"]
AE["AsyncOdometryEstimation<br/>imu/frame 环形缓冲(1000/50)<br/>单 worker:按到达序逐条发射输入槽并喂入 → process_once 排空"]
OE["OdometryEstimation<br/>insert_imu / insert_frame / process_once /<br/>workload / stop / save_map /<br/>cloud_preprocess 虚函数契约"]
end
subgraph algo["算法插件层 odometry/(config_odometry 的 so_name 选择)"]
direction LR
B["batchlio"] --- F["fastlio"] --- L["lightning"] --- S["smallpointlio"] --- SL["superlio"]
end
subgraph ext["扩展模块层 modules/(config_extensions 的 loading_extension_modules 列表)"]
direction LR
IP["imu_prediction"] --- OL["optimization_loam"] --- OM["optimization_miao"] --- RV["rviz_viewer"]
end
subgraph infra["基础设施 utility/"]
direction LR
CF["config / GlobalConfig<br/>JSON 参数"] --- LM["load_module<br/>dlopen + 进程级句柄注册表"] --- CB["CallbackSlot<br/>事件解耦"] --- LG["logging<br/>spdlog"]
end
TP["thirdparty/<br/>fastlio(esekfom·mtk·ikd_tree) · lightning · miao · faster_ivox<br/>ankerl · sophus · nlohmann · pointlio · scancontext · small_ivox ..."]
entry --> AR
AR -->|"insert_imu / insert_frame"| AE
AE -->|"逐条投喂(先发射输入槽) + 排空"| OE
LM -->|"dlopen → create_odometry_estimation"| OE
OE -.-> algo
AR -->|"load_extensions()"| ext
CF -.-> AR
LM -.-> AR
algo -.-> TP
关键节点说明:
- 入口层:
asuka_rosnode.cpp是最小入口(33 行);asuka_nodelet.cpp与asuka_rosbag.cpp提供动态加载与离线回放两种形态,三者最终都落到同一套AsukaROS数据链路(详见专题页)。 - AsukaROS:所有传感器标定类修正(时间偏移、加速度尺度)被集中收敛在这一层,算法层无需感知;它同时是插件装载与扩展模块装载的装配点。
- AsyncOdometryEstimation:生产者-消费者枢纽,用互斥锁 + 条件变量维护 IMU/帧两条环形缓冲,按传感器时间交错逐条投喂,把 ROS 回调线程与算法计算线程解耦。
- OdometryEstimation:纯虚契约 +
static load_module工厂 +extern "C" create_odometry_estimation,构成算法动态插件的边界。构造函数中odometry->cloud_preprocess()把算法私有的预处理实例反向共享给 ROS 层使用。 - utility/:
load_module.cpp是 dlopen 的唯一通道(RTLD_LAZY,句柄永不关闭);CallbackSlot(8.3KB)是模块间事件订阅/发布的中枢原语。
主线索:一条数据的完整旅程
阅读代码时建议跟随三条链路,它们贯穿全部四个层次:
- IMU 链:
sensor_msgs::Imu→AsukaROS::insert_imu:转ImuData(stamp / linear_acc / angular_vel)→handle_loop_back(stamp, FrameId::IMU)回环检查 →stamp += imu_time_offset、linear_acc *= acc_scale→async->insert_imu入 imu_queue。 - 点云链:
sensor_msgs::PointCloud2→AsukaROS::insert_frame:按lidar_type分发 Livox / Robosense 两种自定义点 →pcl::fromROSMsg转 PCL →handle_loop_back(stamp, FrameId::LIDAR)→stamp += time_offset_lidar_to_imu→ 共享的cloud_preprocess->preprocess(...)(返回 null 则整帧丢弃)→async->insert_frame入队。 - 消费链:worker 线程在条件变量上等待 → 逐条取出事件并解锁 → 先发射
on_insert_imu/on_insert_frame再喂给算法(输入槽由执行器统一发射,三个链路槽同线程保持程序序)→while (odometry->process_once()) {}循环排空计算。
进程生命周期
sequenceDiagram
participant M as main(asuka_rosnode)
participant A as AsukaROS
participant Q as AsyncOdometryEstimation
participant O as OdometryEstimation 插件
participant E as 扩展模块
M->>A: 构造
A->>A: GlobalConfig 定位 config/ 目录
A->>O: load_module(config_odometry.so_name)
O-->>A: 实例 + cloud_preprocess()
A->>Q: make_unique(启动 worker 线程)
A->>E: load_extensions() 装载列表中每个 so
M->>M: 订阅 imu_topic / lidar_topic
loop ros::spin()
M->>A: insert_imu / insert_frame
A->>Q: 回环检查 + 标定补偿后入队
Q->>O: worker:逐条发射输入槽并喂入,process_once 排空
end
M->>A: wait(true) 轮询至 workload==0
M->>A: save(map_saving_path)
A->>Q: stop() 先 join(规避写图竞争)
A->>O: save_map() → mapping_时间戳.pcd
M->>A: 析构
A->>Q: stop()(幂等)
A->>E: stop() × N
A->>E: at_exit(map_saving_path) × N
时序要点:
- 装配顺序(构造函数 L19-43):定位 config 目录 → 初始化日志 → 读
config_odometry的so_name装载算法(失败即throw std::runtime_error)→ 取出共享cloud_preprocess→ 创建异步执行器 → 读 ROS 边界参数(lidar_type、acc_scale、imu_time_offset、time_offset_lidar_to_imu、map_saving_path)→ 最后装载扩展模块。 - 停机顺序(析构 L45-57):先
async->stop()停 odometry worker,再逐个ext->stop(),最后逐个ext->at_exit(map_saving_path)。注释明确:先停掉所有生产者,确保没有在途的槽发射后,模块才能退订回调并析构,避免与发射方竞争。 - save 的强制前置:
workload()只反映输入缓冲,不停 worker 就调用save_map会与正在process_scan()写map_cloud的线程发生数据竞争乃至堆损坏,因此save()第一步就是async->stop()(worker 被 join,析构中的 stop 幂等)。地图按mapping_%Y%m%d_%H%M%S.pcd命名,路径带扩展名则直接使用,否则建目录后落时间戳文件名,以 PCL 二进制写出。
模块分布地图
| 目录 | 职责 | 代表文件 |
|---|---|---|
apps/ |
三种进程形态入口(独立节点 / nodelet / rosbag) | asuka_rosnode.cpp(1.1KB 最小入口)等三个可执行目标 |
src/asuka + include/asuka(顶层) |
ROS 封装中枢:装配、输入适配、停机 | asuka_ros.cpp(6.8KB)/ asuka_ros.hpp |
core/ |
异步执行器、插件契约、公共类型、预处理抽象 | async_odometry_estimation.cpp、odometry_estimation.hpp、types.hpp |
odometry/ |
五种算法插件:batchlio / fastlio / lightning / smallpointlio / superlio | 各实现目录的 create.cpp 导出 create_odometry_estimation |
modules/ |
扩展模块:IMU 前向预测、LOAM 点面优化、miao 图优化、RViz 可视化 | optimization_loam.cpp(20.3KB,模块中最大) |
utility/ |
配置系统、dlopen 装载器、CallbackSlot、日志 | config.cpp、load_module.cpp、callback_slot.hpp(8.3KB) |
config/ |
JSON 配置层级与五种算法参数变体 | config.json(顶层)、config_ros.json、config_odometry/*.json、config_extensions.json |
launch/ |
三形态启动文件 + RViz 显示配置 | rosnode.launch / nodelet.launch / rosbag.launch / config.rviz |
thirdparty/ |
算法依赖库(各算法页详解) | fastlio(esekfom 78KB + ikd_tree 61KB)、lightning、miao、ankerl、sophus、nlohmann 等 |
关键状态一览
- AsukaROS 成员状态:
lidar_type/acc_scale/imu_time_offset/time_offset_lidar_to_imu/map_saving_path均在构造时一次性读入;last_timestamp_lidar/last_timestamp_imu初始为 -1,用于时间戳只增跟踪。 - AsyncOdometryEstimation 状态:三个原子标志
running(初值 true)、end_of_sequence、processing,加上两条容量固定的环形缓冲(imu_queue 1000、frame_queue 50)。 - 负载聚合:
workload() = 队列长度 + odometry->workload()(算法内部负载);wait_idle()以 10ms 轮询等待三条件同时成立——无输入、无 processing、算法负载为 0。wait(true)正是靠它支撑"队列清空再退出"。
边界条件与风险点
- 插件装载失败:
load_module返回空时构造函数直接抛异常,进程不会带病运行。 - 预处理丢弃:
cloud_preprocess返回 null 的帧被静默丢弃,不进入队列。 - 停机后的插入:
insert_imu/insert_frame在!running || end_of_sequence时直接返回——stop 之后的迟到数据被丢弃。 - 队列容量:两条环形缓冲均有界(1000/50),满载时覆盖该流最旧事件。
- 回环检测只告警:
handle_loop_back检测到 IMU 或激光时间回退(如 rosbag 循环播放)仅输出 warn,不丢数、不重置,是数据流入的第一道防线而非拦截器。 - save 竞争:见上文时序要点,
save()必须先 join worker,代码注释逐条说明了原因。 - 句柄永不关闭:
load_module.cpp的进程级open_handles注册表按 so 名缓存句柄,重复装载直接命中缓存;注释明确句柄永不dlclose、插件代码常驻进程,该注册表是未来卸载/重载实现的接缝。
扩展点
- 新增里程计算法:继承
OdometryEstimation实现虚函数契约,在create.cpp导出extern "C" create_odometry_estimation,新增一份config_odometry/*.json指定so_name与超参——切换算法只需改配置指向。 - 定制预处理:
CloudPreprocess/ImuPreprocess为抽象基类,各算法子目录提供同名定制实现,ROS 层只依赖接口。 - 新增扩展模块:
ExtensionModule与里程计基类同构(同为 dlopen 插件),把 so 名加入config_extensions.json的loading_extension_modules列表即可被装载,退出时统一收到stop()与at_exit(map_saving_path)。
推荐阅读顺序
apps/asuka_rosnode.cpp—— 33 行看懂进程生命周期;src/asuka/asuka_ros.cpp—— 装配顺序与输入适配、标定收敛点;core/async_odometry_estimation.cpp—— 并发模型与停机语义;core/odometry_estimation.hpp+utility/load_module.cpp—— 插件契约与装载机制;odometry/<选定算法>/—— 从create.cpp(最小样例)进入主实现,对照config_odometryJSON;modules/与thirdparty/—— 按需深入对应专题页。
Sources: src/asuka/apps/asuka_rosnode.cpp#L1-L33 | src/asuka/src/asuka/asuka_ros.cpp#L1-L186 | src/asuka/include/asuka/asuka_ros.hpp#L1-L64 | src/asuka/include/asuka/core/odometry_estimation.hpp#L1-L40 | src/asuka/include/asuka/core/async_odometry_estimation.hpp#L1-L45 | src/asuka/src/asuka/core/async_odometry_estimation.cpp#L1-L86 | src/asuka/src/asuka/utility/load_module.cpp#L1-L61
关联源码
src/asuka/src/asuka/asuka_ros.cppsrc/asuka/apps/asuka_rosnode.cppsrc/asuka/include/asuka/asuka_ros.hpp