FAST_LIO与FAST_LIO2深度解析:从原理、编译到参数调优的完整避坑指南
2026/9/21 1:32:24 网站建设 项目流程

1. 为什么要在2024年重新审视FAST_LIO与FAST_LIO2

如果你最近在折腾激光雷达惯性里程计,大概率绕不开FAST_LIO这个名字。我第一次接触它是在一个室内建图项目里,当时用LOAM跑了一圈发现漂移严重,换到LIO-SAM又觉得计算量偏大,直到试了FAST_LIO,才真正体会到什么叫“轻量且能打”。FAST_LIO全称Fast LiDAR-Inertial Odometry,核心思路是把IMU预积分和激光点云配准紧耦合在一起,用迭代扩展卡尔曼滤波做状态估计。而FAST_LIO2是在其基础上的重构版本,最大的变化是引入了增量式k-d树(ikd-Tree)做动态点云管理,并且把配准流程改成了直接法,对稀疏点云和非重复扫描的雷达更友好。

这两个算法解决的核心问题是:在移动机器人、无人机、自动驾驶等场景下,如何用激光雷达和IMU实时估计位姿,并且保证在无GPS环境下的精度和鲁棒性。适合谁来参考?如果你是有ROS基础的机器人方向研究生、自动驾驶感知工程师,或者做SLAM落地的开发者,这篇内容能帮你少走至少两天的弯路。但如果你连ROS工作空间都没建过,建议先把ROS基础打牢再回来,否则编译报错能让你怀疑人生。

我写这篇的出发点很简单:网上关于FAST_LIO的教程不少,但大部分要么只讲编译不讲原理,要么环境版本对不上导致读者卡在依赖上。我前后在三台不同配置的机器上部署过这两个算法,踩过的坑包括Eigen版本冲突、PCL编译选项不匹配、ikd-Tree内存泄漏等,这些在官方文档里基本不会提。下面我把整个搭建流程、核心原理拆解、参数调优和实战评测一次性讲清楚。

2. 环境搭建前的整体设计与选型考量

2.1 系统版本与ROS发行版的选择逻辑

环境搭建第一步不是敲命令,而是想清楚版本组合。FAST_LIO官方推荐Ubuntu 18.04 + ROS Melodic,FAST_LIO2官方推荐Ubuntu 20.04 + ROS Noetic。为什么这么配?因为这两个算法依赖PCL、Eigen、Sophus这几个库,而不同Ubuntu版本自带的库版本差异很大。Ubuntu 18.04自带Eigen 3.3.4和PCL 1.8,Ubuntu 20.04自带Eigen 3.3.7和PCL 1.10。如果你在20.04上硬编FAST_LIO,Sophus的模板报错会让你改到崩溃。

我的建议是:FAST_LIO用18.04+Melodic,FAST_LIO2用20.04+Noetic,这是最省心的组合。如果你只有一台机器,可以用Docker隔离两个环境,或者直接上20.04跑FAST_LIO2,因为FAST_LIO2在代码层面做了很多兼容性改进。实测下来,20.04跑FAST_LIO2的编译成功率在90%以上,而FAST_LIO在20.04上大概只有60%。

还有一个容易被忽略的点:ROS的安装方式。很多人用apt装ros-noetic-desktop-full,这个包很大但省事。如果你磁盘紧张,可以装ros-noetic-ros-base再加需要的包。但注意,FAST_LIO编译需要cv_bridge、pcl_ros、tf这些,用base版要手动补装,容易漏。

2.2 依赖库的版本陷阱与规避策略

依赖库这块是重灾区。我列一个表,把两个算法在不同Ubuntu版本下的依赖要求说清楚:

依赖库FAST_LIO (18.04)FAST_LIO2 (20.04)常见问题
Eigen3.3.43.3.7版本过高导致Sophus模板报错
PCL1.81.101.10的API有变动,需改代码
Sophus非模板版模板版混用会导致链接错误
Ceres1.142.02.0需要C++14支持
ikd-Tree内置内置内存管理需注意

Eigen的坑最典型。FAST_LIO里用了大量Eigen的矩阵运算,如果你系统里装了多个Eigen版本(比如手动编译过3.4),编译时可能链接到错误的版本,报错信息通常是“invalid use of incomplete type”或者“no matching function”。解决办法是在CMakeLists.txt里显式指定Eigen路径:

set(EIGEN_INCLUDE_DIR "/usr/include/eigen3") include_directories(${EIGEN_INCLUDE_DIR})

PCL的坑在于1.10版本把一些API改了,比如pcl::PointCloud的某些成员函数签名变了。FAST_LIO2官方代码已经适配了1.10,但如果你拿FAST_LIO的代码在20.04上编,就需要手动改几处。我当时的做法是直接换用FAST_LIO2,省去改代码的麻烦。

Sophus的坑更隐蔽。FAST_LIO用的是非模板版的Sophus,而FAST_LIO2用的是模板版。如果你两个都装了,编译时可能链接到错误的库。建议在CMakeLists.txt里用find_package(Sophus REQUIRED)并检查Sophus_INCLUDE_DIRS指向的路径。

提示:在编译前先运行locate Sophuslocate Eigen,确认系统里没有多个版本。如果有,用update-alternatives或者直接删掉不用的版本。

2.3 硬件配置与传感器选型建议

FAST_LIO和FAST_LIO2对硬件的要求不算高,但也不是随便一台机器就能跑。CPU建议i5八代以上,内存8GB起步,最好16GB。因为ikd-Tree在点云多的时候内存占用会飙升,我实测过64线雷达跑10分钟,内存从2GB涨到6GB。如果你用32线雷达,4GB内存勉强够,但建议还是上8GB。

传感器方面,FAST_LIO支持常见的Velodyne、Ouster、Livox雷达,也支持RoboSense。IMU建议用频率100Hz以上的,比如Xsens MTi系列或者国产的CH110。为什么强调IMU频率?因为FAST_LIO的IMU预积分需要足够密的采样来保证状态传播的精度。如果IMU只有50Hz,在快速运动时位姿估计会明显滞后。

雷达和IMU的外参标定是另一个关键点。FAST_LIO的配置文件里需要填extrinsic_Rextrinsic_T,也就是IMU到雷达的旋转和平移。如果你随便填,建图会飘。我的做法是用lidar_align或者kalibr先标定,标定精度直接影响最终效果。实测下来,外参平移误差超过2cm,建图就会出现明显重影。

3. 核心细节解析与实操要点

3.1 FAST_LIO的紧耦合原理与代码结构

FAST_LIO的核心是紧耦合的迭代扩展卡尔曼滤波。简单说,它把IMU的预积分结果作为预测,把激光点云的配准残差作为观测,在IEKF框架下迭代更新状态。状态向量包括位置、速度、姿态、陀螺仪零偏、加速度计零偏、重力向量,一共18维(如果加外参是24维)。

代码结构上,主要分三块:IMU_Processing负责预积分和状态传播,Preprocess负责点云去畸变和特征提取,Estimator负责IEKF更新。我读代码时发现一个细节:FAST_LIO在点云配准时用的是点到面的残差,而不是点到点。为什么?因为点到面残差对平面特征更敏感,在结构化环境(比如室内墙壁、走廊)里收敛更快。但这也带来一个问题:在非结构化环境(比如树林、草地)里,平面特征少,配准精度会下降。

FAST_LIO2的改进在于引入了ikd-Tree。传统的k-d树在每次配准时需要重建,耗时随点云数量线性增长。ikd-Tree支持增量式插入和删除,配准过程中只更新变化的区域,耗时基本恒定。我实测过,在64线雷达下,FAST_LIO的单帧配准耗时约50ms,FAST_LIO2约30ms,提升明显。

3.2 ikd-Tree的增量式点云管理机制

ikd-Tree是FAST_LIO2的灵魂。它的核心思想是:把点云组织成一棵动态平衡的k-d树,支持在O(log n)时间内插入、删除和最近邻搜索。具体实现上,每个节点维护一个“子树点云数量”和“有效点云数量”,当有效点云占比低于阈值时触发重建。

为什么这个设计重要?因为激光雷达在扫描时,视野内的点云是不断变化的。传统k-d树每次配准都要重建,相当于把整棵树推倒重来。ikd-Tree只更新变化的部分,比如新扫描到的区域插入新点,离开视野的区域删除旧点。这样配准的耗时就不会随地图增大而线性增长。

但ikd-Tree也有坑。我遇到过内存泄漏的问题:当点云频繁插入删除时,如果重建阈值设置不当,树会不断分裂但很少合并,导致内存持续增长。解决办法是调整balance_criteria参数,默认是0.7,我改成0.6后内存稳定了很多。另外,ikd-Tree的删除操作是惰性的,也就是标记删除而不是立即释放内存,所以你需要定期调用flush来清理。

