☰
Kalibr联合标定Realsense D455相机与IMU完整实操指南
2026/10/6 20:23:58 网站建设 项目流程

最近接手了一个带视觉惯性融合的机器人项目,调试VINS-Fusion的时候被外参坑到怀疑人生——明明相机和IMU都是同一颗D455,结果系统初始化反复飘,后来才发现是标定环节偷懒了。做视觉SLAM、VIO或者多传感器融合定位的朋友应该都有体会:相机和IMU之间的外参标定这一步绕不过去,外参差一点,后面的紧耦合优化、预积分全都会连锁出错。这篇我完整梳理一遍用Kalibr对Realsense D455做相机加IMU联合标定的实操流程,包含环境配置、标定板生成、数据采集、两步标定命令解读和常见的报错解决,直接照着抄就能跑通。

在开始之前先说明一下,整套流程我是在Ubuntu 20.04加ROS Noetic环境下实测完成的,理论上18.04加Melodic也完全兼容。适合正在做VINS、ORB-SLAM3、FAST-LIVO这类视觉惯性系统的同学参考,也适合刚入手D455想验证IMU精度的朋友。文章里所有命令和配置文件我都会贴出来,并解释为什么这么写,方便你理解之后按自己项目情况改。

1. 联合标定到底在标什么,为什么绕不过去

1.1 相机内参、IMU内参和外参各自负责什么

很多刚接触这个领域的朋友会把“标定”理解成一个笼统的动作,觉得跑个工具输出个yaml文件就完事了。实际上联合标定至少包含三层含义。

第一层是相机内参,也就是针孔模型下的焦距fx、fy,主点cx、cy,以及畸变系数。D455的彩色相机视野不算特别广,默认用radtan(径向切向畸变模型)就足够了。内参决定了像素坐标和相机坐标系下光线的对应关系,如果内参不准,哪怕外参再准,整个投影残差也会很大。

第二层是IMU内参,包括加速度计和陀螺仪的尺度因子、安装误差、零偏以及噪声密度和随机游走。Kalibr的imu-cam标定流程里其实也会估计IMU的尺度误差和安装误差矩阵,但前提是你至少给一个合理的初值。D455内部IMU的理论参数在官方手册里有,但每颗芯片都有个体差异,有条件的话先用imu_utils这类工具单独标定一下更好。

第三层就是整个联合标定最核心的部分——相机和IMU之间的外参,也就是从相机坐标系到IMU坐标系的旋转矩阵和平移向量,通常写作r_ci和t_ci。外参的物理意义非常直观:IMU测到的角速度和加速度,要转换到相机坐标系下去和视觉特征形成约束,如果这个变换错了,等于一个系统里两套传感器在说两套语言,融合结果自然发散。除了外参,Kalibr还会估计一个时间偏移timeoffset,用来补偿图像和IMU消息之间的时间戳对齐误差。

1.2 为什么必须用数据驱动的方式标

理论上外参可以通过机械图纸量出来,但实际根本不可行。相机的光学中心和IMU的测量中心都不是可以直接用卡尺量到的物理点,你得靠“看”和“感受”去反推它们之间的相对位置。人工量测误差动不动就是厘米级,而视觉惯性系统的外参误差容忍度通常在毫米级和毫弧度级,稍微差一点就会导致初始化失败或轨迹漂移。更重要的是,随着设备温度变化、安装时轻微位移,外参在每次重新安装之后都可能变化,这意味着标定不是一劳永逸的,而是跟着设备状态走的。所以一套可重复执行的标定工具链就是刚需。

1.3 为什么选Kalibr这个方案

标定工具箱其实不少,OpenCalib、Aircraft、basalt这些也都能做,但我最终选择Kalibr主要看中三点。第一是生态成熟,Kalibr是苏黎世联邦理工ASL实验室开源的老牌工具,从2013年左右开始维护,积累了大量的使用案例和踩坑帖,遇到问题搜索一下基本都有答案。第二是算法鲁棒,Kalibr用Aprilgrid而不是普通棋盘格,这种标签自带编码,即使部分遮挡也能解算出ID,意味着标定板边缘被切出画面也不会导致检测崩溃。第三是一站式解决,Kalibr既能做单目、双目、多目相机内参标定,也能做相机与多个IMU的外参联合标定,虽然UI简陋、依赖老旧,但在“把事办成”这件事上非常靠得住。对于D455这种内置IMU的消费级深度相机来说,Kalibr完全够用,不需要上工业级设备。

