关于 Fast-LIO 和 Octomap 这套组合,我陆陆续续被问过太多次了,尤其是第一次用 Livox Mid-360 做室内巡检底盘的朋友。不是编不出来,就是地图乱七八糟,再就是好不容易建完图却不知道怎么存下来给导航用。这篇文章我按自己实际走过的流程写,从编译、参数配置到最终地图落盘,尽量把坑指出来,也把“为什么这么配”讲明白。
1. 这套组合到底解决什么问题
先说清楚 Fast-LIO 和 Octomap 各自扮演什么角色。Fast-LIO 是紧耦合的 LiDAR-Inertial Odometry,把激光雷达点云和 IMU 数据放在迭代卡尔曼滤波里做状态估计,实时输出高频位姿和去畸变后的点云。它对固态激光雷达支持得比较好,所以 Livox Mid-360 这类雷达很常见。
Octomap 是基于八叉树的三维占据栅格建图工具。点和点云不是地图,至少不是导航能直接用的地图。Octomap 会把空间切成不同分辨率的立方体,每个叶子节点存占据概率,既能表达障碍物,也能区分未知区域和空闲区域,而且内存占用比点云小很多。配合 Fast-LIO 输出点云做输入,就能在机器人移动过程中实时更新一张三维占据图。
这套方案适合谁?适合做室内巡检机器人、配送底盘、或者想给 move_base / navigation2 提供避障地图的开发者。对 ROS 基本概念要熟,至少知道话题、TF、launch 怎么一回事。如果这些基础没有,建议先把 ROS 入门过一遍再来碰 Fast-LIO,否则后面排查问题会有点痛苦。
我这个项目里雷达选的是 Mid-360,IMU 也是雷达内置的,所以不需要额外接 IMU。这也是很多人选择它的原因:一体机省事。但省事不代表没有坑,下面从编译开始说。
2. 编译和安装阶段的那些坑
2.1 依赖树怎么排
很多人一上来就把 Fast-LIO 源码 clone 进一个空工作空间,然后catkin_make,接着被报错刷了一屏。Fast-LIO 不是独立软件,它依赖雷达驱动、PCL、Eigen、以及一些 ROS 基础包。依赖关系没理清楚,后面每一步都会卡。
建议环境是 Ubuntu 20.04 + ROS Noetic,这是最省心的组合。Ubuntu 22.04 配 ROS2 也不是不行,但 Fast-LIO 的原版代码是基于 ROS1 写的,用 ROS2 版本需要改的东西多,排查难度也大。如果你不是非要上 ROS2,老老实实用 Noetic。
依赖分几类:
- Livox 雷达驱动:
livox_ros_driver2,这是必须最先编译的。 - 点云相关:
ros-noetic-pcl-ros、ros-noetic-eigen-conversions。 - 建图需要的库:Eigen 3.3.7 以上、PCL 1.10、OpenCV(一般系统自带)。
- 实时通信:
livox_ros_driver2内部会处理 /livox/lidar 和 /livox/imu 话题。
工作空间结构建议这样:
fastlio_ws/ ├── src/ │ ├── livox_ros_driver2 │ └── Fast-LIO先把工作空间建好,再把两个源码放进去。注意整个路径里不要有中文,我见过有人放在/home/用户/新桌面/下面,编译时各种莫名其妙的问题都出来了。
2.2 三个编译报错高频点
第一个高频报错是:
Could not find a package configuration file provided by "livox_ros_driver2"原因就是livox_ros_driver2没有先编译,或者没有source。Fast-LIO 的 CMakeLists 里会通过find_package(livox_ros_driver2 REQUIRED)找它,找不到自然报错。解决办法是先单独编译驱动包,确认没问题后,再整个工作空间一起编译。
第二个高频报错和 Eigen 版本有关。Ubuntu 22.04 自带的 Eigen 版本较新,和某些旧版本库混在一起时会出现类型不完整或者模板实例化错误。如果你坚持用 Noetic,基本不会碰到这个问题。如果碰到了,优先检查系统里是不是同时装了多个 Eigen 版本,删除多余版本或者统一软链接。
第三个坑是编译顺序。Fast-LIO 默认编译时会启动 OpenMP 和 NEON 相关选项,在 x86 上没事,在 ARM 开发板上需要确认编译器支持。树莓派或 RK3588 这类平台上,建议先把CATKIN_MAKE_JOBS调小一点,比如:
catkin_make -j4不然内存小的板子很容易编译到一半被 OOM kill,到时候你以为是代码问题,其实是内存爆了。
2.3 建议的编译顺序
我习惯分三步走,每一步都确认没有报错再继续:
# 第一步:只编译雷达驱动 cd ~/fastlio_ws catkin_make --pkg livox_ros_driver2 source devel/setup.bash # 第二步:编译 Fast-LIO catkin_make --pkg fast_lio source devel/setup.bash # 第三步:全量编译 catkin_make source devel/setup.bash分步编译的好处是报错时定位范围小,是驱动的问题还是 Fast-LIO 自己的问题一目了然。另外,Fast-LIO 的有些代码分支依赖livox_ros_driver2的特定版本,最好拉取官方 release 分支,不要用奇奇怪怪的 fork。把编译阶段稳定下来,后面调参才有意义。
3. 参数配置才是真正的“避坑核心”
3.1 Fast-LIO 的 yaml 参数逐项说
编译过了只能算开始,参数配置才是决定建图质量的核心。Fast-LIO 的配置在config目录下,我用的是 Mid-360,所以我改的是类似mid360.yaml的文件。贴一下我常用的配置,然后逐个解释为什么这么写。
common: lid_topic: "/livox/lidar" imu_topic: "/livox/imu" preprocess: lidar_type: 1 scan_line: 6 timestamp_unit: 0 blind: 1.0 det_range: 20.0 point_filter_num: 6 mapping: acc_cov: 0.1 gyr_cov: 0.1 b_acc_cov: 0.0001 b_gyr_cov: 0.0001 fov_degree: 180 extrinsic_est_en: false extrinsic_T: [0.041, 0.0, 0.018] extrinsic_R: [1, 0, 0, 0, 1, 0, 0, 0, 1]lidar_type: 1表示 Livox 类型,不是所有配置里都是这个值,原版代码对不同雷达有不同编号,选错会直接表现在点云处理异常上。scan_line: 6是 Mid-360 的非重复扫描虚拟线数,这是相对固定的,不用动。
timestamp_unit是时间戳单位,0 表示纳秒,1 表示微秒。这个很关键,如果雷达驱动输出的时间戳单位和你配的不一致,点云去畸变就是错的,地图会出现重影或者分层。Mid-360 默认走的是纳秒,所以填 0。雷达内部时间戳如果用的是微秒,必须改过来,我遇到过有人这个地方没配,画面一直在“旋转”。
blind: 1.0是盲区距离。激光雷达近处会有杂散点,特别是玻璃和镜面反射,给个 1 米盲区能过滤掉很大一部分近场噪音。det_range: 20.0是有效探测距离,如果你建图环境就是室内 10 米以内,建议直接设成12.0或者15.0,远点不但帮不了忙,还会把噪声点带进来。
point_filter_num: 6这个参数做的是点云抽稀,数值越大处理得越快,但地图精度也会下降。一开始可以设成 3,跑起来看 CPU 占用和地图效果再调。实时性优先就调大到 6,精度优先就调小到 2。
acc_cov和gyr_cov是 IMU 加速度计和陀螺仪的测量噪声协方差,b_acc_cov和b_gyr_cov是 IMU 零偏噪声协方差。Mid-360 内置的 IMU 型号相对固定,直接沿用默认值问题不大。如果你是外接 IMU,这里一定要查数据手册,不能套默认值。
3.2 Mid-360 外参与时间戳校正
extrinsic_T和extrinsic_R是 IMU 和激光雷达之间的外参。Mid-360 因为是集成方案,出厂时 IMU 相对雷达中心的位置是固定的,大多数时候很小,但不代表一定是零。这里有两种做法。
第一种是直接写官方提供的近似值,很多人的移动底盘上雷达是倒装或者斜装的,这个值不仅包括雷达自身 IMU 的外参,还包括雷达安装支架带来的旋转。第二种是跑一遍外参标定,比如用lidar_IMU_calib这类工具,先把结果算出来再填进去。
extrinsic_est_en: false的意思是暂时不启用在线外参估计。一开始调通流程时建议关掉,用固定外参,这样问题好定位。等建图基本稳定了,如果还觉得地图有系统性漂移,再考虑打开在线估计。
时间戳的问题容易被忽略。Fast-LIO 对点云时间和 IMU 时间的对齐要求很高。如果雷达驱动和 IMU 驱动不是同一个时钟源,时间同步误差会直接变成位姿漂移。Mid-360 一体机的好处是雷达和 IMU 在同一个硬件模块里,时间戳天然对齐,省了很多事。如果你外接 IMU,需要先跑tf2同步或者用 FIFO 做时间对齐,否则后端没法做紧耦合。
3.3 Octomap 参数接入注意点
Fast-LIO 输出点云之后,需要把/cloud_registered接到 Octomap_server 上。Octomap_server 是成熟的中继节点,launch 文件里要配置的参数不多,但每一项都有讲究。
<launch> <node pkg="octomap_server" type="octomap_server_node" name="octomap_server"> <param name="resolution" value="0.1"/> <remap from="cloud_in" to="/cloud_registered"/> <param name="frame_id" value="camera_init"/> <param name="sensor_model/max_range" value="12.0"/> <param name="sensor_model/hit" value="0.7"/> <param name="sensor_model/miss" value="0.4"/> <param name="sensor_model/min" value="0.1"/> <param name="sensor_model/max" value="0.99"/> </node> </launch>resolution是体素分辨率,0.1 表示 10 厘米一个格子。室内避障用 0.1 是均衡值,0.05 会更精细但内存和计算量是 0.1 的 8 倍,跑在低功耗小电脑上会很吃力。开放园区场景可以放宽到 0.15 甚至 0.2。
frame_id一定要和 Fast-LIO 发布的 TF 父坐标系一致,通常是camera_init。如果你这里写map,但 Fast-LIO 发布的 TF 根节点是camera_init,地图就会漂到天上去。
sensor_model/hit、sensor_model/miss是对数优势更新率,简单说就是“看到有障碍”和“看到是空”的更新系数。hit 调大一点会让地图对障碍物更敏感,miss 调大一点会让地图更容易把空间判成“可通过”。默认值能跑,但你在实际空旷环境跑,会发现有些地面被判定成占据,这时候适当把 miss 调大到 0.5 会改善。
sensor_model/max_range是雷达最大建图距离。这里我故意设成 12 米,不是雷达测不到更远,是远端点云噪声太多,建图时会生成一堆飘在外面的孤立体素。如果你要建大场景地图,这个值可以调高,但一定要配合体素滤波和噪声过滤。
4. 跑通整个建图链路
4.1 launch 启动顺序与作用
配置好的环境直接从启动开始。我习惯开三个终端,分别是雷达驱动、Fast-LIO、Octomap_server。
# 终端1:启动 Mid-360 驱动 roslaunch livox_ros_driver2 msg_MID360.launch # 终端2:启动 Fast-LIO roslaunch fast_lio mapping_mid360.launch # 终端3:启动 Octomap_server roslaunch octomap_server octomap.launch启动顺序有讲究。先启动雷达驱动,等/livox/lidar和/livox/imu话题稳定输出后再启动 Fast-LIO。Fast-LIO 初始化阶段如果收不到 IMU 数据,会一直等或者直接崩,所以两个话题都确认有数据再往下走。
Fast-LIO 的 launch 文件有的已经内置了雷达驱动,那就只需要启动mapping_mid360.launch。但分开启动更容易排查问题。可以在/livox/lidar话题上用rqt_topic或者rostopic hz看一眼频率,确认 Mid-360 在 10Hz 左右稳定输出,再继续。
4.2 话题和 TF 怎么对上
Fast-LIO 正常启动后,会发布这些关键话题:
/cloud_registered:去畸变后的注册点云,这是 Octomap_server 的输入。/cloud_registered_body:机体坐标系下的注册点云。- TF:
camera_init到body的位姿关系。 /Odometry:实时里程计。
Octomap_server 订阅/cloud_registered,并且需要知道点云是在哪个坐标系下。这个坐标系必须在 launch 里和 TF 树里的父坐标系对上,否则 Rviz 里地图会原地“跳”或者干脆不可见。
我第一次跑的时候 Octomap_server 的frame_id写了map,Fast-LIO 的 TF 是camera_init,结果地图和点云位置差了十万八千里。后来把frame_id改成camera_init,世界一下子清静了。
如果你要接 navigation,建议在 Fast-LIO 的 TF 树外部再加一层map -> camera_init的静态变换,这样导航栈的全局坐标系就能统一到map,不会和 Fast-LIO 内部的camera_init混淆。
4.3 实时性调优思路
建图实时性是很多人的痛点。Fast-LIO 本身计算量不小,加上 Octomap_server 更新地图,低功耗主控很容易跑不满。我实测下来,对实时性影响最大的几个因素是:
- 雷达点云频率和数量。
- Octomap resolution。
- 可视化负载。
- 点云抽稀参数。
如果你在 Rviz 里打开了 Grid 或者 PointCloud2 实时渲染,CPU 和 GPU 占用都会明显上升。建图过程中不要打开太多可视化插件,尤其是占用栅格地图的 “OccupancyGrid” 显示,它会持续吃 CPU。建议只开点云和 TF,确认没问题后再打开地图显示。
另一个办法是降低 Fast-LIO 的点云处理量。point_filter_num从 3 调到 6,建图精度损失一点,但帧率能提升不少。还有一个思路是把发送给 Octomap_server 的点云再做一次体素滤波,不过这里要小心,滤波会把细小障碍物抹掉,最好在 launch 外增加一个单独的过滤节点,不要把 Fast-LIO 的输出直接改得太狠。
如果是大场景,把 Octomap_server 的resolution从 0.1 调到 0.2,内存占用差不多能降到原来的 1/8,建图速度也会更快。室内导航场景一般 0.1 够用,室外大场景 0.2 更现实。
5. 地图保存与格式转换全流程
5.1 保存 Octomap 的两种方式
建图结束后必须把地图保存下来,否则一关进程全没了。Octomap_server 提供了两种保存方式。
第一种是命令行启动一个octomap_saver节点,它会自动订阅当前的 octomap 并写入文件:
rosrun octomap_server octomap_saver -f ~/map.ot第二种是直接调服务,很多时候更灵活:
rosservice call /octomap_server/save_map "{filename: '/home/user/map.ot'}"保存文件的后缀很重要。.ot是文本格式,.bt是二进制格式。同样一张地图,.bt文件体积小很多,加载速度也快。如果做长期存档,建议保存成.bt:
rosrun octomap_server octomap_saver -f ~/map.btOctomap_server 在保存时会把当前地图的占据概率一起写进文件。所以保存前不要把里程计或建图节点杀掉,等地图稳定后再保存,不要边运动边保存,不然文件里是一张“半成品”。
5.2 输出 2D 栅格地图给导航用
很多场景只需要 2D 栅格地图给 move_base 用,这时候可以订阅 Octomap_server 发布的/projected_map,然后用map_server里的map_saver保存:
rosrun map_server map_saver map:=/projected_map -f ~/map_2d保存后会生成map_2d.pgm和map_2d.yaml。.pgm是图像文件,.yaml是地图元信息,比如分辨率、原点坐标和占据阈值。
需要注意一点:/projected_map是 Octomap 在 Z 轴方向投影生成的 2D 占据栅格,它会把上方空间和下方空间合并处理。如果你的机器人需要在多层结构里移动,且有一层是镂空的,投影会把上下两层混在一起。这种情况更适合直接用 3D Octomap 做局部避障,2D 投影只用于全局路径规划。
5.3 二进制地图的加载和后续使用
保存好的.bt或.ot文件可以用于后续加载。最简单的验证方式是重新打开 Octomap_server,让它从文件加载:
rosrun octomap_server octomap_server_node map.bt加载成功后,它会发布对应的 octomap 和投影地图。如果你要用这张地图做全局规划,可以直接把/projected_map接到 move_base 的 global costmap,或者把地图转成 costmap 的静态层。
Octomap 文件本身也可以转回点云做可视化或再处理,常见命令是octomap-octree或者把/octomap_full话题保存成octomap_msgs/Octomap,再离线转换。转换的目的是把地图拿到其他工具里编辑,比如标注禁区或者剔除动态障碍物留下的痕迹。我在实际项目里会先保存原始.bt,再导出一份点云,用于在建模软件里做环境复现。
另外,如果你想把地图放到 navigation2 的插件生态里,可以直接用octomap的 costmap 插件,不需要额外转格式。这种情况保存.bt就够,二进制格式加载效率最高。
6. 常见问题速查与经验心得
6.1 高频问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 地图里出现大量漂浮噪点 | det_range太大或max_range太大 | 把范围限制到实际环境大小,调小sensor_model/max_range |
| 地图分层或点云重影 | 时间戳单位配错 | 确认timestamp_unit和雷达驱动一致 |
| TF 树报错或地图位置离谱 | frame_id和 Fast-LIO 的父坐标系不一致 | 统一用camera_init,不要在 Octomap 里单独写map |
| 保存出来的 map 是空文件 | 保存时没有用服务调用,或者地图节点没保持运行 | 用rosservice call方式保存,确保保存前地图还在线 |
| 建图卡顿严重 | point_filter_num太小、resolution太高 | 调大抽稀参数,把分辨率降到 0.15 或 0.2 |
| 快速转动时地图乱跳 | IMU 外参不对或时间同步差 | 跑一次外参标定,检查时间戳对齐情况 |
| 地图更新不及时 | 点云话题延迟或 CPU 满载 | 关闭可视化,抽稀点云,查看话题频率 |
6.2 几点真心建议
第一,先把基础跑通,再调参。网上很多教程把参数吹得天花乱坠,但对我来说最靠谱的路径是:默认配置先跑起来,看效果,然后每次只改一个参数。一次改三个参数,地图崩了你都不知道是哪个的问题。
第二,对 Mid-360 用户来说,雷达驱动版本一定要用官方支持 PVT 版本和 IMU 频率的版本。Mid-360 的 IMU 是内置的,驱动版本不对会导致 IMU 数据频率不稳定,Fast-LIO 的自然收敛就会出问题。驱动包更新后记得重新编译 Fast-LIO,因为接口可能有细微变化。
第三,保存地图要养成“分阶段保存”的习惯。建图过程中每隔一段时间保存一份.bt文件,这样即使最后一张图建崩了,也还有中间状态可以回溯。我经历过一次跑了二十分钟建出来的地图,保存前窗帘突然被吹动,结果全局地图多出一片“墙壁”的事,从那以后我都是十分钟存一次。
第四,避障不是非要用高分辨率地图。0.05 分辨率听起来很诱人,但实际导航中 0.1 足够,很多工业机器人用的甚至是 0.2。你的目的是让机器人安全通过,不是做三维扫描建模。过度追求精度只会让计算负载上升、实时性下降,得不偿失。
这个流程跑通之后,后续在很多平台上都能复用,只需要根据雷达和 IMU 的差异调整外参和协方差。Fast-LIO 加 Octomap 的组合不会消失,它就像建图界的“大人机”,看着朴素,但真到部署的时候你会发现,它比一堆花哨框架可靠得多。希望这篇文章能帮你少踩几个坑,把时间花在真正有意思的导航逻辑上,而不是和参数死磕。