拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FAST_LIO2调试实战:IMU初始化与点云畸变矫正全链路解析

FAST_LIO2调试实战:IMU初始化与点云畸变矫正全链路解析

1. 项目概述

FAST_LIO2这名字,做激光SLAM的兄弟应该都不陌生。如果你还没接触过,我用一句话给你讲清楚它是干什么的:这是一个把激光雷达和IMU(惯性测量单元)数据紧耦合在一起做状态估计和建图的开源方案,核心是用迭代误差状态卡尔曼滤波器把两类传感器数据融合起来,输出高频率、低漂移的定位结果和点云地图。

我第一次跑通FAST_LIO2的时候,说实话感觉挺复杂的,因为它在数学推导上做了很多优化,代码结构和注释也不像教学demo那样友好。但如果你只是调包跑起来,那门槛不高,编译过、改个配置、跑个数据集基本就能出图。真正的坑在于,FAST_LIO2的精度和稳定性严重依赖于前端输入质量,说白了就是IMU初始化搞不干净、点云畸变矫正参数没给对,后面地图就是花的、轨迹就是飘的。网上很多朋友跑官方数据集没问题,一到自己录的数据就崩,绝大多数情况都是在这两个环节上出了问题。

这篇博文我就基于自己实际调试FAST_LIO2的经验,从IMU初始化开始,到点云畸变矫正的完整处理链路,一步一步拆给你看。内容包括原理层面的关键推导、实操层面的代码和参数配置、以及我踩过的一些坑和排查方法。适合已经能跑通基础demo、想深入理解并自己调试FAST_LIO2的开发者。如果你之前没用过任何激光SLAM方案,可能需要先花点时间补一下ROS、PCL、Eigen这些基础工具链的知识,然后再回来看这篇会更顺。

2. IMU初始化:为什么它是整个系统能不能飞的前提

2.1 IMU在FAST_LIO2里的角色定位

先想清楚一个问题:IMU在FAST_LIO2里到底是干嘛的?很多人只知道IMU是“辅助定位的传感器”,但这个理解太粗了。在FAST_LIO2的框架里,IMU承担了两个核心职责:一个是状态预测,另一个是点云畸变矫正的基准源。

状态预测好理解,相邻两帧激光点云之间,激光雷达没有测量,那系统怎么知道机器人动了多少?靠IMU积分。IMU的加速度计和陀螺仪以几百赫兹的频率输出测量值,状态预测模块拿这些测量值做递推,估计出这段时间里的位姿变化。这个预测结果一方面用来给激光雷达帧提供初值,另一方面也用来给点云做运动补偿。

点云畸变矫正是FAST_LIO2能够产出高质量地图的关键。激光雷达扫描一帧点云需要时间,这个过程中雷达本身在运动,所以每个点的坐标实际上是在不同时刻、不同雷达坐标系下测量的。如果不做矫正,直接把这些点当成同一时刻的数据拼接起来,那出来的点云就是“拖影”的、畸变的。怎么矫正?就是用IMU递推出的每时刻位姿,把所有点都变换到统一坐标系下。这也就是标题里“从IMU初始化到点云畸变矫正”这条链路的内在逻辑。

所以IMU初始化如果没做好,影响的不只是定位初始时刻的精度,而是整个运行过程中状态预测和点云矫正的质量。这就是为什么FAST_LIO2对IMU初始化这么敏感的原因。

2.2 初始化流程的核心步骤

FAST_LIO2的IMU初始化,代码实现集中在IMU_Processing这个类里。核心思路其实不复杂:系统启动后,用一段静止或近似静止的IMU数据,估计出陀螺仪和加速度计的偏置(bias)、重力加速度的方向、以及初始的姿态。

整个初始化分两个阶段:

第一阶段是静止数据采集。系统启动后先等IMU数据累积够一定数量。默认配置里跟初始化相关的参数有一个init_time,表示初始化过程中要持续多长时间,通常设置2到3秒就够。这段时间内传感器应该尽量保持静止。为什么必须静止后面详细解释,这里先记住结论:初始化期间不要动设备。

