
做Lidar-IMU标定这件事我前前后后折腾了小半个月。最难的不是把代码跑起来而是跑起来之后发现结果根本不对——点云叠加在墙面上有重影轨迹一长就裂开。后来我回过头去把lidar_align这个工具从头到尾捋了一遍又把数据采集、参数设置、结果验证每个环节挨个排查了一遍才算真正把外参标定这件事吃透。这篇东西我不想写成论文式的教程就按我自己从零到一的实操路径来写把那些文档里没写清楚、但实际影响结果的细节全部摊开。想搞定Lidar-IMU外参的直接跟着做就行能少走很多弯路。1. 为什么你搞不定外参标定这件事的本质和常见误区1.1 外参到底是什么不标定会怎样外参描述的是两个传感器坐标系之间的一次刚体变换包含旋转矩阵和平移向量。放在Lidar-IMU的场景里就是激光雷达坐标系和IMU坐标系在空间中的相对位置关系。D435i这类自带IMU的深度相机出厂之前大多已经做过内参和外参的联合标定但自己搭的激光雷达加IMU组合外参只能自己想办法。外参不标定会造成什么问题我用一个很直观的例子解释一下。激光雷达扫描出来的每一个点默认是在雷达自身坐标系下的坐标。算法在融合IMU数据时需要把这一帧点云变换到IMU坐标系下。如果旋转、平移给错了点云在拼接时就会错位地面像搓衣板一样起伏墙面出现双重轮廓。做SLAM建图的时候影响更明显机器人在走廊转一圈回来地图的闭合误差飙得极高。很多朋友觉得外参是固定值标一次就一劳永逸。这个理解不完整机械安装结构一旦因为外力撞击、螺丝松动产生位移外参就变了。所以标定这个事情不只是新装设备要做设备重装、碰撞之后都要重新做一遍。这也是为什么把一套可靠的标定流程掌握在手里比单纯依赖某一个工具重要得多。1.2 为什么很多人标出来效果还是不对我观察到一个高频现象同一份代码别人跑出来就是准的自己跑出来怎么都不对。问题往往不在算法本身而在数据质量和初始值。工具给了一个优化框架但它不会告诉你你把rosbag录成什么样、初始外参给成什么样结果会天差地别。lidar_align这类基于非线性优化的工具本质上是在求解一个优化问题。任何非线性优化都有“局部极小值”的问题也就是说如果你给的初始外参离真实值太远优化过程会收敛到某个错误的结果上去而且这个错误结果在损失函数上看起来还挺合理。很多人第一次标定给的初始值是0,0,0激光雷达和IMU又有将近10厘米的物理距离优化器找来找去找不到正确的山头最后停在了一个四不像的位置。更隐蔽的问题是数据采集环境。你拿着设备在一个狭小的、特征稀疏的走廊里录了一段包点云在走廊方向几乎看不出变化。算法想要通过点云对齐来估计位姿但是走廊方向上的约束弱到可以忽略Rosbag里那些点云帧对优化器来说和废数据没什么区别。1.3 不要只盯着lidar_align先评估手上的硬件条件选工具不能只看社区热不热你得先想清楚手里的传感器是什么类型。lidar_align适合机械式激光雷达比如Velodyne VLP-16、HDL-32这样在360度范围内均匀扫描的设备。如果你用的是Livox固态激光雷达那种非重复式扫描模式点云分布特性和机械式完全不一样直接套lidar_align大概率效果不好这时候更适合去找对应的标定工程。另外lidar_align依赖里程计输入。大多数人的思路是用轮式里程计或者IMU积分出来的姿态作为输入但轮子打滑会引入大量误差IMU积分时间一长就漂移。我后来用的方案是先用LIO算法跑一帧一帧的激光里程计把得到的里程计话题作为lidar_align的输入这样点云和里程计之间的关联更好。实测下来标定结果的稳定性提升了一个档次。2. lidar_align是怎么工作的原理只有三步2.1 核心思路让两把“尺子”互相校验lidar_align的核心思想说起来很简单。激光雷达移动的时候每一帧点云都是在一个连续轨迹上的不同位置采样的。如果外参正确用IMU/里程计给出的相对位姿把各帧点云变换到一起场景中的固定物体应该是严格重合的。外参一旦有偏差点云之间就会产生系统性错位。算法要做的就是找出那个让所有点云对齐得最好的外参。具体到实现上工具把每个激光雷达检测到的点投影到目标坐标系下搜索其最近邻点然后计算距离残差。这个思路和ICP点云配准很像但不同之处在于lidar_align优化的变量不只是外参还包含每一帧点的位姿更新量。换句话说它把位姿估计和外参估计放到同一个非线性优化框架里联合求解了。这对我们有什么启发它意味着工具对输入里程计的质量是有容忍度的因为优化器会自己去修正一部分位姿误差。但容忍度是有限度的如果输入的位姿轨迹本身就是漂移的、跳变的优化器会把位姿误差强行塞进外参里你就得到了一个“看起来在训练集上很准实际上放到真实环境就崩”的标定结果。2.2 损失函数与优化策略理解这些才能调好参数lidar_align使用Ceres Solver进行非线性优化。默认的核函数是HuberLoss这个核函数会对大的残差进行降权提高算法对离群点的鲁棒性。在室内环境中玻璃墙、窗户、金属反光面会产生很多错误的点这些点如果以正常权重参与优化会把外参带偏核函数的鲁棒性就体现在这里。工具中还有一个关于点云采样的参数优化时不会把每一帧的全部点都拿来做匹配而是按一定比例随机采样。降低采样点数能显著提升优化速度但如果你的点云噪声大、结构稀疏采样太少会导致约束不足。我自己的做法是先用默认参数跑一轮看loss曲线趋势。如果loss下降很快但最终值较大就把采样点数加大再跑。有几种方法判断优化是否收敛一是看Ceres输出的迭代次数和cost下降曲线二是看优化结束后的loss值是否在一个远小于初始loss的区间三是把标定结果可视化出来直接看点云拼接效果。不要只盯数值数值小不代表一定准因为算法的损失函数和真实几何误差之间不是绝对等价的关系。2.3 从原理反推使用条件三类场景先排除理解了原理之后很多问题就有了答案。比如为什么场景特征太少标定效果差因为点云最近邻搜索需要几何约束在一个纯平面的天花板、地面和光秃秃的墙壁组成的空间里激光点云在法线方向的约束很弱优化器没法准确判断外参中的某些分量。第一个需要排除的场景是长而狭窄的走廊。沿着走廊方向几乎没有任何特征变化类似视觉SLAM里的退化场景算法在走廊方向上的估计就没有信息量。第二个是空旷的大场地。激光雷达扫出去几十米都是平地点云找最近邻找到了也是同一片区域优化问题病态。第三类是高速旋转的场景。手持设备快速甩动、车辆高速转弯点云会产生运动畸变而lidar_align本身是不做运动畸变补偿的它默认帧内的点云是刚性的。我建议数据采集时保持设备运动平稳避免急加减速和快速旋转即便做不到完全匀速也尽量让位姿的变化连续平滑。3. 开工前的准备从源码编译到数据采集3.1 编译lidar_align环境依赖和容易踩的坑lidar_align是一个ROS功能包依赖ROS、Eigen、Ceres Solver和PCL库。在Ubuntu 16.04 ROS Kinetic那会我装的时候最头疼的是Ceres版本和Eigen版本打架。后面换到Ubuntu 18.04 ROS Melodic系统自带的Eigen和Ceres版本兼容性好很多编译基本不用折腾。我录了一段实际的操作过程cd ~/catkin_ws/src git clone https://github.com/ethz-asl/lidar_align.git cd .. catkin_make第一次编译如果报找不到Ceres相关的头文件先检查有没有安装libceres-dev。用apt安装的老版本Ceres和lidar_align在某些接口上有差异建议直接从Ceres官网源码编译安装最新稳定版编译耗时几分钟而已。另一个容易忽略的问题是PCL版本。lidar_align用的是PCL的kd-tree做最近邻搜索如果机器上同时装了几个版本的PCLCMake在链接库的时候可能找错版本。我在新机器上编译时会先看一眼pcl_version确保和ROS自带的PCL版本一致否则会出现运行时报错找不到符号的情况。3.2 数据采集的标准姿势比想象中更重要前面花了那么大篇幅讲数据质量到实际操作的时候更要对数据苛刻一点。我采集数据用的是一条长约80米的室内通道通道两侧是平整的墙面地面有规律的纹理墙面上有门洞、消防栓和凸起的柱子天花板上挂着灯和管道这种环境对激光雷达来说信息量非常丰富。采集时要保证激光雷达能完整扫描到周围360度的环境不要让人或设备贴墙面太近。手持设备移动的速度控制在每秒0.3米到0.5米既不能快到来不及扫描也不能慢到图像帧之间几乎没有位移。整个采集过程控制在两分钟左右太短了约束不够太长了IMU积分和里程计的漂移又会被放大。设备的高度变化也要刻意加进去我习惯在扫描过程中主动做一个弯腰、一个垫脚的动作让IMU在pitch方向上有明显的激励。很多人在标定时容易忽略一个现象如果IMU只在水平面上运动外参的roll和pitch方向约束严重不足标出来的旋转矩阵在俯仰方向不准地面稍微有点坡度就会露馅。3.3 rosbag的记录与话题检查录制的时候要把原始话题完整记录下来不要用--compression压缩压缩格式在后续处理时会引入不必要的解码开销。用如下命令记录rosbag record /velodyne_points /livox/lidar /imu/data -O calib.bag话题名称视你实际的驱动而定重点是同时记录点云话题和里程计/IMU话题。录制完成后用rosbag info calib.bag查看话题频率和消息数量。我遇到过IMU话题频率只有10Hz的情况对lidar_align来说IMU的消息频率不需要太高因为它只用里程计做帧间位姿约束但话题不能长时间丢数据丢数据会导致相邻点云的位姿变换出现跳变。还有一个需要提前做的操作检查点云话题的坐标系定义。lidar_align要求点云消息的frame_id和里程计消息的frame_id在语义上是统一的。实际操作中发现很多驱动把点云frame_id写成了laser而里程计frame_id是base_link这本身没问题但在配置launch文件时你要保证给lidar_align传递的topic名称和frame_id对应关系是一致且明确的。4. 手把手跑通lidar_align配置、运行、结果验证4.1 launch文件配置几个关键参数的技术细节lidar_align的launch文件负责启动标定节点核心参数包括点云话题、里程计话题、初始外参和优化配置。我放一个典型的配置片段launch node pkglidar_align typelidar_align namelidar_align outputscreen param namepoint_cloud_topic value/velodyne_points/ param namepose_topic value/laser_odom/ param nameoutput_topic value/lidar_align/transforms/ param nameinitial_translation value0.0 0.0 0.0/ param nameinitial_rotation value0.0 0.0 0.0/ param namevisualize valuetrue/ /node /launch注意initial_translation和initial_rotation不是随便填的。如果你知道雷达和IMU之间的近似安装位置比如IMU在雷达正上方5厘米那就把初始平移设为0 0 0.05。如果你还知道的大致角度偏差也填进去。一个合理的初始值能让优化器少走弯路大幅降低陷入局部极小的概率。还有一个参数叫use_scan或者类似的布尔开关控制是否在前端预处理阶段滤除地面点和远距离稀疏点。开启后优化速度更快但滤除地面点会削弱z方向上的约束。对放在轮式机器人上的设备地面约束本来就很强可以开启对手持设备不建议开。4.2 运行标定和观察loss曲线配置完成后先启动roscore再运行节点roscore roslaunch lidar_align lidar_align.launch节点启动后会自动订阅话题并从bag文件里回放数据。运行过程中Ceres会不断输出优化迭代信息主要看两个指标cost和迭代次数。正常情况是cost从一个较大的值开始快速下降然后逐渐收敛到一个稳定的平台区。如果cost不降反升大概率是话题配置错了或者初始值偏差过大导致优化发散。整个优化过程可能持续几分钟。跑完之后会生成一个Transform.json文件里面保存了最终的外参。保存格式包括平移向量和四元数四元数在使用时要注意归一化我遇到过手滑直接拿原始四元数去用导致旋转矩阵不对的情况。4.3 结果验证别只看数值点云会说话标定结果好不好最大的检验标准是把它放到系统里去看实际表现。我自己摸索出一套三层验证法。第一层把标定出来的外参替换到现有算法里停在原地让设备自转一圈观察点云构图。如果墙体、桌面边缘锐利清晰没有重影说明旋转外参基本准确。如果有横向错位优先怀疑yaw方向的外参没标准。第二层让设备走一个边长2米左右的方形轨迹最后回到起点。用标定后的外参跑一遍建图看终点的地图和起点能不能闭合。闭合误差小于20厘米算合格小于5厘米说明外参标定得相当好了。第三层验证平移分量的精度在设备前方放一个已知高度的箱子建图完成后用工具量一下箱子顶面的高度误差。这个测试对z方向的平移误差非常敏感如果箱子高度和真实值差了10厘米以上回去重新标。这三层验证都过了我才会把这个外参写进正式的配置里。这个流程看起来繁琐但能过滤掉很多看起来数值合格、实际不能用的情况。5. 高频踩坑现场这些问题你大概率会遇到5.1 话题和坐标系相关的坑坑1rosbag回放速度不匹配。用rosbag play回放时如果数据点云频率和里程计频率差很多或者话题之间的时间戳没有对齐节点可能产生抖动。解决方法是回放前加--clock参数让节点使用bag的仿真时间同时对IMU和雷达都使用同样的时间基准。坑2点云frame_id和里程计frame_id不匹配。有时点云的frame_id是空字符串里程计是odom节点在内部做坐标系变换时会直接拿不到有效变换。检查方法很简单回放bag时打开rviz加一个PointCloud2显示和一个Path显示看着两者是否在同一个坐标系下运动如果不一致需要先做坐标系的统一。坑3里程计消息类型不匹配。lidar_align要求里程计消息是nav_msgs/Odometry有些方案提供的是geometry_msgs/PoseStamped虽然不是完全不能用但需要做一个小转换节点。我写过一个简单的pose2odom节点把PoseStamped包装成Odometry发出去实测没有问题。5.2 采集与场景相关的坑坑4环境特征太少导致旋转外参不准。在一个标准办公室角落里标定三面是墙特征单一标出来的外参在roll方向上偏差经常超过2度。我的做法是先在一个特征丰富的环境中标定一轮得到粗值后再换一个不同特征的环境跑第二轮两轮结果的差异在可接受范围内才算稳。坑5雷达线束少导致点云过于稀疏。VLP-16这种16线雷达在远距离上的点云分布非常稀疏直接用原始点云跑优化kd-tree搜索到的最近邻可能离真实最近点相距很远。一个解决思路是先用voxel filter降采样再在匹配前做一次点云的表面法线估计让最近邻匹配对更鲁棒。5.3 工具本身限制的补充说明lidar_align假设激光雷达和IMU之间是刚性连接如果你的设备里加了减震结构或者装在云台上这个假设就不成立了。刚性连接是标定的前提条件。工具对多雷达配置也有一定支持可以通过launch参数配置多个点云话题但每个雷达和IMU的外参是分开优化的。多雷达时建议先单独标定每个雷达的外参然后再做联合优化直接一开始就联合优化容易因为某个雷达数据质量差而带偏全部结果。6. 一份能“抄作业”的标定检查单6.1 标定前的checklist我每次给新设备做标定都会过一遍下面的清单能避免绝大多数低级错误传感器固定可靠没有松动已知雷达和IMU的大致安装位置和朝向准备好初始值选择一个结构特征丰富、尺寸适中的室内环境规划一条包含直线、转弯、上下俯仰变化的采集路径录制rosbag并用rosbag info确认两个核心话题都有数据修改launch文件中的话题名和初始外参启动节点并且回放数据观察优化过程这张表看着简单但每一条背后都有对应的血泪教训。比如第四条规划路径我早期就是随手拿着设备在办公室里走了一圈结果因为缺少俯仰激励标定出来roll和pitch偏差非常大在地面上跑算法时地图整体倾斜。6.2 标定结果在真实系统里的应用拿到Transform.json之后还要做一次坐标系的整理。多数LIO算法比如FAST-LIO、LIO-SAM的配置文件里需要提供外参四元数和平移向量。这里的顺序和坐标系定义一定要和算法约定对齐。我踩过一回lidar_align输出的外参定义是“把雷达系变换到IMU系”而算法里要的是“把IMU系变换到雷达系”方向反了之后整个系统的点云全部炸开。后来我在代码库里加了一个外参求逆的检查函数每次换外参都会自动算一遍变换矩阵的行列式和逆矩阵避免了这类低级错误。另一个应用场景是手眼标定的朴素版本把这一套流程扩展到相机和雷达联合标定。社区里常说的“相机标定”和“手眼标定”技术栈虽然不太一样但核心思路是一致的都是求解两个传感器坐标系之间的刚性变换。如果你已经掌握了lidar_align的流程再去理解基于ArUco标定板的相机-雷达标定或者基于棋盘格的视觉-惯性标定上手会快很多。这算是标定这个领域里共通的底层逻辑。6.3 一些让结果更稳的进阶经验第一点条件允许时多跑几轮标定。每一轮用不同的初始值开始比如在真实安装估计值的基础上加上几度的扰动跑完看多次优化结果的一致性。一致性好的标定结果可信度才高。第二点做好地面真值的交叉验证。我用过一种简单粗暴的方法把设备放在地面上的一个精确标记处用尺子记录雷达和IMU各自到标记的距离和高度然后计算外参的平移分量。和lidar_align的结果对比能得到一个非常直观的精度参考。第三点标定这件事其实没有“一劳永逸”的答案设备用久了结构件可能因为热胀冷缩发生微小的形变所以定期重新标定也是工程上的标准化操作。我个人在实际操作中的体会是拿到一个好的外参标定结果功夫几乎都花在准备阶段。数据采好了环境对了初值合理了优化器自己就能跑出好结果反过来数据一塌糊涂换再好的工具也是白搭。希望这篇东西能帮你在Lidar-IMU标定的路上少折腾几个来回。