最近在调一台差速底盘移动机器人,传感器配置很常见:一个水平安装的单线激光雷达、一颗九轴IMU、两个轮子的编码器里程计。定位和建图跑的是hdl_graph_slam,这套开源方案本身很成熟,但真正让我崩溃的不是算法,而是IMU、底盘车轮里程计、单激光雷达三者之间的外参标定。外参不准,后面所有建图、定位全是白费功夫。这篇工作笔记就把我整个标定过程、踩过的坑和验证方法完整记录下来,给同样在搞机器人外参校准的同学做个参考。
1. 先搞清楚:这套系统里三个传感器各自扮演什么角色
1.1 hdl_graph_slam的传感器分工与数据流
hdl_graph_slam是日本名古屋大学团队开源的3D SLAM方案,虽然名字里带graph_slam,但它对传感器配置的宽容度很高,很多做室内移动机器人的团队都会把它改造适配到单线激光雷达上。在我的这套配置里,三个传感器的角色是这样分的:
底盘车轮编码器输出的是2D里程计(x、y方向位移加绕z轴的旋转角),频率一般能到50Hz甚至100Hz,短时间内非常稳定,但在打滑或者地面不平的场景会迅速累积误差。IMU输出的是三轴角速度和线性加速度,频率通常在100Hz到200Hz之间,它的roll和pitch绝对可信,yaw会漂移但短时相对变化很准。单线激光雷达输出的是2D平面内的激光扫描,扫描匹配(scan matching)给出的相对位姿变化非常精准,是整套建图定位的主心骨。
在hdl_graph_slam的数据流里,机器人底盘里程计和IMU负责提供高频先验位姿,让激光扫描匹配有一个好的初始值,激光数据本身通过NDT或者ICP与局部地图进行配准,生成更精确的帧间位姿,最后送入因子图优化,后端再做回环检测和全局优化。这三个信号是互相补充的关系:激光精度高但没有绝对朝向参考,IMU提供了稳定的姿态参考但yaw会漂,底盘里程计提供了平滑的位移参考但会打滑。外参标定的目标,就是把这几个坐标系之间的刚性变换关系精确求出来,让它们在时间同步的前提下能对齐到同一个空间上。
1.2 外参标定到底在标什么:坐标系变换的本质
外参标定,说白了就是求两个传感器坐标系之间的旋转变换R和平移变换t。激光雷达有它的坐标系,通常x轴朝前、z轴朝上;IMU也有自己的坐标系,每个IMU的数据手册上都会画;底盘的车轮里程计则工作在base_link或者base_footprint坐标系下。在ROS里,所有这些坐标系最终靠tf数树串起来,任何一个frame之间的变换错了,下游节点直接起飞。
我见过不少人觉得外参差个一两度没大碍,这是最大的认知误区。用最简单的情况算笔账:如果激光雷达相对底盘的yaw角标定误差是1度,机器人直行10米,激光点云构建出来的地图相对于真实墙面的横向偏差就是10×sin(1°)≈0.17米。这还没算里程计轮径误差、IMU安装倾斜这些叠加因素。在一个20米的走廊里走一圈回来,地图偏出半米很正常。所以外参标定的精度,直接决定建图回环能不能闭合、定位能不能稳定跟踪,是所有上层功能的地基。
2. 标定之前的准备工作:硬件安装与底盘里程计预处理
2.1 三个传感器的物理安装要点
外参标定不是纯软件能解决的,硬件安装方式很大程度上决定了标定的难度和工作量。我这次的做法是在机器人底座上焊了一个铝型材支架,三个传感器全部固定在一个刚性平台上。这个点极其重要,如果传感器之间存在弹性连接,车辆震动后外参就变了,标定结果再准也没有意义。
安装时需要注意几个容易被忽略的细节:激光雷达尽量水平安装,让扫描平面平行于地面,这样后续建图时地面的干扰小;IMU的安装方向尽量与底盘坐标系对齐,哪怕差个几度都没关系,但一定要确保安装牢固,不允许有微小晃动;底盘车轮的编码器安装精度决定了里程计的原始质量,链条传动或者皮带传动会引入间隙和形变,能直连减速电机最好直连。另外,三个传感器的线缆要走好,避免在运动过程中拉扯导致传感器位置偏移。
还有一个很多人不重视的点:在记录数据之前,用水平尺检查激光雷达顶面是否水平,用倾角仪记录IMU安装座相对水平面的初始角度。这些物理信息在后面标定roll和pitch的时候可以用来互相验证,防止软件标定结果出现方向性的错误。
2.2 底盘轮式里程计的预处理:轮径、编码器与轮距
很多人一上来就做传感器外参标定,结果发现怎么标都不对,最后排查下来是底盘里程计本身的内参就是错的。轮式里程计的内参主要指三个量:轮径、编码器每圈脉冲数、左右轮距。这三个量不正确,里程计输出的位移和旋转就是错的,拿错误里程计去标激光外参,自然不会有好结果。
轮径的测量建议用“滚动测量法”,不要直接用卡尺量轮胎外径,因为在自重下轮胎会压扁,动态滚动半径和静置半径差别很大。我的做法是:在平整地面上用粉笔画一条起点线,推动机器人让两个轮子各滚10圈,用卷尺量总距离,除以10π得到实测轮径。这样做一次能同时验证编码器的每圈脉冲率是否和码盘标称一致。
轮距的测量要分驱动方式,差速底盘直接量左右轮接地点之间的垂直距离,也就是轮子胎面中心面在横向上的间距。对于用增量式编码器加减速箱的底盘,编码器脉冲计数和轮子转角之间的关系可以简单写成:轮子转动角度(rad) = 编码器脉冲数 / 编码器每转脉冲数 / 减速比 × 2π。左右轮转角差除以轮距就是底盘的旋转角速度,左右轮速度平均值就是底盘平移线速度。把这些公式在代码里跑通,用遥控器让机器人原地转一圈,看里程计输出的累计旋转是否接近360度,就能快速验证轮距和轮径是否正确。
2.3 IMU的内参确认与数据检查
IMU数据在使用前应该做一些基本检查,否则容易把外参标定结果误导到错误方向。检查项目包括:轴正负方向是否和ROS标准定义一致(x向前、y向左或向右、z向上要按实际传感器的手册确认),静止时加速度计的模长是否接近9.81,静止时陀螺仪的零偏是否足够小,以及rostopic的发布频率是否稳定。
我这次用的IMU在数据手册里和ROS坐标系定义就有冲突,手册里的y轴方向和ROS标准定义差了个负号。如果直接接入hdl_graph_slam不做修正,ROS的tf系统会用错误数据去计算,这种问题在标定时完全暴露不出来,因为数据曲线看起来都是正常的,只有跑建图时才会发现在某个方向上地图被系统性扭曲。所以建议在标定前先把三个轴的加速度和角速度录一段数据,人为地朝已知方向晃动传感器,核对数据变化方向是否符合预期。
拿到原始数据后,还要检查时间戳是否连续、是否存在乱序。IMU频率高,对时间戳的一致性要求也高,有个简单方法:记录1万条消息,看时间戳差值分布是否均匀,如果存在明显跳变,就要在驱动里做时间戳平滑处理。
3. 底盘轮式里程计到激光雷达的外参标定
3.1 手动量测初值:不精确但能兜底
在所有精细标定之前,先用卷尺和角度尺手动量出一组外参初值,这组值不要求非常精确,但必须保证方向正确、量级不出错,这样后续的自动标定才有合理的迭代起点。
具体量测对象是激光雷达坐标系原点在底盘坐标系中的位置,以及激光雷达朝向相对底盘朝向的旋转角。实际操作起来是这样的:把机器人抬到一个平台上,用线锤把激光雷达扫描中心投影到底盘平面上,测量它到后轴中心(差速底盘取两轮连线的中点)的纵向距离和前向距离,这就是x和y的平移量。z方向则直接量激光雷达扫描平面到底盘落地平面的高度。yaw角可以量激光雷达侧面与底盘侧面的平行度,如果安装时用直角靠尺贴过,这个角度误差应该在1度以内。
量出来的初值记下来,写进Urdf或者tf发布脚本里作为初始外参。这里有个经验:不要等到标定完成才把外参灌进系统,而是先用量测值把tf跑起来,保证我们后续的标定脚本能够实时拿到准确的传感器数据。手动量测虽然粗糙,但胜在靠谱,它能帮你在后续算法标定出异常值时兜个底,一眼看出结果是不是离谱。
3.2 直线行驶标定法:x/y偏置与yaw角
接下来做一次直线行驶实验,这个实验能把激光雷达相对底盘的yaw角粗标出来,同时还能验证z偏置是否合理。操作方法是:在机器人前方大约5到8米处垂直放一块大平板(比如几块纸箱拼起来),然后让机器人朝平板方向直线行驶2米左右,期间记录激光扫描数据和底盘里程计数据。
核心原理是,当机器人沿底盘x方向直线运动时,激光雷达扫描到的平板应该是垂直的,也就是在激光坐标系下,平板平面和激光x轴的夹角,直接反映了激光雷达相对底盘的yaw装偏角。如果激光外参里yaw是0,那么平板点云拟合出来的直线方向和激光x轴应该垂直,一旦不垂直,这个角度偏差就是需要补偿的yaw误差。用最小二乘法拟合点云里的直线,取出直线法向量,计算其与x轴的夹角,就是我们要标定的yaw值。
x和y偏置的标定稍微复杂一点,但也可以用直线行驶加旋转组合完成。具体做法是让机器人原地旋转90度后再走一段直线,通过对比底盘里程计给出的位移变化量和激光点云中参考物位置的相对变化,构建方程组解出x、y偏置。公式推导本质上是刚体变换的坐标平移方程,实际操作时直接写一段Python脚本,用最小二乘法求解一个2×2线性系统即可。这个过程中最容易出的问题,是直线行驶时车轮本身的行驶方向偏差与激光外参yaw耦合耦合在一起,所以建议前后做两次反向行驶取平均,抵消底盘开不直的系统误差。
3.3 利用建图结果反查外参残差
直线标定做完之后,外参的粗值已经可以用了,但离精细的精度要求还差一些。这时候我建议直接跑一次hdl_graph_slam建图,然后从建图结果里反查外参残差,这是一种工程上实用的细标定方法。
具体做法是在一个特征丰富的室内环境里建图,比如办公室走廊加房间的区域,确保有大量垂直墙面、柱子、转角这些几何特征。建图完成之后,把生成的地图点云用可视化工具打开,先看墙壁的厚度和锐利度,如果墙是双层甚至三层虚影,说明外参还有问题。此时可以检查特定的残差模式:如果机器人行驶方向上的墙出现重影,最可能是激光x方向的外参偏差或者yaw残差;如果转弯处地图出现扇形模糊,通常是激光y方向外参偏差过大,导致旋转中心不在激光坐标系原点。
更精确一点的做法,是把底盘里程计位姿和激光scan matching输出的位姿做时间对齐后,用一段包含直线和转弯的数据,计算两者transform的相对序列。理论上这段相对序列里,旋转部分应该始终为零,如果出现稳定的非零旋转,就是外参旋转残差;平移部分则体现外参平移残差。把这套残差序列用优化器(Cerres或G2O)做一次最小二乘拟合,就能把外参精修到位。
4. IMU到激光雷达的外参标定
4.1 为什么IMU外参这么容易翻车
IMU外参标定是整套系统里最容易翻车的环节,原因是IMU和激光雷达都提供“姿态”信息,但姿态定义存在歧义。激光雷达的姿态变化是通过scan matching得到的,它的参考系是激光坐标系本身,而IMU的姿态是相对世界坐标系的absolution朝向。两者之间只差一个固定的旋转外参,但这旋转外参同时耦合了安装旋转和坐标定义不一致,导致很多人在标定时区分不开。
另外,IMU外参中的roll和pitch与yaw对系统的影响方式完全不同。roll和pitch标定不准,会让点云在重力方向上产生倾斜,地图呈现出整体扭曲、越远越明显;yaw标定不准,会让IMU提供的角速度先验在水平面内转错方向,导致激光匹配在快速旋转时发散。必须分别对待。
还有一个隐藏坑:IMU的安装通常不保证和底盘坐标系完全水平,一旦有颠簸,IMU测量的重力方向和对惯性量的分解就会出现偏差,让融合滤波器把不存在的倾斜当作真实运动补偿掉,造成点云整体扭转。所以IMU外参如果没有标定到位,在移动机器人上直接表现就是建图稍微走远一点就开始扭麻花。
4.2 静态对齐法:重力方向定roll/pitch
IMU外参的roll和pitch可以通过静态数据直接求出来,原理非常直观:机器人静止时,IMU加速度计测量到的只有重力加速度,方向竖直向下。如果IMU和底盘之间不存在roll和pitch安装角,那么加速度计的读数应该是(0, 0, 9.81)(在z向上的坐标系中)。读数里出现了横向的分量,就说明IMU相对底盘存在倾斜。
把机器人放在一个尽量水平的平地上静止一段时间,录制IMU加速度原始数据,取平均值消除噪声。用平均值反算翻滚角:roll = atan2(acc_y, acc_z),pitch = atan2(-acc_x, sqrt(acc_y² + acc_z²))。把这两个角度作为外参旋转矩阵的R_x和R_y,就可以把IMU的姿态对齐到底盘的重力方向。
这里面要注意,静态对齐法求的是IMU相对“重力垂直方向”的姿态,而不是相对底盘机械坐标系的姿态。如果底盘本身放在斜坡上或者轮子气不平,这两个概念会混在一起。所以操作时一定要找水平平台,并且在前轮后轮都置零的情况下静置。通常做法是把机器人放在一块已知水平的桌面上,或者用垫片把底盘调到水平,保证静态对齐结果可信。
4.3 动态标定:旋转激励法(简化版手眼标定)
roll和pitch求完之后,剩下yaw。IMU的yaw外参求解要借助运动激励,本质上是简化版的手眼标定:在同一个时间段内,IMU输出的姿态变化量和激光scan matching输出的姿态变化量,它们之间只相差一个固定的旋转外参。
实际操作时,让机器人原地做一系列缓慢、大幅度旋转,速度不要快,角速度控制在每秒20度以内,避免激光扫描畸变和IMU饱和。旋转角度要足够大,累计转动应超过180度甚至360度,覆盖充分的旋转激励。在旋转过程中同步录制IMU姿态四元数序列和激光scan matching输出位姿序列,把两者的旋转增量序列对齐。
求解时,最直接的办法是把每一帧的相对旋转写成四元数,构造一个最小二乘问题:给定IMU相对旋转q_imu和激光相对旋转q_laser,找一个固定四元数q_ex使得对每个时刻满足 q_ex ⊗ q_imu = q_laser ⊗ q_ex。这是典型的手眼标定AX = XB形式,可以使用开源库中的经典解法(比如Tsai方法)求解。实际上因为只有yaw一个未知量,加上前两步已经锁定了roll和pitch,可以直接将两种旋转的角度增量序列做差取平均,得到yaw偏置角,不需要完整解矩阵,更简单可靠。
关键点在于绘图检查:把IMU yaw角度和激光scan匹配yaw角度放在同一张图里,旋转段应该保持平行,如果两者之间存在一个常数偏置,该偏置就是misyaw外参。此时还需要检查平行度是否随时间漂移,如果漂移则说明IMU本身的yaw在旋转过程中有累计误差,需要先检查IMU陀螺仪的零偏。
5. hdl_graph_slam中的外参配置与效果验证
5.1 参数文件里怎么填:frame_id与tf配置
外参标定完成后,要正确地把这些值写进hdl_graph_slam和相关节点的参数配置里。以我这次实践为例,ROS的tf树结构是:map → odom → base_link → laser、base_link → imu_link。激光雷达和IMU相对底盘的tf,由静态坐标变换发布节点发布,漂移量就是用我们标定出的外参。
在hdl_graph_slam的launch文件和相关的yaml参数文件中,需要确认几个关键配置项。首先是输入topic的remap,scan_matching_odometry节点订阅的是/scan或者/points_raw,IMU节点订阅的是/imu/data_raw或/imu/data,底盘里程计如果需要接入先验位姿,需要把/odom正确remap给相应节点。其次是frame_id一致性,激光雷达点云的frame_id要设置为标定对象对应的激光坐标系名称,IMU消息的frame_id要和IMU的tf名称一致,否则tf系统在查询变换时会直接报“unknown frame”,根本跑不动。
还要注意hdl_graph_slam里对地面去除的处理:它原本是针对3D激光设计的,有floor_detection节点来过滤地面点。单线激光雷达水平安装时,激光扫描平面几乎和地面平行,实际上扫描到的并不是完整的地面,而是墙面脚线附近的高度层。此时应该把与地面相关的参数关掉,或者直接不使用floor_detection节点,避免它把墙面误判为地面点滤掉。同时校准时z轴方向的外参偏差,对2D建图影响不大,但对后续加装3D传感器或者做斜坡场景就很重要,不能省。
5.2 建图质量的几个验证指标
外参标定得好不好,跑一圈建图就能立刻看出来。我会在标定完成后做一组标准化的验证流程,拿到几个关键指标来判断是否达到预期。
第一个指标是地图中墙体的点云厚度。在理想情况下,墙面应该是一个约等于激光雷达测距噪声(2到3厘米)的薄层,如果墙体呈现扫描线宽方向的弥散,说明外参标记不准或者存在震动。第二个指标是机器人回到起点时的回环闭合误差。实际做法是让机器人沿着一个形状跑一大圈回到起点,等回环检测触发后,看后端优化修正幅度。如果修正幅度超过20厘米而地图在闭合之前明显错位,说明某个外参很大概率还有残差。第三个指标是旋转处的点云锐利度,在有直角墙角的房间原地旋转180度,查看墙角点云是否出现分叉或者圆角模糊。
除此之外,我还会用reflection matching的方式检查激光点云是否与imu朝向一致:在浏览器里同时显示点云和IMU车体朝向箭头,晃动机器人,观察箭头和点云旋转是否保持同步。这项测试非常直观,如果有延迟或相位差,直接提示外参或时间戳问题。
5.3 定位模式的切换与外参一致性
hdl_graph_slam既能建图也能定位,但要实现从建图到定位的平滑切换,外参必须始终一致。换句话说,建图和定位过程中,激光雷达、IMU、底盘三者之间的相对关系不能变,一旦变了,地图得重建,外参得重新标。
在定位模式下,我通常把scan matching的初值提供给ndt匹配,同时用IMU和底盘融合的位姿作为预测,来补偿激光匹配在重复场景下的退化问题。实际测试时,用遥控器沿建图路径反向走一遍,看看定位位姿是否紧贴地图墙线,以及位姿是否出现跳动。如果反向走时位姿明显偏向一侧,最常见原因就是外参yaw标定时用的行驶方向和多路径不完全一致,底盘开不直导致的系统误差起了作用。这时需要做多方向行驶的记录来平均掉这些系统偏差。
另外我养成了一个习惯:在每次标定完外参后,把三组值(激光相对底盘、IMU相对底盘、底盘轮距轮径)存入一个配置文件并打git tag,出问题时可以直接回滚对比,避免反复标定导致版本混乱。
6. 常见问题与排查技巧实录
6.1 建图出现重影或墙体分层
这个现象几乎是外参问题最典型的表现。如果重影的距离和行驶速度成正比,优先怀疑激光雷达相对底盘的x方向平移外参不准,因为x偏置误差会导致激光位置时刻滞后或超前于真实位置。如果重影只出现在转弯段,优先怀疑y方向平移外参,因为旋转中心不在激光原点时,转弯时激光点云会整体做圆弧偏移。
处理方法是先回到第3.3节的反查步骤,用优化器求解平移残差。有几次我偷懒跳过细标定直接跑长走廊,结果地图两侧墙壁越来越厚,最后走廊宽度比真实宽度大了20多厘米,检查后才发现是激光y方向外参少了3厘米。这个数量级靠肉眼在CAD里看不出,必须通过优化器求。
6.2 机器人直行但地图斜着偏
这种问题的根源大概率是yaw外参标定不准,但也有可能是底盘的单侧轮径标定错误,导致差速底盘本身上下直行时带了一个恒定转向角。区分方法很简单:让机器人向前走5米再倒着走回原点,如果地图前后两次轨迹重合说明是yaw外参,如果不重合说明是轮径不一致或者底盘安装本身歪。
针对这一现象我曾经吃过一次教训:最开始用直线标定法求到的yaw外参是-1.2度,但建图还是往右偏。后来发现是激光安装支架本身在碰撞中弯了0.5度,测量簧片及支架的水平度后才发现,重新校正了安装件再标定就正常了。所以出现方向性偏移时,先检查机械结构是否变形,再怀疑算法。
6.3 旋转时点云出现“甩尾”拖影
旋转时点云甩尾,是IMU外参yaw和激光外参yaw两个都有问题的最典型症状。原因是机器人旋转时,IMU了提供错误的旋转先验,导致scan matching在旋转过程中的迭代收敛到错误局部极小值,点云位置落在真实位置之后或者之前,形成类似拖影的效果。
排查办法是先固定激光外参,单独调整IMU外参yaw,观察甩尾方向是否变化。如果变化了,说明IMU外参占主导;如果没有变化,说明激光外参占主导。这招在实战中非常有效,两个参数可以交替迭代,每次调整量为上次的一小半,通常三四轮迭代就能收敛到不错的效果。另外要提醒,旋转速度不要过快,点云畸变本身就是甩尾的一个重要来源,和IMU后端频率也有关系。
6.4 记录数据时的几个习惯性操作
标定效果好不好,数据质量占一半。记录标定数据时,有几个习惯特别值得养成:首先是录数据前把传感器充分预热,IMU尤其需要预热几分钟再录,否则零偏漂移会把标定结果带偏;其次是记录数据时使用rosbag,把所有topic一起记录,并且确保时间戳同步,后面回放时可以反复调参测试;最后是在标定过程中记录实验环境描述和操作动作,比如“直线2米朝东、原地顺时针360度”,这样后期分析数据时就能对照物理含义。
还有一个奇葩但有效的技巧:每次标定完成后,我会拿高斯分布随机做十几组微扰动,加到标定好的外参上,让机器人重复一段固定路径,看建图质量的退化方式。这一步能确认系统对外参误差的敏感方向,帮你了解哪一项是必须精确标定的、哪一项差个几毫米没关系,后续评故障时能快速锁定方向。
我个人在实际操作中的体会是,外参标定是一项系统工程,三分算法七分数据,机械安装的高度一致性比任何精巧的标定算法都重要。不要指望一步到位,先配一个靠谱初值跑通流程,再逐项精修,每次只动一个量,看清效果再动下一个,这是最稳妥的路径。这套流程做完,hdl_graph_slam建图定位的效果会立竿见影,地图墙线锐利、回环闭合稳定、定位不再漂移。后续如果再换传感器或者改底盘,只需照着这套方法重新标一遍即可,整套流程二十分钟就能跑完。