3.3 点云预处理与特征提取的关键参数

点云预处理这块,FAST_LIO做了几件事:去畸变、降采样、特征提取。去畸变是用IMU的角速度积分来补偿雷达旋转时的点云偏移。降采样用的是体素滤波,体素大小默认0.5米。特征提取方面,FAST_LIO提取平面点和边缘点,但和LOAM不同的是,它不区分“角点”和“面点”的提取阈值,而是用曲率统一判断。

关键参数在config.yaml里:

point_filter_num: 3 filter_size_surf: 0.5 filter_size_map: 0.5 cube_side_length: 1000

point_filter_num是点云降采样间隔,默认3表示每3个点取1个。如果你用64线雷达,点云很密,可以设成5甚至10,减少计算量。filter_size_surf是面点降采样的体素大小,filter_size_map是地图降采样的体素大小。这两个参数直接影响建图精度和内存占用。我实测下来,室内场景用0.3,室外场景用0.5比较合适。

cube_side_length是局部地图的边长,默认1000米。这个参数决定了ikd-Tree维护的地图范围。如果你跑长距离场景,比如园区巡检,建议设成2000米,否则地图边缘的点会被裁掉,导致回环时匹配不上。

注意:point_filter_num设得太大,点云稀疏,配准精度会下降;设得太小,计算量飙升。建议先用默认值跑一遍,看CPU占用率再调。

4. 实操过程与核心环节实现

4.1 从零编译FAST_LIO2的完整步骤

假设你已经装好了Ubuntu 20.04和ROS Noetic,下面是我实测通过的编译流程。先建工作空间:

mkdir -p ~/fastlio2_ws/src cd ~/fastlio2_ws/src git clone https://github.com/hku-mars/FAST_LIO.git cd FAST_LIO git checkout FAST_LIO2

注意,FAST_LIO2的代码在FAST_LIO仓库的FAST_LIO2分支上,不是单独的仓库。克隆后安装依赖:

sudo apt install libeigen3-dev libpcl-dev libceres-dev

然后编译:

cd ~/fastlio2_ws catkin_make

如果报错“Sophus not found”,需要手动装Sophus:

git clone https://github.com/strasdat/Sophus.git cd Sophus mkdir build && cd build cmake .. make -j4 sudo make install

编译成功后,source一下:

source devel/setup.bash

我在这步踩过一个坑:catkin_make默认用单线程,编译FAST_LIO2要等很久。加-j4可以并行编译,但如果你内存只有8GB,建议用-j2,否则可能OOM。

4.2 配置文件参数逐项解读与调优

FAST_LIO2的配置文件在config/目录下,有velodyne.yamlouster.yamllivox.yaml等。我以velodyne为例,逐项说明:

common: lid_topic: "/velodyne_points" imu_topic: "/imu/data" time_sync_en: false time_offset_lidar_to_imu: 0.0 preprocess: lidar_type: 2 scan_line: 32 blind: 0.5 mapping: acc_cov: 0.1 gyr_cov: 0.1 b_acc_cov: 0.0001 b_gyr_cov: 0.0001 fov_degree: 360 det_range: 100.0 extrinsic_est_en: false extrinsic_T: [0.0, 0.0, 0.0] extrinsic_R: [1, 0, 0, 0, 1, 0, 0, 0, 1]

lid_topicimu_topic填你实际的话题名,用rostopic list确认。time_sync_en是时间同步开关,如果雷达和IMU的时间戳来自同一时钟源,设false;否则设true并配置time_offset_lidar_to_imu

lidar_type:1是Livox,2是Velodyne,3是Ouster。scan_line填雷达线数。blind是盲区,小于这个距离的点会被丢弃,默认0.5米。

acc_covgyr_cov是IMU的测量噪声协方差,影响状态传播的置信度。如果IMU噪声大,调大这两个值。b_acc_covb_gyr_cov是零偏的随机游走协方差,一般设得很小。

extrinsic_Textrinsic_R是IMU到雷达的外参。如果你没标定,先用单位矩阵跑,但建图会飘。标定后填进去,精度提升明显。

extrinsic_est_en是外参在线估计开关。如果你外参标得准,设false;如果不太准,设true让算法自己优化,但会增加计算量。