第二阶段是状态估计。拿到这群静止数据后,代码会计算加速度计输出的平均值。这个平均值包含了重力加速度,而陀螺仪输出在静止状态下理论上应该接近零。通过这两个信息,算法可以确定重力方向在IMU坐标系下的表示,从而把姿态旋转到水平面附近,同时估计出bias的初值。

等你看到控制台输出Initialization finished这样的日志,说明初始化已经完成了。这时系统已经拿到一组可用的初始状态,可以进入正常的状态估计流程了。我试过多次,如果初始化时设备没放稳或者有明显晃动,这组初始状态就会带病上岗,后续想纠回来非常费劲。

2.3 初始化失败的典型表现

初始化失败在FAST_LIO2里不是一个非黑即白的结果——系统很少直接报告“初始化失败”,而是带病运行。常见的表现有几种:

第一种是地图“开口笑”或者“扭曲”。初始化时重力方向估计偏了,那整个坐标系就是斜的,地平线方向不对,地图自然也会跟着歪。

第二种是运行开始后短时间内轨迹就开始漂移。初始速度估计不对,或者bias初值离真实值太远,迭代卡尔曼滤波的协方差收敛不了,状态估计就越跑越偏。

第三种是初始化进程卡死。比如IMU的话题数据没对上、时间戳不一致、或者IMU数据频率和配置里的标称值差距太大,都会导致初始化阶段迟迟收不到足够的数据。

我在一开始调试的时候遇到过一个比较隐蔽的问题:imu_topic配置写的是/imu/data_raw,但实际录的包里面IMU话题是/imu/data,导致FAST_LIO2一直收不到IMU数据,控制台日志一直在等。这个问题排查了我一个晚上,最后用rostopic list核对才发现话题名对不上。

2.4 初始化参数的合理配置

FAST_LIO2的IMU相关参数主要在配置文件的common和imu两个字段下面。我直接贴一份我实测过比较稳的配置片段,然后再逐个解释关键参数为什么这么设:

common: lid_topic: "/livox/lidar" imu_topic: "/livox/imu" imu: # IMU的标称频率,单位Hz imu_rate: 200 # 加速度计噪声密度,单位 m/s^2 / sqrt(Hz) acc_norm: 0.01 # 陀螺仪噪声密度,单位 rad/s / sqrt(Hz) gyr_norm: 0.001 # 初始化时间,单位秒 init_time: 3.0 # IMU安装偏移(相对雷达坐标系),单位米和弧度 extrinsic_T: [0.0, 0.0, 0.0] extrinsic_R: [1, 0, 0, 0, 1, 0, 0, 0, 1]

imu_rate一定要和你IMU实际发布的频率一致。Livox的IMU发布频率通常是200Hz,但你最好用rostopic hz /livox/imu确认一下。如果配置的标称频率和实际频率差太多,系统在做离散化积分的时候会有明显的累积误差。

acc_norm和gyr_norm是IMU的噪声密度参数,这个理论上应该从IMU的datasheet里查,或者根据Allan方差分析结果来标定。但实际项目中,很多人直接沿用FAST_LIO2默认参数也能跑得不错,因为算法本身对这两个参数的扰动有一定鲁棒性。不过如果你发现系统对IMU噪声变得过于敏感,或者状态估计协方差收敛异常,可以优先检查这两个值是否合理。

extrinsic_T和extrinsic_R是IMU相对雷达的外参。这个参数如果给错了,整个系统会非常难收敛,地图会很散。建议做一次标定,而不是拍脑袋填零。常用的标定工具比如lidar_camera_calib之类的外参标定方法,花点时间跑一遍,比盲目调参高效得多。