2. 环境准备:把Kalibr和Realsense D455跑起来

2.1 系统依赖与Kalibr编译安装

Kalibr目前官方支持ROS Melodic和Noetic两个版本,我建议直接用Noetic,Python3的支持更干净。系统装好Ubuntu 20.04和ROS Noetic之后,先把依赖补齐,再编译Kalibr。

sudo apt update sudo apt install python3-catkin-tools python3-igraph python3-pip python3-scipy python3-numpy pip3 install pyyaml

然后创建工作空间并克隆Kalibr源码:

mkdir -p ~/kalibr_ws/src cd ~/kalibr_ws/src git clone https://github.com/ethz-asl/kalibr.git cd ~/kalibr_ws catkin_make

编译过程正常情况下几分钟完成,如果遇到pip安装igraph速度很慢或者失败,我踩过的一个坑是直接换用系统源指定的版本,sudo apt install python3-igraph会比pip install更稳。还有个常见问题是Kalibr源码本身对numpy版本比较敏感,Noetic环境下默认的numpy版本太高,有可能会在运行时报ValueError或者类型转换错误。我的处理办法是给Kalibr单独建一个Python2/3兼容的虚拟环境,或者在kalibr仓库里指定一个兼容版本的分支,比如noetic分支。

2.2 Realsense D455驱动安装与话题确认

D455用的是Intel RealSense SDK,也就是librealsense,我们还要安装对应的ROS功能包realsense-ros。librealsense建议直接从Intel的apt源装,这样固件升级和动态库都比较省心。

sudo apt install ros-noetic-realsense2-camera

装完驱动后,启动相机节点时要注意默认launch不一定把IMU打开。realsense2-camera默认是会发布IMU话题的,但需要确认一下。

roslaunch realsense2_camera rs_camera.launch unite_imu_method:=linear_interpolation

加入unite_imu_method参数后,驱动会额外发布一个融合后的/camera/imu话题,这个参数含义是把IMU的角速度和线加速度做线性插值,使消息时间戳更平滑。对于Kalibr来说,这个选项很有帮助,因为原始IMU话题时间戳偶尔会出现不小跳动。

启动之后,用工具确认话题状态,这一步非常关键,后面Kalibr只认bag里录制的话题名和频率:

rostopic hz /camera/color/image_raw rostopic hz /camera/imu

正常情况下彩色图30FPS,IMU 200Hz左右。如果你发现IMU频率只有100Hz,别担心,D455的IMU默认是200Hz,但某些固件版本下需要手动在launch里加imu_framer_rate:=200参数。

提示:录制bag之前,一定要先确认图像和IMU的话题时间戳使用相同的时间基准。D455驱动发布的话题时间戳默认都是采集板时钟,如果你之前在launch里加了external_cam之类的参数,可能会导致时间错乱。

2.3 常见坑:LibRealSense与ROS包版本不匹配

如果是自己编译安装的librealsense,很容易出现realsense2_camera启动后报找不到固件或者驱动版本太旧的问题。我的建议是先运行realsense-viewer打开图形界面,确认固件已经升级到最新,再启动ROS节点。realsense-viewer里能看到IMU数据是否正常滚动,这一步能过滤掉很大一部分硬件层面的问题。

3. 标定板与数据采集,决定成品的80%

3.1 生成并打印Aprilgrid标定板

Kalibr官方推荐使用Aprilgrid而不是传统棋盘格。棋盘格虽然也能检测,但存在对称性问题,会导致标定板方向判断模糊,而Aprilgrid每个tag都有独一无二的编码,甚至遮挡住一半也能恢复出完整位姿。生成标定板直接用Kalibr自带的命令:

kalibr_create_target_pdf --type apriltag --nx 6 --ny 6 --tsize 0.024 --tspace 0.3

这几个参数的含义我需要额外解释一下。nx和ny是内部角点数量,不是tag的数量,比如6x6的tag网格内部有5x5个角点,Kalibr会以tag之间的交点作为特征点。tsize是每个黑色方块的实际边长,单位米,这里0.024代表24mm。tspace是方块间距与边长的比值,0.3表示间距是边长的30%,这个值不能设太大,因为标定板需要保持紧凑,也不能太小,否则tag间的边框信息不足。

