ARTICLE DETAIL

资讯详情

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

Kalibr相机-IMU外参标定实战:从录制到VINS接入的避坑指南

Kalibr相机-IMU外参标定实战:从录制到VINS接入的避坑指南 有阵子我把VINS-Mono跑在自组的双目IMU硬件上静态初始化一切正常稍微动起来姿态就开始漂。群里老司机只问了一句“你外参标定了吗”我瞬间有点心虚——因为我一直是用游标卡尺量的相机和IMU相对位置再手工量个角度塞进参数文件。后来老老实实用Kalibr做了一次完整的相机-IMU外参标定才发现这种“手量外参”的做法比不标还坑。这篇东西不打算写成翻译文档更像是我自己踩完坑之后的一份备忘录Kalibr到底在算什么、录制数据时哪些细节直接决定成败、命令行怎么跑、结果文件怎么读、以及把它接到VINS这类VIO系统里时还有哪些坑。适合刚接触视觉惯性SLAM、或者已经跑通Demo但标定结果总不稳定的朋友。1. 在动手之前先搞清楚Kalibr解的到底是什么1.1 相机与IMU之间的“六自由度”身份信息两个刚性连接的传感器它们之间一定存在一个固定的刚体变换一个3x3的旋转矩阵R和一个3x1的平移向量t合起来通常写成4x4齐次矩阵T。这就是外参。对相机-IMU系统来说这个T的本质含义是IMU坐标系下的一个三维点怎么投影到相机坐标系下。反过来说相机坐标下的一个量怎么换到IMU坐标下。知道这个变换之后视觉信息和惯性信息才能对齐到同一个坐标系里去做融合。六个自由度分别是绕三个轴的旋转角和沿三个轴的平移量任何一个误差最终都会体现在VIO轨迹的漂移上。这里有一个反复有人问的问题为什么不能直接量因为IMU的坐标系原点通常不在PCB板的几何中心加速度计和陀螺仪各自的原点也不一定重合芯片手册里往往给的是封装参考点不是真实的measuring frame。相机的光心更抽象它甚至不在镜头外壳的某个可标记位置而是由内参标定确定的一个虚拟点。用尺子量出来的数只要偏差几毫米融合后运动越剧烈、误差越大。1.2 它和棋盘格内参、机械臂手眼标定有什么不同很多人把相机内参标定、相机-IMU外参标定、工业机器人的手眼标定甚至九点标定混在一起其实它们解决的是不同层级的问题。张正友标定法解决的是单目相机内参问题焦距、主点、畸变系数描述的是三维世界点到二维像素点的映射。棋盘格角点的世界坐标假设在标定板平面上这个假设和IMU没有关系。九点标定是工业场景里常见的2D-2D项目把像素坐标映射到某个平面机械坐标上本质是一个平面单应性变换既没有三维旋转也没有IMU参与。手眼标定求解的是相机与机械臂末端之间的变换形式上也是AXXB和相机-IMU外参标定有一点相似但传感器约束来源完全不同手眼标定用机械臂的位姿和相机观测Kalibr则用IMU的角速度/加速度和相机的视觉重投影。Kalibr把相机内参、相机-IMU外参、甚至时间偏移放在一个大优化问题里联合求解。这一步和单独标内参有本质区别它需要IMU数据的动力学约束而不是静态位姿约束。所以拿张正友标定法用的那种静态棋盘格照片是无法完成相机-IMU外参标定的。1.3 Kalibr在整条视觉SLAM流水线中的位置在VINS-Mono、VINS-Fusion这类系统里通常假设相机与IMU的外参是已知且固定的。初始化阶段需要靠外参把IMU的角速度、加速度转换到相机坐标系或者反过来以建立视觉帧之间的相对旋转约束。外参不准初始化就废了。比较常见的工作流是先用Kalibr标定相机内参再用Kalibr标定相机-IMU外参最后把标定结果填进SLAM系统。也可以顺便用kalibr_calibrate_cameras做双目内参和双目外参标定。它输出的相机模型可以无缝对接主流VIO框架这也是Kalibr能成为事实标准的原因之一。2. 连续时间轨迹模型Kalibr的核心“巧劲”2.1 为什么不能用普通的离散时间对齐早期做视觉惯性标定一个直观思路是“同时刻对齐”把图像帧时间戳上的相机位姿和IMU积分出来的位姿对比然后解算外参。但这个问题在实际数据里有个硬伤图像和IMU设备的时间戳往往不同步再加上卷帘快门曝光时间的影响某个像素到底对应哪个IMU采样时刻本身就是一个未知量。Kalibr的聪明之处在于它不要求时间戳完全对齐而是把整段数据的相机轨迹表示成一条连续的样条曲线。图像在任何时刻曝光都可以在这条曲线上取到对应的位姿然后和IMU积分做对比。这样时间偏移也变成了待估计的参数之一而不是一个需要人工对齐的预处理步骤。2.2 B样条轨迹、IMU积分和重投影误差怎么拧在一起Kalibr的联合标定目标函数从宏观上看是两类误差的和视觉重投影误差和惯性测量误差。视觉这一路利用Aprilgrid的已知角点通过标定板角点的三维坐标和相机观测的像素坐标构造重投影残差。这个残差里同时含有相机内参、畸变系数、相机到IMU的外参、时间偏移、以及B样条轨迹控制点。惯性这一路把B样条轨迹对时间求导得到角速度和线加速度再和IMU的实际测量值做残差。样条轨迹是连续函数所以天然支持对时间的求导也能在任意时间点做插值。优化时所有控制点、外参、时间偏移、IMU零偏一起被放进一个非线性最小二乘问题里迭代求解。这里有个比较工程化的点B样条控制点的数量和稀疏程度决定了对高频运动的表达能力。如果动作太猛但控制点太稀轨迹就拟合不过去如果控制点太密又容易过拟合噪声。Kalibr通过机制选定控制点用户不需要手动调整但对数据的激励程度还是有要求。2.3 Aprilgrid棋盘格的“完全体”Kalibr默认推荐Aprilgrid而不是传统棋盘格原因在于棋盘格存在方向歧义一块对称的棋盘格旋转180度后检测出的角点顺序可能完全互换这在多帧优化里会造成外参朝错误方向收敛。Aprilgrid的每个格子内部带有类似二维码的编码算法能识别出每个格子的唯一编号即使部分遮挡也能恢复出完整的角点标识。另外一个实用优点是Aprilgrid可以在部分反光或遮挡时仍保持较好的检测率。传统棋盘格一旦某个边缘被遮挡角点顺序就乱套。Aprilgrid在数据集录制过程中容错率高很多。实际标定时如果你发现连续几帧只检测到少量角点Kalibr也会直接跳过这些帧但视觉约束总量会被削弱所以录制时要尽量保证格子始终完整、清晰。打印Aprilgrid时用kalibr_create_target_pdf这个工具生成PDF然后打印在平整、哑光、没有皱褶的硬板上。我见过有人偷懒打在A4白纸上贴纸箱结果纸面轻微褶皱标出来的内参焦距直接偏了20个像素后面联合标定怎么跑都对不上。3. 数据采集直接决定成败我的录制清单3.1 硬件检查与标定板打印先确认相机镜头是定焦或者把自动对焦、自动曝光、自动白平衡全部关掉。变焦镜头哪怕焦距变动一点点内参就变了外参标定也就没有意义。自动曝光会造成运动过程中图像亮度剧烈变化边缘检测不稳定。如果相机支持手动固定曝光时间尽量把曝光控制在一个较短的值减少运动模糊。IMU这边先确认它输出的角速度、加速度数据不是经过滤波的“平滑值”。很多飞控或手机IMU的驱动默认开了低通滤波Kalibr用这类数据拟合惯性残差时会很难看。在数据集里看一眼IMU原始数据如果加速计噪声小到0.001m/s²以下多半是滤波过了需要找驱动能否关闭滤波器。标定板打印后用尺子量一下真实打印出来的黑色tag边长而不是直接信PDF参数。尤其是去打印店出图的尺寸经常会被缩放。把这个实测值填进target yaml的tag_size字段tag_spacing填空隙宽度和tag边长的比例单位是比例不是米。这个字段坑过不少人。3.2 动作设计把六个自由度喂饱录制动作的核心是让IMU和相机经历丰富的角速度和加速度变化同时让Aprilgrid尽可能保持在图像视野内且清晰。一个规律是“慢-快-慢”先缓慢移动再突然快速旋转或平移再缓慢下来。快慢交替能激发不同频率段的运动有利于B样条轨迹估计和IMU噪声参数辨识。旋转要比平移重要尤其是绕重力方向的偏航旋转。陀螺仪的角速度测量是外参旋转估计最直接的约束来源。录制时手拿设备转八字、画圈、快速翻滚都要尝试。平移动作可以快速前后左右移动产生加速度激励。但要避免纯匀速运动因为IMU只能感知加速度匀速段没有任何惯性信息等于白录。整个数据段控制在60秒到180秒比较合适。太短运动激励往往不够太长IMU零偏随时间变化反而引入系统误差。录制时注意标定板不要长期占据视野边缘畸变区域对重投影误差非常不友好。最好让标定板在画面中占比30%到50%。3.3 rosbag录制命令与topic配置使用ROS录制时图像topic尽量使用sensor_msgs/Image的raw数据不要用CompressedImage。压缩编码会带来额外处理时间而且时间戳不一定能反映真实曝光时刻。IMU topic使用sensor_msgs/Imu。录制命令很简单rosbag record -O imu_cam_calib.bag \ /cam0/image_raw \ /imu0开始录制前先查看话题的发布频率rostopic hz /cam0/image_raw rostopic hz /imu0相机频率保证20Hz以上IMU频率保证100Hz以上最好200Hz。频率太低B样条在高动态段容易失去约束。录制过程中不要碰标定板不要转动标定板保持标定板固定只动相机。Kalibr的模型假设标定板本身是静止的世界基准如果标定板跟着动整个问题就错乱了。录制结束后第一时间回放一遍bag检查有没有长时间掉帧或时间戳跳变rostopic delay /image_raw如果发现某个时间段时间戳突然倒跳几百毫秒这一段大概率会污染整个标定结果建议重新录。3.4 录制后15分钟检查法别急着跑Kalibr先做几个快速检查逐帧查看图像确认没有大面积过曝、过暗、模糊的段落在bag里占比过高。用ROS自带的rqt_image_view逐帧拖动确认Aprilgrid角点清晰。画一下IMU角速度曲线检查有无明显饱和比如突然平顶。IMU饱和会直接毁掉旋转激励标定。检查图像时间戳和IMU时间戳是否都在单调递增时间基准最好来自同一个时钟源。我自己的经验是一个bag在录制现场花10分钟做上述检查比跑完Kalibr发现结果不对再回头排查省至少两小时。4. 命令行实操跑通完整的Kalibr标定流程4.1 准备target、camchain、imu三个yaml跑Kalibr之前需要准备三个输入文件标定板描述、相机内参描述、IMU描述。标定板描述文件april_6x6.yamltarget_type: aprilgrid tag_cols: 6 tag_rows: 6 tag_size: 0.033 tag_spacing: 0.3tag_size单位是米必须和实测标定板一致。tag_spacing是黑色tag之间的空隙宽度和tag边长的比值不带单位。相机内参描述文件camchain.yaml由内参标定生成格式大致如下cam0: camera_model: pinhole distortion_model: radtan distortion_coeffs: [0.0052, -0.0101, 0.0012, -0.0015] intrinsics: [458.12, 459.36, 382.45, 242.18] resolution: [752, 480] rostopic: /cam0/image_rawIMU描述文件imu.yamlimu_model: calibrated update_rate: 200 gyroscope_noise_density: 1.74e-04 gyroscope_random_walk: 1.20e-05 accelerometer_noise_density: 9.78e-04 accelerometer_random_walk: 1.86e-04imu_model通常写calibrated表示忽略安装误差和尺度因子。噪声密度和随机游走可以先用芯片手册的参考值或者用Allan方差结果填。实际体会是这两个参数只要量级对对外参标定结果影响不大但尽量别乱填差几个数量级否则惯性残差容易被误缩放。4.2 先标相机内参kalibr_calibrate_cameras先单独标相机内参这是联合标定前的必要步骤。即使你打算后面联合优化给一个接近真实值的内参初值能显著提高联合标定的稳定性和收敛速度。kalibr_calibrate_cameras \ --target ./april_6x6.yaml \ --bag ./imu_cam_calib.bag \ --models pinhole-radtan \ --topics /cam0/image_raw \ --show-extraction--models参数指定相机模型和畸变模型。如果你确定是鱼眼相机可以换pinhole-equi但绝大多数普通镜头用pinhole-radtan就够了。运行结束后会生成camchain-imu_cam_calib.yaml文件里面包含内参、畸变系数和重投影误差统计。这一步如果重投影误差在0.2像素以内说明相机的内参标定质量比较好。如果到了0.5像素以上先检查标定板是否平整、图像是否模糊再重新录数据。4.3 联合标定相机与IMUkalibr_calibrate_imu_camera联合标定命令kalibr_calibrate_imu_camera \ --target ./april_6x6.yaml \ --cam ./camchain-imu_cam_calib.yaml \ --imu ./imu.yaml \ --bag ./imu_cam_calib.bag \ --time-calibration这里的核心参数是--time-calibration加上它之后Kalibr会把相机与IMU的时间偏移也作为待估计参数。如果省略系统假设时间戳完全对齐。实际数据里两者几乎不可能完全对齐强烈建议保留这个参数。命令运行期间终端会输出优化的迭代过程。如果看到重投影误差持续下降但IMU误差基本不动很可能是IMU噪声参数填错或者IMU数据被滤波过。如果出现过不了初值检查多半是角点提取失败太多或IMU频率过低。运行结束后会生成多个文件results-imucam-*.txt核心结果文件。camchain-imucam-*.yaml带外参的相机描述。imu-imucam-*.yamlIMU描述。一些.pdf和.png可视化结果包含残差图和轨迹图。4.4 结果文件怎么读打开results-imucam-*.txt核心内容大致是Calibration results T_ci: [[ 0.0147, -0.9997, 0.0180], [ 0.9998, 0.0145, -0.0168], [ 0.0166, -0.0182, -0.9997]] t: [ 0.0042, 0.0171, -0.0202] t_ci: 0.012345T_ci是Kalibr给出的相机到IMU坐标系变换矩阵(t_ci)是时间偏移单位秒正值表示相机时间戳比IMU晚多少秒。重点看最后的reprojection error和gyro/accel RMSE重投影误差通常在0.2到0.8像素之间。陀螺仪和加速度计残差越小越好一般会在零点零几到零点几之间。如果重投影误差飘到2像素以上或者外参连续跑几次结果差异很大基本可以断定数据质量不行问题多半出在录制阶段而不是命令参数。5. 标定结果不好或VINS发散时按这个思路排查5.1 从重投影误差和IMU残差判断问题有一个容易被忽视的习惯标定完只看外参矩阵不看残差。其实残差才是判断这个外参是否可信的最重要指标。出现以下现象时的排查思路我整理成了表现象可能原因优先排查方向重投影误差 2像素标定板弯曲、图像模糊、内参初值太差检查打印板平整度重录清晰数据外参两次标定旋转差超过2度旋转激励不足或帧数不够增加快速旋转动作延长录制时间IMU残差特别大IMU噪声参数量级错了核对噪声密度检查是否滤波过固定状态外参正常运动后发散时间偏移没标或曝光过长加--time-calibration固定曝光偏航角外参不稳定绕重力方向旋转不够增加yaw方向大幅旋转如果基于同一个bag连跑两次外参结果差异在0.5度和1毫米以内基本可以认为数据质量能接受。如果差异明显即使残差不大也建议重新录数据而不是直接选用其中一组结果。5.2 重复标定一致性检查和数据原因定位我自己遇到过最典型的情况同一个bag跑两遍Kalibr外参旋转差了5度。重投影误差反而都小于0.5像素。后来发现是因为这个数据集里标定板一直保持在画面中心偏左视觉约束在右侧视野严重缺失加上旋转动作集中在同一个方向整体问题处于“病态”状态——残差看着不错但解不唯一。这种情况的检查办法是把bag分别截成前半段和后半段各跑一次标定对比两组外参。如果两半结果差异很大说明数据中约束不充分。另一种办法是直接把外参固定到某组结果然后在VINS里做一次短距离实跑对比。大数据量下如果轨迹明显发散别怀疑VINS回头重新标。5.3 时间戳、曝光时间和固件采集这些隐蔽因素时间戳偏移比外参旋转更容易被忽略。即使加了--time-calibration如果相机的image_raw时间戳是在驱动层才灌进去的而不是在传感器曝光中断那一刻记录的偏移本身可能都会随时间漂移。低速运动时影响很小一旦快速转动视觉和惯性数据会整体错位VINS在运动激励大的时候就会异常发散。卷帘快门相机也会带来一个特殊问题同一帧不同行曝光时刻不同Kalibr默认假设全局快门。如果用的是卷帘快门标定结果在静止时还能用但在剧烈运动时会有较大的不一致。有条件的话尽量用全局快门工业相机。没有条件就把曝光时间调短降低卷帘影响。IMU驱动层如果有备用缓冲或中断处理也要确认时间戳不是批量处理补的“均匀时间戳”这类时间戳在高频运动中会产生肉眼不可见的延迟。6. 把Kalibr结果喂给VINS坐标系换算与我的收尾经验6.1 从T_ci到VINS的body_T_camKalibr输出的T_ci表示从IMU坐标系到相机坐标系的变换即把IMU坐标系下的点变换到相机坐标系。而很多VIO框架在配置里要求的是body_T_cam即从相机到IMUbody的变换或者反过来不同框架的定义甚至同一框架不同版本都有可能相反。最稳妥的做法不是背定义而是自己在代码里做一次换算。假设Kalibr结果记为T_ci那么对应的相机到IMU变换就是它的逆矩阵import numpy as np T_ci np.array([ [0.0147, -0.9997, 0.0180, 0.0042], [0.9998, 0.0145, -0.0168, 0.0171], [0.0166, -0.0182, -0.9997, -0.0202], [0.0, 0.0, 0.0, 1.0] ]) T_ic np.linalg.inv(T_ci) print(T_ic)拿到T_ic后再看目标框架对body_T_cam的定义。如果框架里的命名很明白是“body to cam”那就直接填T_ci如果是“cam to body”就填T_ic。这一步差一个逆矩阵旋转可能差90度或者平移完全错位运行起来VINS会在几分钟内发散。6.2 落地验证让重力向量告诉你方向对不对填好参数后开机静止放置系统读取加速度计在IMU坐标系下的读数归一化后得到重力方向。把这个重力方向通过外参旋转到相机坐标系看它是否大致指向相机图像平面中的“下”方向。如果指向了左右或上方说明外参旋转方向反了或者某个坐标系定义理解错了。这个验证非常快不需要跑完整数据集几十行脚本就能完成。我在接入不同相机和IMU组合时都会做一次很多花几个小时排查的VINS发散问题最后往往就是外参方向差了一个逆矩阵或者90度旋转。脚本计算量不大但能省下的时间非常可观。6.3 关于标定这件事我最后的体会标定不是一个“跑个命令完事”的步骤而是一个系统工程。真正靠谱的做法是花时间把数据采集做扎实先单独标内参再联合标外参然后做一致性验证最后在目标SLAM系统里用实跑数据做最终确认。每一步都是为了后面少花几倍时间填坑。我现在每次碰到视觉惯性系统的问题都会先问一句“数据采集时旋转够不够、时间戳有没有校准过、外参方向验证了吗”。这三个问题排查完八成问题都能定位。希望这份从理论到实践的Kalibr标定经验能帮你少踩几个我已经踩过的坑。
返回列表