如果你做视觉惯性里程计、SLAM,或者任何依赖相机加IMU融合定位的系统,那你迟早会撞上同一个问题:相机和IMU之间的相对位姿到底是多少。相机坐标系原点在光心,IMU坐标系埋在电路板内部,中间还隔着安装角度偏差,靠卡尺量根本量不出来。这个问题不解决,后面跑VIO、跑SLAM,状态估计里始终藏着一个系统性偏差,短时间内看着没事,时间一长必然拉夸。
我以前跑VIO项目的时候,第一件事就是把相机内参、IMU噪声参数、相机与IMU之间的外参以及两者的时间偏移全部标定干净。这一套流程里,社区最常用的工具就是Kalibr。Kalibr能做多相机标定,也能做相机和IMU的联合标定,一次优化把旋转、平移、时间偏移全部估计出来。这篇总结就是把整套流程掰开揉碎讲一遍,从原理到实操,再到我踩过的坑,希望能帮你少走一段弯路。
1. 为什么要做Camera-IMU联合标定
1.1 单传感器标定解决不了的问题
相机有自己的内参,焦距、主点、畸变系数,这些可以用棋盘格或AprilTag单独标定。IMU也有自己的噪声特性,陀螺仪零偏、加速度计噪声、随机游走,这些可以用艾伦方差之类的手段分析。但这两者之间跨传感器的关系,单传感器标定完全覆盖不到。
外参就是相机坐标系与IMU坐标系之间的刚体变换,简单说就是从相机光心到IMU物理中心的相对位置和相对旋转。这个量对融合算法来说是硬输入,外参错一点,视觉和惯导给出的运动估计就会互相矛盾。典型的例子是:相机认为你往左转了1度,IMU认为你往右转了2度,融合算法只能折中,结果就是轨迹偏、姿态飘。
时间偏移是另一个容易被忽略的量。相机在曝光瞬间记录图像,IMU在采样时刻记录加速度和角速度,两个传感器的时钟基准往往不是完全同步的。尤其是低成本IMU和滚动快门相机组合,时间偏移可能达到几十毫秒。这个偏差在快速运动观察标定板时会产生明显的拖影和错位,对联合标定的精度打击非常大。
1.2 Kalibr到底做了什么
Kalibr的一次联合标定,输入是标定板的视频帧序列加IMU数据序列,输出是相机内参、畸变参数、相机到IMU的外参旋转矩阵和平移向量,以及时间偏移。所有参数被放在一个非线性优化问题里,同时用视觉重投影误差和IMU运动学误差对齐估计,最终写成一个 YAML 文件加一份可读的 PDF 报告。
社区里其实也有其他解决方案,比如有些VIO系统内置了在线标定模块,MATLAB也提供了类似的标定工具箱。但我个人还是倾向于用Kalibr作为独立的离线标定工具,原因是它的结果更透明,优化过程中会输出逐帧误差、残差分布、收敛情况,这些信息对排查问题非常有价值。而且它支持多种相机模型和畸变模型,适应鱼眼、广角、针孔等不同镜头类型。
2. 底层逻辑:这个标定是怎么算出来的
2.1 把标定变成一个非线性优化问题
Kalibr的核心思想很直接:把一段运动轨迹用参数曲线表示,让相机观测到的标定板特征点和IMU测量到的角速度、加速度共同约束这条轨迹。相机视角下,标定板上的三维特征点应该投影到图像检测的位置;IMU视角下,轨迹的角速度、加速度应该和陀螺仪、加速度计读数一致。
这两类约束并不直接看图说话,而是同时依赖于外参和时间偏移。外参决定相机运动和IMU运动之间的坐标变换关系,时间偏移决定哪一帧图像对应哪一段IMU数据。Kalibr把所有参数统一放进一个非线性最小二乘框架,迭代求解,最终收敛到让两类误差都尽量小的一组参数。
2.2 B样条轨迹:为什么非它不可
这里有个关键问题:相机是离散采样的,比如30fps或60fps,IMU是高频采样的,比如100Hz到200Hz。两套数据的采样时刻天然不同,怎么比较?Kalibr的答案是:用B样条曲线来参数化连续运动轨迹。
你可以把B样条想象成一条“非常平滑的曲线”,由若干控制点控制走向。这条曲线给定任意一个时间值,都能算出对应的位置、姿态、角速度和线加速度。有了这条连续轨迹,相机帧和IMU采样点就都能插值到各自对应的时间上,再做误差计算。这也是Kalibr最值得一提的设计选择。
B样条还有一个好处:它本身具有光滑性,不容易出现突变。IMU输出的角速度和加速度本质上是运动轨迹的导数和二阶导数关系,如果轨迹参数化得太“硬”,求导之后会抖动得很厉害,标定结果必然不稳定。B样条天然带有平滑约束,既适应了IMU连续测量特性,又让优化过程更稳健。
2.3 可观测性:为什么运动方式那么重要
联合标定需要运动激励充分,说人话就是:标定过程中设备必须转得足够花哨。只平移不旋转,或者只绕一个轴转,系统就无法同时观测到旋转外参的各个分量。常见的要求是绕多个轴做大幅旋转,配合平移和变速运动。
这里有个物理上的直觉:如果你让设备匀速平移,IMU的加速度计测到的是一个几乎恒定的加速度,而相机看到的是标定板在画面里匀速移动。这种情况下,平移外参的某些方向和旋转外参的耦合很难区分。反过来,你猛加速、急刹车、大幅旋转,轨迹的加速度和角速度分量丰富,约束就强,参数收敛就快,结果也稳定。
3. 标定前到底要准备什么
3.1 工具链和运行环境
Kalibr早期版本深度依赖ROS环境,因为数据格式主要走ROS bag。不过现在的新版本已经可以直接在纯Python环境下使用,也可以通过源码编译运行。我实际用的是Python路径,避免了一整套ROS依赖带来的麻烦。
依赖项主要有numpy、OpenCV、scipy、pyyaml、matplotlib和imageio。源码拉下来之后,按官方说明编译即可。如果你在Ubuntu上操作,建议先确认Python版本和OpenCV版本,版本不匹配会触发一堆莫名其妙的报错。装好之后可以在命令行直接跑kalibr_calibrate_cameras --help验证环境,能打印帮助信息就说明基础环境OK了。
3.2 标定板的选择和YAML配置
标定板是联合标定的视觉基准,能用AprilTag就用AprilTag,优先于传统棋盘格。AprilTag的角点检测比棋盘格更鲁棒,每个tag自带ID,部分遮挡时也不容易匹配错误,检测精度更高。
标定板的配置写在YAML文件里,常见格式是这样的:
target_type: 'aprilgrid' tag_size: 0.05 tag_rows: 6 tag_cols: 6 tag_spacing: 0.3tag_size是单个tag的边长,单位是米,必须测量准确。tag_spacing是tag之间空白间距相对tag边长的比例,打印后也要实际核对。很多人在这里填错,标定结果会偏得很离谱,这是最容易被忽视的一个细节。
打印标定板时,建议用不反光的哑光纸,贴在平整的硬板上。板子本身要平整,尺寸要够大。手持太小的标定板,离相机远了就检测不到,离近了又超出视野,数据采集会非常痛苦。
3.3 IMU噪声参数从哪来
联合标定需要先提供IMU的噪声参数和随机游走参数,Kalibr不负责估计这些东西。这些参数通常从IMUdatasheet里直接查到,比如陀螺仪噪声密度、加速度计噪声密度、陀螺仪随机游走、加速度计随机游走。如果没有datasheet,也可以用长时间静止数据做艾伦方差分析来估算。
实际操作中,这些参数不要求特别精确,大概在一个量级内基本可用。它们的作用是为IMU误差项提供权重,偏差太大时整个优化会过度信任视觉或者过度信任IMU,导致结果不稳定。另外IMU的更新频率必须准确填写,这个直接影响运动轨迹插值的正确性。
3.4 数据采集标准
采集质量决定标定结果上限。光线要充足且均匀,标定板不能有明显反光和阴影,否则特征检测精度会下降。相机曝光时间尽量调短,减少运动模糊。如果相机支持全局快门,优先选全局快门,滚动快门会引入额外的帧内畸变,标定精度要求高时最好能规避。
运动方式上,我自己的经验是:手持设备,动作要慢但幅度要大。大幅旋转六个方向都覆盖,加平移、加速、减速,整个过程保持标定板大部分时间在视野内。每次采集建议至少1-2分钟,IMU频率100Hz以上,相机帧率不要低于30fps。
4. 实操全过程:从内参到外参
4.1 第一步:单目相机内参标定
联合标定前,先把相机内参标出来。虽然Kalibr理论上可以同时估计内参和外参,但在我的工作流程里会先单独标一次内参,把相机的焦距、主点、畸变固定下来,这样联合标定的优化变量更少,收敛更稳定。
标定命令大概是这样:
kalibr_calibrate_cameras \ --target april_6x6.yaml \ --bag cam_imu_01.bag \ --topics /cam0/image_raw \ --models pinhole-radtan \ --show-extraction--models指定相机模型和畸变模型,普通针孔镜头用pinhole-radtan就行,鱼眼镜头根据情况用omni-radtan或equidistant。--show-extraction可以可视化角点提取情况,强烈建议加上,能亲眼看到每一帧特征点到底有没有提准。
跑完后会生成camchain YAML文件,里面包含内参矩阵、畸变系数、图像分辨率。每个数值看一眼,量级是否正常要心里有数。焦距单位是像素,几百到几千都正常,畸变系数如果大得离谱,多半是提取或配置有问题。
4.2 第二步:联合标定命令
内参就绪后,开始联合标定。命令格式如下:
kalibr_calibrate_imu_camera \ --target april_6x6.yaml \ --cam camchain_cam0.yaml \ --imu imu.yaml \ --bag cam_imu_01.bag \ --time-calib true其中imu.yaml要手写,记录IMU频率和噪声参数:
accelerometer_noise_density: 0.0002 accelerometer_random_walk: 0.00002 gyroscope_noise_density: 0.0001 gyroscope_random_walk: 0.00001 update_rate: 200.0 rostopic: '/imu0'--time-calib true表示启用时间偏移估计。如果你的设备时间同步已经很准,也可以设false,但绝大多数场景建议保留true,Kalibr会连同外参一起优化出时间偏移。
运行时间取决于数据长度,我通常1-2分钟数据,单线程跑下来需要几分钟到十几分钟不等。期间会输出大量迭代信息,看到误差下降曲线逐渐趋于平缓就是好现象。
4.3 第三步:结果解读
联合标定输出三样东西:一个带外参的camchain文件、一个修正后的imu.yaml、一份PDF报告。报告里最重要的是残差分布图,包括重投影误差、陀螺仪误差、加速度计误差。重投影误差通常在亚像素级别才算理想,IMU残差则应该在零附近随机分布,如果出现明显趋势性偏差,说明标定过程可能有问题。
外参结果会以旋转矩阵加平移向量的形式给出,也可能同时给出四元数。拿到之后要做一件事:把它转换成你系统代码里的格式,并和实际安装方向做个常识检查。比如相机朝前、IMU朝上,旋转矩阵应该大致符合预期。平移量的量级也要符合物理常识,离谱的值多半是某个环节出错了。
时间偏移结果在报告里也会有显示,单位通常是秒。正负号表示相机和IMU的时钟先后关系,绝对值在毫秒量级比较常见。如果优化出的时间偏移极大,比如几百毫秒,说明数据时间戳本身就有问题,需要先检查录制链路。
4.4 第四步:结果验证
标定结果不能光看报告好看就完事,我在实际工作中一定会做两次复核。第一次是把同一段数据跑两遍,用不同的初始值或更换部分参数,看外参结果是否稳定。第二次是拿标定结果去跑一个VIO或SLAM demo,观察轨迹是否平滑、有没有系统性漂移。
还有一种快速验证方法:把标定出的外参和时间偏移应用到没有参与标定的新数据上,做一个纯视觉惯性对齐实验,看短时间内的估计姿态是否贴合实际运动。如果新数据上表现也稳定,基本可以认为标定结果可信。
5. 常见问题与排查技巧实录
我在使用中遇到过的坑,整理成下面这个速查表,基本覆盖了Kalibr联合标定最常见的故障场景。
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 特征提取不到或提取率极低 | 运动模糊过大、光照不足、文化暗 | 降低曝光时间、增强光照、放慢运动速度 |
| 重投影误差持续超标 | 标定板尺寸配置错误、相机内参不准 | 重新测量tag_size/间距、重新标内参 |
| IMU残差呈现锯齿状 | IMU噪声参数量级不对 | 检查datasheet或重做艾伦方差分析 |
| 优化不收敛或发散 | 运动激励不足、数据时长太短 | 增加旋转幅度和变速运动、延长采集时间 |
| 时间偏移结果异常偏大 | 时间戳同步链路有延迟、bag录制异常 | 检查传感器时间戳、启用时间偏移估计 |
| 标定结果在bin一眼不合理 | 平移方向或旋转矩阵违反安装常识 | 检查IMU和相机坐标方向定义 |
| AprilTag识别率低 | 打印模糊、纸张反光、tag_spacing填错 | 重新打印高分辨率标定板、核对YAML |
一个很容易踩的坑是:标定板YAML里,棋盘格和AprilTag的字段完全不一样。棋盘格用的是square_size和target_rows/target_cols,AprilTag用的是tag_size和tag_spacing。如果你从网上找了一段模板混着填,Kalibr可能直接报错,也可能不报错但结果全错。
另外,相机的shutter type也很关键。如果相机是滚动快门,快速运动时图像帧内会出现几何畸变,Kalibr默认没有补偿机制,标定精度会受到明显影响。我在一个项目里遇到精确对不准的情况,最后发现是相机暗光环境下自动曝光时间太长,运动模糊加滚动快门双重叠加,导致各种优化都不收敛。手动固定曝光、加光源、减慢动作之后才恢复正常。
还有个容易忽视的问题是bag数据是否完整。录制过程中如果IMU消息丢帧严重,或者相机topic有大量缺帧,最终误差会忽高忽低。建议录制后用工具检查两个topic的消息数,确认IMU频率稳定、相机帧数没有明显空洞后再开始标定。
数据采集时标定板离相机的距离也有讲究。太远,角点间距只有几个像素,定位精度先天不足;太近,标定板部分出框,有效约束减少。理想状态是标定板占画面面积的30%-50%,同时尽量保持标定板在景深范围内平整面对相机。
6. 个人经验与扩展建议
我个人的体会是:标定这个环节,数据质量大于算法细节。Kalibr的优化框架经过大量项目验证,只要输入数据靠谱、配置正确,结果基本不会有问题。相反,如果你拿一段不进光、模糊、乱晃的数据去跑,再怎么调参也救不回来。所以每次标定前,我都会先花几分钟检查数据质量,而不是直接往命令行里灌数据。
一个小技巧:标定前先静态放置传感器几秒钟,再开始运动。这段静态数据能让IMU的初始零偏估计得更准,也让B样条轨迹拟合有稳定的起点。录制结束后,再静态放置几秒,同样有助于减少末端数据对轨迹拟合的扰动。前后各留一段静置期,成本几乎为零,但对结果稳定性帮助很大。
标定结果文件的命名里往往带有连续时间戳,建议每次标定后把对应的bag、标定配置、标定报告放同一个目录管理。后面如果发现系统表现异常,可以快速回溯当时用的参数和数据,排查起来会省很多力。
另外值得一起做的扩展方向:如果你有多个相机,Kalibr也支持多相机与IMU联合标定。多相机标定可以先把相机之间外参固定,再联合优化IMU外参。相机的快门类型、IMU的温漂特性、标定板材质的选择,这些都会影响最终精度的上限,实际项目中值得逐个排查验证。
Kalibr不是唯一选择,但对摄像机和IMU联合标定这个场景来说,它依然是最透明、最通用、最值得先尝试的方案。把这个工具用熟练,不管是做机器人导航、无人机视觉定位,还是AR/VR设备姿态跟踪,都能省下大量联调时间。希望这份总结能让你少踩几个坑,一次跑出可信的标定结果。