注意:init_time并非越大越好。太长的话占用启动时间,而且长时间静止时IMU的bias估计虽然更准,但提升幅度有限;太短的话数据量不够,估计结果噪声大。3秒左右是一个我认为比较合理的平衡点。

3. 点云畸变矫正:把每一帧点云都“拉”到同一个时刻

3.1 畸变是怎么产生的

接下来说点云畸变矫正,这是FAST_LIO2能建出精细地图的另一个基础。

理解点云畸变,要先理解激光雷达的扫描机制。以Livox为例子,它采用的是非重复扫描方式,但无论是Livox还是机械式雷达,一帧点云的采集都需要一段时间——Livox的帧率常见是10Hz,意味着每100毫秒雷达会完成一次点云覆盖。在这100毫秒里,如果机器人正在运动,那么扫描早期采到的点和后期采到的点,对应的雷达位姿是截然不同的。

举个例子,假设雷达以0.5米/秒的速度直线前进,100毫秒内移动了5厘米。如果不对畸变做矫正,这帧点云里所有的点会同时“压”到帧尾时刻的坐标系下,那近处物体的边缘就会直接糊掉5厘米。对建图来说,5厘米的误差是致命的——室内的墙缝、门框、货架边缘全都对不齐。

FAST_LIO2的畸变矫正思路不是在后处理阶段一次性矫正整帧点云,而是在前端就把运动信息注入进去。具体来说,它利用IMU递推的状态预测结果,估算出这一帧扫描期间每一个激光点对应的雷达位姿,然后把点从各自的测量时刻变换到统一的帧尾坐标系下。这就是所谓的“去畸变”。

3.2 畸变矫正的代码链路

在FAST_LIO2的代码里,畸变矫正的核心逻辑不是单独一个函数,而是嵌在点云预处理和状态更新流程里。

我直接说几个关键的代码节点:

预处理阶段,原始点云会被拆分成一个个点的集合,每个点附带它自己的时间戳。代码里常见的是用一个结构体数组,每个元素包含点的三维坐标和相对帧起点的时间偏移point_infos或者relative_time。

状态预测之后,系统有了这一帧扫描期间每个时刻的位姿估计。然后代码遍历这一帧里的每个点,根据点的时间戳找到对应的位姿,把点从测量时刻变换到帧尾时刻。

这个变换在代码里有一个专门的函数来干,但不同版本的实现方式不一样。核心逻辑大致是:

// 伪代码,逻辑参考FAST_LIO2 for (size_t i = 0; i < cloud->size(); ++i) { double point_time = cloud->points[i].timestamp; // 当前点的时间戳 // 根据时间戳插值得到当前时刻的位姿 Eigen::Matrix3d R_point = interpolateRotation(start_pose, end_pose, point_time); Eigen::Vector3d t_point = interpolateTranslation(start_pose, end_pose, point_time); // 将点从测量时刻坐标系变换到帧尾坐标系 Eigen::Vector3d p_original( cloud->points[i].x, cloud->points[i].y, cloud->points[i].z ); Eigen::Vector3d p_compensated = R_point.transpose() * (p_original - t_point); cloud->points[i].x = p_compensated.x(); cloud->points[i].y = p_compensated.y(); cloud->points[i].z = p_compensated.z(); }

注意这个变换里用的是旋转矩阵的转置,本质上是把点从“测量时刻坐标系”变换到“帧尾坐标系”。为什么不反过来?因为状态预测给出的位姿是“当前时刻传感器在世界/帧尾坐标系下的位姿”,而我们手里拿到的点是“在当前时刻传感器坐标系下的坐标”。想要统一到帧尾坐标系,就需要用旋转矩阵的逆(也就是转置)把点从测量时刻的传感器坐标系“拉”到帧尾坐标系中。

3.3 畸变矫正效果的关键影响因素

畸变矫正的效果好坏,不完全取决于矫正代码本身,更大程度上取决于预测位姿的精度。预测位姿来自IMU递推,所以影响要素有几个:

第一,IMU的bias估计是否准确。bias如果偏了,IMU积分出的速度和位姿会快速发散,几毫秒内的误差可能不大,但一帧扫描持续100毫秒,累积起来就足以让点云出现明显的畸变残留。

第二,IMU和雷达之间的时间同步。时间同步问题在实车上特别常见,IMU和雷达各自有独立时钟,如果时间戳没对齐,哪怕偏移只有几毫秒,畸变矫正的精度也会受到很大影响。

第三,点云的时间戳解析是否正确。有些驱动给的点云时间戳不是采集时刻,而是点云发布时刻,这个误差比时间同步偏移更严重,直接导致畸变矫正参考的时间基准是错的。

提示:如果你发现畸变矫正做得不准——体现在地图上就是物体边缘发虚、重复扫描对不齐——优先检查IMU和雷达的时间同步,而不是急着调外参。我在实际项目中遇到过一个类似问题,折腾了很长时间去标外参结果改善甚微,最后发现是驱动里的时间戳源选错了。

3.4 点云频率和运动速度对畸变的影响

畸变的严重程度和运动速度、点云帧率直接相关。运动越快、帧率越低,畸变越严重。FAST_LIO2对这种畸变的容忍度不算特别低,因为它有IMU数据作为矫正基准。但前提还是IMU数据质量要过关。

举个例子,如果机器人以1米/秒速度过弯,角速度大概0.5弧度/秒,一帧100毫秒内的转角大约是3度。这个程度的旋转畸变,如果不做矫正,点云直接是“拧着”的。有了IMU递推和畸变矫正之后,转角可以被补偿掉绝大部分,剩下的残差取决于IMU的精度和预测算法。

如果你用的是机械式雷达,比如Velodyne、Ouster这些,每帧扫描期间雷达转了一圈,畸变模式不太一样,但矫正原理完全一致。区别在于机械式雷达的一帧扫描时间有时更长(比如10Hz时是100毫秒),畸变量更大,对矫正的依赖也更强。

4. 实操环节:完整跑通FAST_LIO2的流程记录

4.1 环境准备和编译

讲完原理和关键点,我带你从头到尾实操一遍。这里的步骤都是我实际试过的,直接照做基本能跑通。

先说环境,我的测试环境是Ubuntu 20.04 + ROS Noetic + Livox SDK,实测下来这套组合比较稳。FAST_LIO2的编译依赖主要有PCL、Eigen、livox_ros_driver,以及一个可选的livox_ros_driver2。官方README里写了详细的依赖安装命令,但有几个坑我得单独提醒:

PCL版本不要用太老的,至少1.10以上,否则某些点云类型定义对不上会编译报错。Eigen用系统自带的就行,不用特意装最新版。livox_ros_driver的版本跟FAST_LIO2的代码有兼容性要求,我建议直接用官方仓库里推荐的版本,不要自己随手装最新的,否则消息类型定义变了会导致编译失败。

编译流程大致是:

mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/hku-mars/FAST_LIO2.git git clone https://github.com/Livox-SDK/livox_ros_driver.git cd ~/catkin_ws catkin_make source devel/setup.bash

如果你用的是Livox_ros_driver2,编译方式稍有不同,具体参考官方文档。第一次编译会久一点,等着就行。编译过程中最常见的报错是缺少某个依赖的头文件,根据报错信息apt install对应库就行。

4.2 配置文件的修改要点

编译通过后,进入配置环节。FAST_LIO2的配置文件在config/目录下,针对不同雷达型号有不同配置,比如livox_mid360.yaml、livox_avia.yaml等等。选一个跟你的雷达型号匹配的配置,然后修改几个关键的参数。

第一是lid_topic和imu_topic,改成你自己的雷达和IMU实际发布的话题名。

第二是extrinsic_T和extrinsic_R,如果IMU和雷达是刚性固定安装且做过标定,直接填标定结果;如果完全没有标定数据,先用全零外参会跑通流程,后续再补标定。但要注意全零外参只适合“快速验证流程”,想要好效果必须标定。