打印出来之后,一定要用尺子量实际尺寸,因为打印机的缩放比例不一定是100%。如果实测边长是23.5mm而非24mm,就把yaml文件里的tagSize改成0.0235,这个微小差异对内参影响不大,但对联合标定中的外参平移误差影响不小。

3.2 标定板target yaml文件的写法

在运行标定命令之前,需要手写一个描述标定板参数的yaml文件,内容按实际标定板来填。

target_type: 'aprilgrid' tagCols: 6 tagRows: 6 tagSize: 0.024 tagSpacing: 0.3

这和生成PDF时的参数一一对应。注意tagSize单位是米,填错了整个标定结果会等比例错掉。

3.3 数据采集动作规范与时长控制

很多人标定失败的原因不在算法,而在数据采集太随意。Kalibr对数据的运动激励有一定要求,简单说就是:让IMU充分感受到角速度和线加速度的变化,让相机充分看到不同姿态下的标定板。

录制的具体动作,我总结了一套比较高效的模式。开始时让相机正对标定板保持静止半秒,然后依次绕相机坐标系的X轴、Y轴、Z轴缓慢旋转,旋转过程中手腕不要僵硬,保持小幅度平移。每个轴旋转几轮之后,再做一些“8”字形的摆动,保证角速度和线加速度方向不断变化。整个动作持续120到180秒就足够,太长的数据反而会导致优化时间过久。

需要注意避免的几种情况:

  • 纯旋转没有平移。相机在纯旋转运动下观测到的视差变化不够,内参标定会退化,外参标定也一样。
  • 动作太剧烈。D455的运动曝光模式下图像容易模糊,Aprilgrid角点检测失败率会直线上升。宁可动作慢一点稳一点,也不要甩来甩去。
  • 标定板太小或离得太远。标定板在画面中占比太低,角点提取精度就会下降。我建议标定板宽度占到画面宽度的三分之一到二分之一。
  • 标定板超出画面。虽然有tag编码,但大量角点丢失会让优化不稳定,尽量保持标定板在画面内。

3.4 固定曝光与图像参数设置

D455的自动曝光在标定时必须关掉,不然室内走动时亮度突变会导致部分帧的Aprilgrid检测失败。可以在launch里加参数,也可以用dynamic_reconfigure在线设置:

rosrun dynamic_reconfigure dynparam set /camera/color exprosure 200 rosrun dynamic_reconfigure dynparam set /camera/color gain 100

exposure的取值根据实际环境亮度调,室内光源稳定时我一般取100到300。总之原则就是让画面亮度稳定、不过曝、不欠曝。

录制bag的命令如下:

rosbag record -O d455_calib.bag /camera/color/image_raw /camera/imu

录完以后顺手做一个检查,确保bag里有图像有IMU:

rosbag info d455_calib.bag

看到类似/camera/color/image_raw: 4000 messages和/camera/imu: 30000 messages就说明数据量是够的。

3.5 采集过程中还有一个容易忽略的要点

D455的彩色相机分辨率在标定的时候建议直接用1280x720,不要为了省计算量降到640x480。因为内参标定依赖角点提取精度,分辨率越低,像素量化误差对重投影误差的影响越大。到了联合标定阶段,如果嫌处理慢,可以先用图像抽取工具对bag做减帧,而不要降分辨率。

4. 相机内参标定:迈过第一道坎

4.1 相机模型选择与launch参数解析

在开始内参标定之前先明确一下使用的相机模型。Kalibr里最常用的两个是pinhole-radtan和pinhole-equi。radtan模型适合普通视角相机,畸变被建模为径向畸变加切向畸变,D455的彩色相机正好适用。如果用的是鱼眼镜头或者纯深度相机标定,才需要考虑equi等全景模型。

内参标定的命令格式如下:

kalibr_calibrate_cameras --target my_april.yaml \ --bag d455_calib.bag \ --topics /camera/color/image_raw \ --models pinhole-radtan \ --show-extraction

--show-extraction参数会弹出一个窗口,动态显示当前帧检测到的Aprilgrid角点,建议第一次标定的时候打开看一眼。如果画面中大量帧显示提取失败,就不要让命令继续跑下去了,直接Ctrl+C终止,改曝光或者换数据。