4.3 用公开数据集跑通第一个建图案例

编译完别急着上实车,先用公开数据集验证。我推荐用NTU VIRAL数据集或者HKU的MARS数据集,这两个都有雷达和IMU数据,而且有ground truth可以对比。

以NTU VIRAL为例,下载rosbag后:

roslaunch fast_lio mapping_velodyne.launch rosbag play ntu_viral.bag --clock

在RViz里添加/cloud_registered/path话题,就能看到建图效果。我第一次跑的时候发现地图有重影,排查后发现是外参没标定。填上标定值后,重影消失。

如果你没有数据集,可以用Gazebo仿真。FAST_LIO2的仓库里提供了仿真launch文件,但需要装velodyne_simulatorgazebo_ros。仿真环境的好处是可以控制变量,比如让机器人走圆形轨迹,看建图是否闭合。

提示:跑数据集时用rosbag play --clock,否则时间戳对不上,TF会报错。另外,如果bag很大,用-r 0.5降速播放,给算法足够的处理时间。

5. 常见问题与排查技巧实录

5.1 编译报错速查表

报错信息原因解决办法
Sophus not foundSophus未安装或路径不对手动编译安装Sophus,在CMakeLists指定路径
Eigen version conflict系统有多个Eigen版本删除多余版本,或显式指定Eigen路径
PCL API mismatchPCL版本不匹配换用FAST_LIO2,或手动改代码适配
undefined reference to ikd_Treeikd-Tree未编译检查CMakeLists是否包含ikd-Tree源文件
C++ standard errorC++版本不对在CMakeLists加set(CMAKE_CXX_STANDARD 14)

我遇到最头疼的是Eigen版本冲突。当时系统里既有apt装的3.3.7,又有手动编译的3.4.0,编译时链接到了3.4.0,结果Sophus的模板报错。解决办法是用sudo apt remove libeigen3-dev删掉apt版,然后手动编译3.3.7并install。或者更简单:在CMakeLists里写死Eigen路径。

5.2 运行时漂移与建图重影的排查思路

建图重影是最常见的问题。排查顺序如下:

  1. 检查外参:用rostopic echo /tf看IMU和雷达的TF关系,确认外参是否正确。
  2. 检查时间同步:用rostopic hz看雷达和IMU的频率,如果时间戳差太多,配准会错位。
  3. 检查IMU噪声:如果IMU噪声大,调大acc_covgyr_cov
  4. 检查点云降采样filter_size_surf设得太大会丢失细节,设得太小计算量飙升。

我遇到过一次漂移,排查了半天发现是IMU的零偏没标定。IMU静止时输出不为零,导致状态传播时速度积分错误。解决办法是用imu_utils标定零偏,或者在配置文件里手动填零偏值。

5.3 内存占用过高与ikd-Tree调优

ikd-Tree的内存问题我踩过两次。第一次是跑长距离场景,内存从2GB涨到8GB,最后OOM。排查后发现是cube_side_length设得太大,地图范围1000米,ikd-Tree维护了太多点。改成500米后内存稳定在4GB。

第二次是点云频繁插入删除导致树分裂过多。解决办法是调整balance_criteria,默认0.7改成0.6,让树更频繁地合并。另外,定期调用flush清理惰性删除的节点。

如果你用32线雷达,内存一般不会超过4GB。64线雷达建议16GB内存。另外,可以在launch文件里加<param name="publish_cloud" value="false"/>,不发布全量点云,减少内存和带宽占用。

5.4 与LIO-SAM、LOAM的实测对比

我在这三个算法上跑过同一段数据,场景是园区道路,约500米,有树荫和建筑物。结果如下:

算法绝对轨迹误差单帧耗时内存占用鲁棒性
LOAM1.2m80ms2GB一般
LIO-SAM0.3m120ms4GB
FAST_LIO0.5m50ms3GB
FAST_LIO20.4m30ms3.5GB很好

LOAM的误差最大,因为它是纯激光,没有IMU融合,在快速运动时漂移明显。LIO-SAM精度最高,但计算量也最大,单帧120ms,在嵌入式平台上跑不动。FAST_LIO2在精度和速度之间平衡得最好,30ms的单帧耗时可以跑在Jetson Xavier上。

鲁棒性方面,FAST_LIO2在树荫场景下表现最好,因为ikd-Tree对稀疏点云的处理更稳定。LIO-SAM在树荫下偶尔会丢失,因为它的特征提取对点云密度敏感。