第三是point_filter_num参数,这个参数控制每几个点取一个参与运算。值越大,参与计算的点越少,速度越快,精度越低。我一般先用默认值跑通,再根据实时性和精度的平衡去调。

还有一些参数比如max_iteration,控制迭代卡尔曼滤波的最大迭代次数,一般8到10左右比较合适。调得太大反而可能过拟合,导致地图出现奇怪的噪点。

4.3 运行数据集和实时数据

配置改好后就可以跑了。先开roscore,再启动雷达驱动,最后启动FAST_LIO2节点:

roscore # 另开终端 roslaunch livox_ros_driver livox_lidar.launch # 再开一个终端 roslaunch fast_lio mapping_mid360.launch

如果你用的是录好的bag包,那就先启动FAST_LIO2节点,然后播放bag:

rosbag play your_bag.bag

这里有个小技巧,播放bag的时候可以用--clock参数,让系统使用bag里的时间戳,避免时间不同步的问题。我还习惯加-r 1.0控制播放速率,保持和真实场景一致,因为FAST_LIO2对IMU积分频率和点云帧率的对应关系比较敏感,加快或放慢播放速度可能导致系统表现异常。

运行起来之后,用rviz打开FAST_LIO2提供的rviz配置(路径在rviz_cfg/目录下),就能看到实时建图和轨迹。如果你能看到地图随着传感器运动不断扩展、并且重复扫描的区域没有明显错位,说明整个系统已经正常工作。

4.4 我的一次踩坑实录

分享一次我比较典型的调试过程,当时用的是自主采集的数据,IMU通过串口接入,激光雷达是Livox Mid-360。

第一次跑的时候,地图一直发散,轨迹飘出去十几米后直接崩溃。我一开始怀疑是外参没标好,花了一天时间用标定工具把外参重新标了一遍,但问题依旧。后来我把init_time从默认的3秒改成5秒,并且确保启动后前几秒完全静止,问题就缓解了很多。原因是我采集数据的时候,启动脚本和雷达驱动之间有点延迟,FAST_LIO2开始接收IMU数据时设备已经在轻微抖动了,初始化数据不干净,bias估计严重偏离真实值。

另外一次是在转场时,启动顺序搞反了:先启动了FAST_LIO2节点,再启动雷达驱动,结果初始化阶段没有收到任何点云数据,系统一直处于等待状态。以后我每次调试都固定一套启动顺序,并且写进启动脚本里,避免人为操作出错。

提示:启动时注意传感器是否已经在正常输出了,然后再启动FAST_LIO2。如果启动时序不对,初始化阶段可能采不到完整数据或者采到的是异常数据,影响非常大。

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

5.1 编译报错汇总

编译阶段的问题其实是新人最容易卡住的。我整理几个我见过的高频编译错误:

报错特征可能原因解决办法
fatal error: livox_ros_driver/CustomMsg.h: No such file or directory雷达驱动没编译或消息没生成先编译livox_ros_driver,再编译FAST_LIO2
undefined reference to ... pcl::...PCL版本不兼容检查PCL版本,升级到1.10以上
Eigen对齐报错Eigen版本过高或过低安装系统推荐的Eigen版本,不要混用多个版本
double free or corruption运行时报错常见于点云数据不干净检查雷达驱动是否正常,点云时间戳是否有异常

编译报错的解读逻辑是:先看是编译期还是运行期,编译期错误优先检查依赖版本,运行期错误优先检查数据质量。

5.2 运行中地图发散的排查思路

地图发散是FAST_LIO2最头疼的问题之一,可能的原因非常多,我建议按优先级排查:

第一优先级:初始化数据质量。启动时传感器有没有静止足够久?有没有被碰过?这里可以看初始化阶段的日志确认。