命令跑完后,会生成三个文件:

  • camchain-d455_calib.yaml:包含相机模型的畸变系数和焦距等内参,后续联合标定要直接用。
  • result-cam-d455_calib.txt:重投影误差统计。
  • report-cam-d455_calib.pdf:可视化报告。

4.2 内参结果判读标准

打开result-cam-d455_calib.txt,重点关注重投影误差的均值。正常情况下,所有角点的重投影误差均值应该小于0.3像素,如果超过0.5像素就要怀疑数据质量了。我的个人标准是:0.2以内优秀,0.2到0.5能用,大于0.5果断重录。

重投影误差偏大的常见原因,一是标定板不平整,纸贴在硬纸板上有褶皱;二是图像运动模糊,降低了角点检测精度;三是标定板距离相机太远,角点定位精度下降。碰到误差大的情况,优先检查这三个方向,而不是去调Kalibr参数。

4.3 内参标定的翻车点:检测不到Aprilgrid

最常见的报错是No valid tag found或者日志里出现大量Detection failed。先排查标定板参数是否正确,再检查图像是否过曝。还有一个很多人不知道的点:Aprilgrid对光照比较敏感,如果标定板表面有反光,白色区域和黑色区域对比度不足,检测解码会失败。所以打印标定板尽量用哑光纸,不要用光面铜版纸。

4.4 内参标定的一个实用技巧

kalibr_calibrate_cameras每次运行结果会有微小差异,这是正常的。如果你希望结果更稳定,可以一次性录制多条bag,分别标定几次,然后对比结果。如果几次标定出的焦距波动在0.2%以内,说明标定置信度高。如果波动超过1%,说明数据质量不行,不是算法不稳定。

5. 相机IMU联合标定核心实操

5.1 编写IMU参数yaml文件

联合标定需要两个输入文件,一个是上一步内参标定生成的camchain-d455_calib.yaml,另一个是描述IMU话题和噪声参数的yaml。下面是我在D455上使用的模板:

# d455_imu.yaml imu_topics: [/camera/imu] update_rate: 200 accelerometer_noise_density: 0.000467 accelerometer_random_walk: 0.00019 gyroscope_noise_density: 0.000237 gyroscope_random_walk: 0.000003

update_rate必须和实际IMU发布频率一致,建议用rostopic hz /camera/imu测一下再填。噪声密度和随机游走的初值可以先从D455官方手册或者imu_utils标定结果里取,Kalibr在联合优化时会把它们作为先验,如果数值差几个数量级会导致优化发散。我给的这组数值是从多块D455标定统计出来的常见范围,如果你没有imu_utils的标定结果,直接用这个作为初值问题不大。

5.2 联合标定命令和参数详解

一切准备就绪后,运行核心命令:

kalibr_calibrate_imu_camera --target my_april.yaml \ --cam camchain-d455_calib.yaml \ --imu d455_imu.yaml \ --bag d455_calib.bag \ --max-iter 300

参数含义如下:

  • --target:标定板yaml。
  • --cam:相机内参标定结果。
  • --imu:IMU参数yaml。
  • --bag:数据bag。
  • --max-iter:最大迭代次数,默认可能只有200,数据质量好的话几十步就收敛了,如果数据较差可以适当调大,我一般给500。

运行期间终端会输出每轮迭代的代价(cost)变化。正常情况cost应该单调下降并趋于平稳,这个过程通常几分钟。如果cost在几百轮后还在剧烈震荡,大概率是外参初值给得太差,或者数据里的IMU噪声模型填得太离谱。

5.3 联合标定结果文件解读

运行结束后会生成camchain-imucam-d455_calib.yaml和report-imucam-d455_calib.pdf。打开yaml文件,最核心的内容在camchain部分:

cam0: T_cam_imu: - [0.015, -0.999, -0.001, 0.014] - [0.999, 0.015, 0.001, -0.001] - [-0.001, 0.001, -0.999, -0.012] ... timeoffset: 0.0032

这个T_cam_imu就是外参矩阵的前面几列,旋转加平移,4x4齐次变换矩阵。timeoffset单位是秒,表示IMU时间戳相对于图像时间戳的偏移。D455的SDK内部已经做了同步,timeoffset通常很小,在毫秒量级,但如果这个值超过10毫秒,就要去检查ROS时间同步机制是否正常。