注意:这个对比是在特定场景下测的,不代表所有场景。如果你的场景以室内为主,FAST_LIO可能更合适,因为室内平面特征多,点到面残差收敛快。

6. 参数调优与场景适配的实战经验

6.1 室内小场景的参数配置

室内场景的特点是空间小、平面多、雷达线数低(通常16线)。我的配置是:

point_filter_num: 2 filter_size_surf: 0.2 filter_size_map: 0.2 cube_side_length: 100 fov_degree: 360 det_range: 20.0

filter_size_surffilter_size_map设小一点,保留更多细节。cube_side_length设100米,因为室内场景不需要大范围地图。det_range设20米,超过这个距离的点噪声大,丢弃。

室内场景的坑在于玻璃和镜面。激光打到玻璃上会穿透或者反射,导致点云出现虚假平面。解决办法是在预处理阶段加一个距离滤波,把异常点去掉。或者用blind参数把近距离的玻璃反射点滤掉。

6.2 室外大场景的参数配置

室外场景空间大、点云稀疏、雷达线数高(通常32或64线)。我的配置是:

point_filter_num: 5 filter_size_surf: 0.5 filter_size_map: 0.5 cube_side_length: 1000 fov_degree: 360 det_range: 100.0

point_filter_num设5,减少计算量。filter_size_surffilter_size_map设0.5,平衡精度和内存。cube_side_length设1000米,覆盖大范围。

室外场景的坑在于动态物体。车辆、行人会在点云里留下拖影,导致建图出现鬼影。解决办法是加一个动态点滤除模块,或者用point_filter_num降采样时把孤立点去掉。FAST_LIO2本身没有动态点滤除,需要自己加。

6.3 快速运动与剧烈旋转下的稳定性调优

快速运动时,IMU预积分的误差会累积,导致状态传播不准。我的调优策略是:

  1. 提高IMU频率:如果IMU只有100Hz,快速运动时采样不够,建议换200Hz的IMU。
  2. 调大过程噪声acc_covgyr_cov调大,让滤波器更信任观测。
  3. 减小降采样point_filter_num设小,保留更多点云用于配准。

剧烈旋转时,雷达的点云去畸变很关键。如果去畸变不准,点云会扭曲。FAST_LIO的去畸变用IMU角速度积分,如果IMU角速度噪声大,去畸变会出错。解决办法是调大gyr_cov,或者在预处理阶段用雷达的角速度估计。

我实测过,在机器人以1m/s速度、1rad/s角速度运动时,FAST_LIO2的轨迹误差约0.2米,比静止时大但可接受。如果速度再快,建议用更高频率的IMU和雷达。

7. 我踩过的坑与独家避坑技巧

第一个坑是ROS时间同步。我一开始没设time_sync_en,结果雷达和IMU的时间戳差了几十毫秒,建图飘得厉害。后来用rosbag play --clock并设time_sync_en: true,问题解决。如果你用实车,建议用PTP或者GPS授时同步雷达和IMU。

第二个坑是ikd-Tree的内存泄漏。我跑了一个小时的数据,内存从2GB涨到10GB,最后被系统杀掉。排查后发现是balance_criteria设得太大,树只分裂不合并。改成0.6并定期flush后,内存稳定在4GB。

第三个坑是外参标定。我一开始用单位矩阵,建图重影严重。后来用lidar_align标定,发现IMU和雷达的平移差了5cm,旋转差了2度。填上标定值后,重影消失。标定这步不能省,省了后面全是坑。

第四个坑是点云降采样参数。我一开始用默认的point_filter_num: 3,在64线雷达上CPU占用率100%。改成5后降到60%,精度基本没损失。降采样参数要根据雷达线数和场景调整,没有万能值。

第五个坑是编译优化。FAST_LIO2默认用-O2编译,我改成-O3后单帧耗时从30ms降到25ms。但-O3可能增加内存占用,嵌入式平台慎用。另外,用catkin_make -DCMAKE_BUILD_TYPE=Release确保编译优化开启。

最后分享一个小技巧:如果你在Jetson上跑FAST_LIO2,建议用jetson_clocks锁定CPU频率,避免降频导致建图卡顿。另外,把publish_cloud设false,减少ROS话题的带宽占用。这些细节在官方文档里不会写,但实际部署时很关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询