
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 MelodicFAST_LIO2官方推荐Ubuntu 20.04 ROS Noetic。为什么这么配因为这两个算法依赖PCL、Eigen、Sophus这几个库而不同Ubuntu版本自带的库版本差异很大。Ubuntu 18.04自带Eigen 3.3.4和PCL 1.8Ubuntu 20.04自带Eigen 3.3.7和PCL 1.10。如果你在20.04上硬编FAST_LIOSophus的模板报错会让你改到崩溃。我的建议是FAST_LIO用18.04MelodicFAST_LIO2用20.04Noetic这是最省心的组合。如果你只有一台机器可以用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需要C14支持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 Sophus和locate 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_R和extrinsic_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的单帧配准耗时约50msFAST_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: 1000point_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”需要手动装Sophusgit 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.yaml、ouster.yaml、livox.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_topic和imu_topic填你实际的话题名用rostopic list确认。time_sync_en是时间同步开关如果雷达和IMU的时间戳来自同一时钟源设false否则设true并配置time_offset_lidar_to_imu。lidar_type1是Livox2是Velodyne3是Ouster。scan_line填雷达线数。blind是盲区小于这个距离的点会被丢弃默认0.5米。acc_cov和gyr_cov是IMU的测量噪声协方差影响状态传播的置信度。如果IMU噪声大调大这两个值。b_acc_cov和b_gyr_cov是零偏的随机游走协方差一般设得很小。extrinsic_T和extrinsic_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_simulator和gazebo_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 运行时漂移与建图重影的排查思路建图重影是最常见的问题。排查顺序如下检查外参用rostopic echo /tf看IMU和雷达的TF关系确认外参是否正确。检查时间同步用rostopic hz看雷达和IMU的频率如果时间戳差太多配准会错位。检查IMU噪声如果IMU噪声大调大acc_cov和gyr_cov。检查点云降采样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 namepublish_cloud valuefalse/不发布全量点云减少内存和带宽占用。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.0filter_size_surf和filter_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.0point_filter_num设5减少计算量。filter_size_surf和filter_size_map设0.5平衡精度和内存。cube_side_length设1000米覆盖大范围。室外场景的坑在于动态物体。车辆、行人会在点云里留下拖影导致建图出现鬼影。解决办法是加一个动态点滤除模块或者用point_filter_num降采样时把孤立点去掉。FAST_LIO2本身没有动态点滤除需要自己加。6.3 快速运动与剧烈旋转下的稳定性调优快速运动时IMU预积分的误差会累积导致状态传播不准。我的调优策略是提高IMU频率如果IMU只有100Hz快速运动时采样不够建议换200Hz的IMU。调大过程噪声acc_cov和gyr_cov调大让滤波器更信任观测。减小降采样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_TYPERelease确保编译优化开启。最后分享一个小技巧如果你在Jetson上跑FAST_LIO2建议用jetson_clocks锁定CPU频率避免降频导致建图卡顿。另外把publish_cloud设false减少ROS话题的带宽占用。这些细节在官方文档里不会写但实际部署时很关键。