注意:T_cam_imu的含义是从IMU坐标系到相机坐标系的变换。用的时候要搞清楚你的VIO系统需要的是T_ic还是T_ci,VINS-Fusion里读取的外参是T_ic,也就是相机到IMU的变换,需要在Kalibr输出结果的基础上取逆,这个方向搞反了是新手最常见的错误之一。

5.4 联合标定后如何快速验证

标定结果不是拿到yaml文件就算完事,我每次都会做一个快速验证。方法很简单:把Kalibr输出的外参替换到VINS-Fusion或ORB-SLAM3的配置里,然后跑一段之前录好的测试数据,看系统能否正常初始化、位姿轨迹是否平滑。如果初始化阶段相机静止时IMU bias能快速收敛,且在快速运动时定位没有明显漂移,说明外参是可靠的。如果初始化一直失败,先把外参方向调换一下再试。

6. 高频报错与排查实录

6.1 报错速查表

下面是我和几个朋友在整套流程里遇到过的典型报错,按现象、原因、解决方式整理成一张速查表。

报错现象可能原因解决方式
No valid tag found标定板检测失败检查曝光、标定板尺寸和表面反光,降低动作速度
Initialization failed数据激励不足或IMU噪声参数错误检查采集动作是否覆盖充分旋转,检查imu.yaml噪声数量级
ImportError: No module named igraph缺少Python依赖sudo apt install python3-igraph或pip install python-igraph
The relative pose cannot be determined标定板静止了太长时间增加平移和旋转幅度,让相机持续运动
std::bad_alloc或内存溢出bag数据量过大用rosbag filter按帧抽取,减小bag体积
No imu messages in bag录制时IMU话题没发布在launch中开启IMU相关参数,确认rostopic hz有频率
Topic not found话题名写错用rosbag info查看实际话题名
cost持续震荡不收敛IMU内参初值和实际偏差太大用imu_utils先标定IMU内参,更新噪声参数
标定完成但重投影误差巨大标定板不平整或尺寸填错换硬板支撑标定板,核对tagSize实测值
内参结果每次漂移很大标定板占画面比例太小靠近标定板,增大目标占比

6.2 报错排查思路:先检查数据再检查参数

很多人一看到Kalibr报错就到处改参数,我不建议这么做。Kalibr的算法本身经过充分验证,绝大多数报错的根源在输入数据而不是参数。排查顺序我建议固定成这样:

  1. 先用rosbag info确认话题名、消息数量、时长。
  2. 用rqt_bag或者Python脚本抽取几帧图像,肉眼确认标定板清晰可见。
  3. 打开IMU话题,画出角速度和加速度曲线,确认没有长时间零值或者突跳。
  4. 再回到yaml参数,从target尺寸、噪声参数开始核对。

6.3 内存不足导致std::bad_alloc的处理

彩色720P视频录制两三分钟,bag文件可能达到几百MB甚至上GB,Kalibr读入bag后要建立视觉特征轨迹和IMU样条,内存占用会翻好几倍。8GB内存的笔记本在标定过程中容易直接崩溃。我的做法是先用rosbag filter把bag拆短,保留中间运动最充分的一段60到90秒,已经足够标定。也完全可以降低图像帧率到15Hz再标定,代价只是角点观测数量少一些。

rosbag filter d455_calib.bag d455_short.bag "t.to_sec() >= 30.0 and t.to_sec() <= 120.0"

6.4 时间戳同步和timeoffset异常

有时候联合标定跑完,其他结果都正常,唯独timeoffset估算出来是几十毫秒甚至几百毫秒,这就是在提醒你数据时间基准出问题了。D455驱动虽然自带时间同步,但如果你在launch中启用了多个realsense相机或者使用了网络时钟同步,时间戳可能会出现偏移。解决方法是检查rostopic hz输出里的时间戳稳定度,还不行就换一台没有外接PTP等时钟服务的机器重录。

卡尔曼滤波。标定本身是个一次性投入,但回报是长期的定位精度稳定性。如果你标完一次之后发现系统还是容易飘,不用急着怀疑算法,回头看看本文提到的这几个检查点,多半是数据采集环节埋了雷。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询