
做视觉惯性里程计和SLAM相关工作的人对“外参标定”这四个字一定不陌生。相机和IMU装在同一个平台上如果不知道它们之间那个固定的旋转和平移关系视觉和惯性数据根本没法真正融合。我第一次跑开源VIOVINS-Mono、OKVIS这类时最折磨我的不是算法本身而是那个怎么调都不对劲的外参。后来系统学了Kalibr才明白标定不是玄学而是一套有明确数学表达、有严格操作规范、有可验证结果的工程流程。本文就把我折腾了两周的“从理论到实践”完整路径摊开讲标定问题是什么、Kalibr怎么求解、数据该怎么录、结果怎么看、坏了怎么排查。目标只有一个让准备用Kalibr做相机-IMU外参标定的团队一次跑通少踩坑。1. 先想清楚外参标定到底在求解什么1.1 外参的数学定义一个矩阵解决的事所谓相机-IMU外参本质是两个刚体坐标系之间的相对位姿。一般用齐次变换矩阵 \(T_{CI}\) 表示它把IMU坐标系下的三维点变换到相机坐标系下。展开来看就是\[ T_{CI} \begin{bmatrix} R_{CI} p_{CI} \\ 0 1 \end{bmatrix} \]其中 \(R_{CI}\) 是3x3旋转矩阵表示IMU坐标系相对相机坐标系的姿态\(p_{CI}\) 是3x1平移向量表示IMU坐标系原点在相机坐标系下的空间位置。很多人一开始会被“C到I还是I到C”绕晕这里用一个笨办法记读作“从IMU到相机的变换”也就是把一个点先放进IMU坐标系再通过 \(T_{CI}\) 换算到相机坐标系。后面写配置、看结果、做验证时先确认坐标系方向再动手能省下不少返工时间。为什么要这个变换因为相机和IMU是两个独立传感器各自有坐标系。视觉特征的位置是在图像平面上的像素坐标IMU测量的是机体坐标系下的加速度和角速度二者空间基准不一致。如果把外参忽略等于默认两个传感器原点重合并完全对齐这在物理上几乎不可能哪怕结构设计得再好也有几毫米到几厘米的平移偏差还有安装角度误差。这些误差在融合算法里会被无限放大轻则轨迹漂移重则系统直接发散。1.2 为什么不能靠卡尺量出来听到这里你可能会问外参能不能直接量毕竟两个传感器都装在设备上用游标卡尺和角度尺也能测个大概。我的答案是能量但量不准而且量出来的值往往不够用。原因有三条。第一坐标系原点和几何中心不是一回事。相机坐标系原点在光学中心IMU坐标系原点在MEMS加速度计的敏感中心这两个点都不在传感器外壳的几何中心而且敏感中心的位置厂商不会公开很难从外部量测。第二安装过程本身有误差。哪怕结构件加工精度是±0.1mm装配时拧螺丝、贴双面胶、受热膨胀都会让实际外参偏离设计值。第三视觉-惯性融合对平移精度的容忍度远低于你的直觉。对于常见的手持设备和机载设备平移误差几毫米可能还能忍旋转误差哪怕只有一度的几十分之一在长时间积分后也会让位置漂移得没法看。所以工程上需要一个方法能从传感器自身的输出数据里反推外参。这正是Calibration要干的事。1.3 Kalibr的求解思路把外参变成优化变量Kalibr做相机-IMU外参标定本质是一个非线性最小二乘问题。它同时利用标定板上的已知特征点和IMU的角速度、加速度测量构建一个联合代价函数\[ J \sum_k \rho_{\text{vis}}(r_{\text{vis},k}) \sum_m \rho_{\text{imu}}(r_{\text{imu},m}) \]其中 \(r_{\text{vis}}\) 是视觉重投影残差把标定板上的物理点投影到图像平面与实际检测到的像素坐标比较差值越小说明外参和相机内参越准。\(r_{\text{imu}}\) 是惯性测量残差根据估计的相机运动轨迹反推出相机加速度和角速度再通过外参变换到IMU坐标系与IMU实际输出比较。整个过程用迭代优化比如Gauss-Newton框架同时估计相机内参、外参、时间偏移和重力方向。为什么要联合优化因为外参无法独立观测。你看到的每一帧图像都是相机内参、相机位姿、外参共同作用的结果你读到的每一个IMU数据也是外参和运动状态共同作用的结果。只有把两类数据放进同一个优化框架里联合求解才能让视觉和惯性互相约束把外参唯一地“锁”出来。这就是Kalibr最核心的思路理解了这一点后面所有操作上的讲究都有了依据。2. 标定前的准备环境、标定板、传感器参数一个都不能少2.1 Kalibr环境搭建比想象中简单但要注意ROS版本Kalibr依赖ROS运行所以第一步是装一个干净可用的ROS环境建议在Ubuntu上操作。安装方式有两种一种是用系统包管理器直接装预编译版本另一种是源码编译。预编译版本一般是老版本功能稳定但缺少一些较新的修复源码编译能拿到最新代码适合需要debug或者开发扩展功能的人。我自己的经验是如果不是要改源码直接用预编译版本就够了。标定流程是固定套路老版本完全够用没必要把时间浪在编译依赖上。安装官方依赖时有个小坑要先把rosdep的依赖检测跑通否则后面编译报一堆找不到包的错误。具体命令不复杂核心是把kalibr工作区用catkin编译出来然后source一下环境。装完以后验证一下rosrun kalibr kalibr_calibrate_imu_camera如果提示命令找不到多半是环境变量没有生效检查一下~/.bashrc里有没有加source /opt/ros/版本/setup.bash以及工作区的devel/setup.bash。2.2 标定板不是随便打印一张棋盘就完事Kalibr使用的视觉靶标通常是AprilGrid它不是普通黑白棋盘格而是一种带编码的方形格阵列。每个格子角落都有一个独特的二进制编码这样做的好处是即使标定板部分出画或被遮挡算法也能通过编码识别出当前看到的是哪个角点鲁棒性远高于单纯黑白棋盘。生成标定板很简单用Kalibr自带工具即可rosrun kalibr kalibr_create_target_pdf --type apriltag --nx 6 --ny 6 --tsize 0.088 --tspace 0.3这条命令会生成一个6x6的AprilGrid标定板的PDF每个格子边长约8.8厘米格子间距是边长的30%。生成的PDF直接打印出来贴在一块绝对平整的刚性平面上就能用。关于标定板材质我有三条经验。第一一定要贴在硬质平板上泡沫板、软木板、纸板都会因为弯曲引入不可忽视的角点位置误差最终影响外参精度。第二打印尺寸和target.yaml里的配置必须严格一致尤其注意有些打印机默认会开启“缩放以适合页面”这会让实际尺寸和理论尺寸差出好几毫米标定前务必关掉这个选项最好再用卡尺实际量几个格子的边长复核。第三表面不要覆膜反光的亚克力板会严重干扰角点检测。2.3 IMU参数文件别再用默认值糊弄imu.yaml是Kalibr标定时必须提供的配置文件里面主要包含IMU的topic名、更新频率、加速度计和陀螺仪的噪声密度与随机游走。这里有一个很多人会忽略的点IMU参数不能用一套默认值套所有传感器。不同型号IMU的噪声特性差异极大工业级MEMS和汽车级MEMS差一两个数量级都很常见。参数的准确度直接影响标定结果中IMU残差的权重如果参数写得太乐观噪声设得过小算法会过度信任IMU反而让外参跟着有噪声的IMU数据跑偏如果参数写得太保守噪声设得过大IMU约束发挥不了作用视觉主导下外参精度也会下降。正确的做法是找官方资料。很多IMU厂商会在datasheet里直接给出角度随机游走ARW和速度随机游走VRW可以直接换算。如果资料里没有用Allan方差分析工具对一段静止采集的数据做分析也是一条可靠路子。实在拿不到再用工程经验值# imu.yaml 示例 rostopic: /imu0 update_rate: 200.0 accelerometer_noise_density: 2.0e-3 accelerometer_random_walk: 0.005 gyroscope_noise_density: 1.5e-4 gyroscope_random_walk: 2.0e-5注意这里抄来的数值只是保守起步上线之前务必换成自己传感器的实测值。2.4 相机内参外参标定的前提相机-IMU外参标定有一个隐含前提相机内参是已知的。这里的“已知”是指你已经对同一台相机做过内参标定得到焦距、主点、畸变系数。如果内参不准外参标定结果绝对不可能准因为视觉重投影误差会把内参误差“分摊”到外参上。先用Kalibr单独标一遍相机内参rosrun kalibr kalibr_calibrate_cameras --target target.yaml --bag cam_calibration.bag --models pinhole-radtan录制内参标定的bag时也讲究手法把标定板在相机视场中从边缘到中心、从近到远、各种倾斜角度反复移动确保图像覆盖整个传感器平面特别是四个角落。畸变严重的鱼眼镜头角落覆盖不足会导致畸变系数估计崩溃。3. 核心实操录一段好bag跑一版干净结果3.1 录制rosbag的正确姿势比想象中讲究得多标定数据的质量九成取决于录制环节。很多时候标定失败或结果异常回看bag才发现运动激励不够、光照突变、标定板出画白白浪费好几个小时。录制前先确认两件事相机topic和IMU topic都在稳定发布并且时间戳是同步的。可以在终端里用rostopic hz检查rostopic hz /cam0/image_raw rostopic hz /imu0确认频率稳定后用rosbag record同时录制两个topicrosbag record /cam0/image_raw /imu0 -O imu_cam_calibration.bag录制运动时要牢记一个核心原则充分激励传感器的所有运动模态。意思就是别让设备安安稳稳待在桌子上也别让它只绕着一个轴慢慢转。好的运动模式类似“空中写数字8”拿着设备在三维空间里做大幅度的旋转和平移先绕X轴转几圈再绕Y轴转几圈再绕Z轴转几圈中间穿插前后左右的平移抖动让加速度计和陀螺仪都经历丰富的动态变化。为什么要这样做因为标定算法需要从运动数据中区分外参和传感器自身状态。如果设备静止或匀速运动加速度计的输出会被重力主导陀螺仪也没有角速度变化视觉特征位置基本不变整个系统处于“退化”状态外参不可观算法要么不收敛要么收敛到一个随机值。录制时长建议控制在30秒到90秒之间。太短数据量不足太长容易累积漂移且中途更容易出现意外。我通常习惯录制前先让设备静止2秒再开始运动给算法一个初始偏置估计的窗口。另外还有几个细节标定板要始终保持在相机视场范围内尤其不要丢失太久光照要均匀稳定避免强烈的明暗变化导致角点检测抖动IMU的采样频率尽量在100Hz以上太低会丢失高频动态信息影响IMU残差约束。3.2 三个yaml文件和一条命令标定前需要准备三个配置target.yaml、camchain.yaml、imu.yaml。target.yaml和imu.yaml的内容我们前面已经提到camchain.yaml是相机内参文件可以由kalibr_calibrate_cameras输出。一切就绪后运行主程序rosrun kalibr kalibr_calibrate_imu_camera \ --bag imu_cam_calibration.bag \ --cam camchain.yaml \ --imu imu.yaml \ --target target.yaml \ --time-calibration这里我建议始终加上--time-calibration参数。即使传感器时间戳看起来是同步的但由于相机曝光时刻和IMU采样时刻在硬件层面往往有固定偏移让算法估计时间偏移能显著提高结果一致性额外计算量也不大属于“稳赚不赔”的选项。运行过程中终端会输出大量迭代信息包括每一轮迭代的残差变化。正常情况下优化会在几十次迭代内收敛最终残差会稳定在一个较小值。如果发现残差剧烈震荡或不下降基本可以判断输入数据有问题先别急着分析结果回头检查bag质量。3.3 标定结果文件拆解运行结束当前目录会生成一组文件camchain-imucam-*.yaml、imu-imucam-*.yaml、result-imucam-*.txt、report-imucam-*.pdf有时还有可视化相关文件。result-imucam-*.txt里有最终的外参结果长这样Calibration results T_ci (imu to camera): R: - [ 0.99995, -0.00123, 0.01001 ] - [ 0.00121, 0.99999, 0.00235 ] - [ -0.01002, -0.00234, 0.99995 ] p: - [ -0.012, 0.023, 0.005 ] Timeshift camera to imu: [s] 0.00123这里要理解输出的坐标系定义T_ci (imu to camera)就是我们前面讲的 \(T_{CI}\)把IMU坐标系下的点变换到相机坐标系。旋转矩阵前三行共9个数接近单位阵说明IMU和相机之间的角度差很小平移向量三个分量单位是米表示IMU原点在相机坐标系下的相对位置。report文件是精美的PDF里面包含标定板的检测覆盖图、运动轨迹图、残差分布图。这些图表不是摆设后文我会专门讲怎么通过它们判断结果质量。3.4 标定结果不唯一谈谈“多跑几次”的正确心态很多新手第一次跑Kalibr发现两次标定结果有差异就慌了怀疑自己操作错了。实际上任何标定都存在不确定度两次结果有一定差异是正常的关键是差异大小。如果两次结果的旋转差异在零点几度以内、平移差异在几毫米以内都算合格。如果差异巨大比如旋转差了几度平移差了几厘米那一定不是正常随机波动而是数据质量出问题了。一个实用做法是同一段bag跑三遍观察结果一致性。如果三遍结果严重不一致优先怀疑运动激励不足、时间戳异常或IMU参数错误。如果三遍结果一致但明显偏离机械设计值优先怀疑标定板尺寸/光学内参错误。这种“多跑几次看一致性”的习惯能帮你快速定位问题出在数据侧还是配置侧。4. 结果验证报告看不出好坏得跑起来才算数4.1 从report.pdf里读出隐藏信息很多人在标定结束后只看txt里的数值却忽略了PDF报告里更直观的质量线索。我建议至少看三张图。第一张是标定板角点的覆盖图。图中每个检测到的角点会画出来如果角点均匀覆盖整个图像平面说明录制时标定板的位姿足够丰富如果集中在中间一块或某几个角落说明运动不够全面外参在视场边缘区域的约束不足。第二张是优化后的相机运动轨迹图。一个合格的结果轨迹应该是平滑的并且和录制的运动模式大致吻合。如果轨迹出现剧烈的锯齿或跳变大概率是时间戳匹配出了问题或者IMU数据里有明显异常。第三张是残差分布直方图。残差是最终优化后每个观测的误差理想情况下应该近似高斯分布集中在零附近。如果残差呈双峰分布、有大量离群点或分布严重偏斜说明某个环节出现了系统性误差常见原因是畸变模型选择错误或标定板不平整。4.2 用VIO做“终极检验”标定结果好不好最直接的标准是把它放进视觉惯性系统里跑一遍看系统能不能稳定工作。以VINS-Mono为例把标定出来的外参写进配置文件运行后观察两个指标一是初始化阶段的重力对齐是否正常二是长时间运行后轨迹是否明显漂移。如果外参正确即使不开回环检测短时间内轨迹都应当平滑稳定如果外参有较大误差系统在刚起步时往往就会表现出姿态震荡或轨迹异常。我个人踩过的一个坑是只看txt里的旋转矩阵接近单位阵就以为没问题结果跑VINS时轨迹左偏得厉害。后来才发现平移那一项在txt里实际上写反了方向填配置时没有做坐标系转换导致VIO里ORB特征点的深度估计和IMU预积分始终不一致。4.3 时间偏移那个容易被忽视的“第四参数”Kalibr输出文件中有一个Timeshift参数表示相机和IMU之间的时间偏移。很多人会忽略它直接填0这在小频率、慢运动的场合也许还能忍但在高频运动或快速旋转场景下一个50ms的时间不同步足以让融合结果发散。我自己做过对照实验同一套设备时间偏移设为0时VIO的姿态误差在30秒内就明显增长把Kalibr估计出的时间偏移填进配置文件后同样30秒的轨迹平滑得多。所以在把外参写进SLAM系统时务必确认系统是否支持时间偏移参数如果支持就一并填进去。5. 常见问题与排查技巧实录5.1 一张表排查标定“疑难杂症”整个标定流程涉及硬件、驱动、时间同步、算法参数、操作手法出问题的环节太多。我整理了一张速查表覆盖我遇到过的绝大多数情况现象可能原因排查与解决办法优化不收敛残差不断增大运动激励不足或数据退化重新录制加大各轴旋转和平移激励避免静止和匀速运动标定板角点检测数量过少光照变化、标定板出画、板面反光检查录制场景调整光照确保标定板全程可见外参两次标定结果差异很大IMU噪声参数错误或时间戳异常核对imu.yaml参数来源用Allan方差实测检查topic时间戳对齐旋转矩阵偏离单位阵较多机械安装角度大属于正常若结果异常波动需查数据对比机械设计值确认没有装反或坐标系定义错误平移向量量级明显不合理标定板实际尺寸与yaml不一致重新量测标定板格子尺寸修正target.yaml结果在VIO里表现差外参坐标系方向填反或时间偏移未设置仔细核对txt中T_ci定义检查VIO配置的外参方向与时间偏移相机topic和IMU topic时间戳差很多驱动时间戳源不同步校准系统时间统一驱动时间戳基准5.2 几段值得记住的“现场教训”除了上面这些可复现的问题还有几条带着现场感的心得写在这里当参考。第一条录制bag之前一定要先确认相机曝光模式。很多工业相机默认是自动曝光在快速来回运动时曝光时间会不断调整导致图像模糊和角点检测抖动。标定场景光照稳定的前提下尽量把相机设为固定曝光时间降低动态模糊的影响。第二条别把标定板拿在手里晃。手持方式不稳会引入高频抖动让角点检测不稳定。更好用的做法是把标定板固定在墙面上或支架上让手拿着设备运动这样标定板稳定设备运动又能充分激励IMU。第三条是时间同步的“土办法”。在做硬件选型或驱动调试时可以用一个简单实验检查时间戳质量让设备快速旋转同时观察IMU角速度曲线和相邻帧图像变化是否高度相关。如果在快速旋转瞬间IMU角速度峰值和图像模糊发生的时刻明显错开说明时间戳标定或者驱动设置有问题需要先解决。5.3 一套我常用的“保底验证清单”踩过足够多的坑之后我现在每次标定完都会按一套固定清单自查效率很高。确认结果文件和bag的时间戳对应没有用错bag检查T_ci的旋转矩阵是不是接近单位阵设计上确实接近时检查平移向量量级和机械设计是否在一个合理范围打开PDF报告看角点覆盖、运动轨迹、残差直方图三项把外参写进VIO短距离测试轨迹是否平滑重启系统重新标一遍对比两次结果是否一致。如果这六项都过了基本可以放心把这份外参用到正式系统里。写在最后的一个小建议我知道很多人看到“标定”两个字就容易紧张觉得理论复杂、流程繁琐。但实际上Kalibr把最难的数学都封装好了你真正需要做好的是给它喂一段高质量的数据。而“高质量”三个字就藏在你对运动的安排、对配置文件的认真、对结果文件的仔细检查里。我每次标定新设备都会把录制过程当作正式操作来对待不图快不将就因为我知道标定误差是后置性的它会在整个系统上线后慢慢暴露出来那时候再回头排查的成本要高得多。多说一句如果你用的是边角料摄像头或者陀螺仪漂移特别明显的廉价IMU也别灰心只要数据质量到位Kalibr依然能给你一个可用的外参标定这事数据比传感器本身更值钱。