第二优先级:时间同步。IMU和雷达的时间戳是否严格同步?用rostopic hz看一下两个话题的采集频率是否正常,用rostopic echo扫一眼时间戳是否合理。

第三优先级:外参。外参不准会导致收敛困难,尤其是旋转外参,差几度地图就基本不能用。建议用正规标定工具做一次外参标定。

第四优先级:参数配置。acc_norm和gyr_norm是否和实际IMU噪声水平匹配?point_filter_num是否导致有效点数过少,状态观测不足?

这个排查顺序是我实践下来性价比最高的。很多朋友一遇到地图发散就疯狂调参,结果调半天没用,其实根源在前面的环节。

5.3 IMU数据质量的自检方法

IMU数据质量的好坏,其实在初始化阶段就能初步判断。一个简单的自检方法是:把设备静止放在桌面上,录制一段IMU数据,看加速度计三轴的输出。正常情况下,模长应该接近9.8 m/s²,三轴方向会因为姿态不同而有差异,但如果合力的方向和大小都在合理范围内,说明加速度计整体没问题。

陀螺仪的自检更简单,静止的时候三轴输出应该都在零附近,如果某个轴的输出长时间大于0.01 rad/s(大约0.57度/秒),说明陀螺仪bias偏大或温度补偿没做好。这种条件下初始化出来的bias估计可能不准。

另外我有一个比较实用的技巧,把IMU数据画出来看曲线。rqt_plot可以直接订阅IMU话题画出数据曲线,肉眼判断是否有异常跳变或长时间漂移,比对着日志数字去分析直观得多。

5.4 点云畸变矫正不出来时的检查顺序

点云畸变矫正没效果,直观的表现是建图后物体边缘“拖影”明显,或者同一物体被重复扫描时位置对不齐。我的检查顺序是这样的:

先确认IMU数据真的被系统使用了。看启动日志和运行的CPU占用,如果IMU话题没有数据或者数据频率太低,系统实际上在做“无IMU的纯激光里程计”,畸变矫正自然名存实亡。

再确认时间戳对齐。把bag播放时的--clock打开,或者确认雷达和IMU用的是同一个时间源。上时间同步卡是很多工控场景的做法,但低成本方案至少要保证时间戳偏移在10毫秒以内。

最后检查畸变矫正代码路径是否真的被执行。FAST_LIO2的代码里有些版本会根据配置跳过畸变矫正,比如某些实时性优先的模式。如果配置不当,可能你看着代码里有矫正逻辑,但实际运行时根本没有进入那一段。

提示:如果你是在仿真环境或者只用bag做离线测试,检查和调试畸变矫正比在实车上容易得多。建议先在离线环境里把整个链路理解透了,再上实车,能省去大量现场排查的时间。

6. 我的一点实操心得

FAST_LIO2这套系统,我前前后后调了小半年,从最开始只会跑官方数据集,到现在能相对从容地处理各种实车数据,最大的体会是:这个方案的精度上限,其实不在算法本身,而在你喂给它的数据质量。IMU初始化给不给力、时间同步准不准、外参标定精不精确,这些“前端问题”几乎决定了最终效果的下限和上限。

很多朋友会把精力放在调卡尔曼滤波参数上,比如状态噪声协方差、观测噪声协方差,觉得把这两个参数调到位了系统就准了。但以我的经验,参数微调的收益远小于把输入数据弄干净。初始化期间设备稳不稳、时间戳对不对得上、外参有没有标定,这些才是决定系统能不能发挥出FAST_LIO2真实水平的关键因素。

最后再分享一个小技巧。如果你在现场调试遇到地图飘了,不要急着关程序改参数,先把当前的IMU原始数据和激光点云数据完整录下来,离线一遍一遍复现问题。离线调试比现场调试高效一个数量级,因为你可以在同一份数据上反复验证修改效果,而不必重复采集数据。这几乎是我现在调试激光SLAM方案的标配套路。

返回列表