手里有一台D435i,想做VIO或者VSLAM,最烦的事就是标定。尤其是如果你已经在跑ORB-SLAM3或者VINS-Fusion,很快会发现一个事实:跑不跑得动,往往不取决于算法本身,而取决于你喂给它的相机内参、相机间外参、IMU噪声密度这些“出厂设置”到底准不准。项目标题里的“RGB+双目+IMU联合标定”正是这套流程里最实用的组合。
先说结论:Kalibr这套工具,我用了几年,经历过Ubuntu16.04、18.04到现在的20.04,整体感受是——工具本身并不复杂,真正折磨人的是环境依赖、数据质量、标定板识别和时间戳同步这四个环节。任何一个没处理好,Kalibr就会给你一堆看不懂的残差图,然后敷衍地丢给你一个“Failed”的状态。所以这篇我不打算复制官方文档,而是把我从环境搭建到联合标定出结果的完整过程,以及中间踩过的坑,一步一步铺开来讲。
D435i在标定这件事上有个天然优势:它的双目是全局快门,RGB是卷帘快门,IMU内置且已经出厂做过初步标定。但这不代表你可以直接用出厂参数。相机镜头在运输、装配、温度变化之后,内参必定有偏差;IMU与相机之间的外参更是每个设备都不一样。VR轮椅、机械臂抓取、无人机避障,任何依赖视觉和惯性融合的系统,都绕不开这一步。
为了让你更清楚整个流程在做什么,下面这张表格先给你一个整体认知,后面每一个步骤都会展开细说:
| 标定阶段 | 输入数据 | 输出结果 | 依赖工具 | 用时参考 |
|---|---|---|---|---|
| 相机内参(单目/双目) | 静态Aprilgrid图像序列 | 相机内参K、畸变系数D、相机间外参 | Kalibr | 操作约20分钟,计算约5分钟 |
| IMU噪声密度 | IMU静止数据(2小时左右) | 陀螺仪/加速度计的噪声密度与随机游走 | imu_utils或Kalibr自带工具 | 数据采集2小时,计算约10分钟 |
| 相机-IMU联合标定 | 运动激励的视觉+IMU数据 | imu-cam外参T_cam_imu、时间延时、重力方向 | Kalibr kalibr_calibrate_imu_camera | 操作约30分钟,计算约10分钟 |
在上表三个阶段中,前两个基本都是为第三个做数据准备。联合标定的核心思路,是让Kalibr把你录制的视觉运动轨迹和IMU积分出的轨迹放在同一个优化框架里对齐,从而解算出两个传感器坐标系之间的旋转和平移关系。理解了这一点,你对后面的参数设置和命令选项就不容易发怵了。
1. 为什么标定的成败往往在开始之前就决定了
很多教程会让你直接进入安装环节,但我建议你先花十分钟搞清楚“标定给谁用”这个问题。这决定了你整个过程中的分辨率选择、帧率设置和标定板尺寸。
1.1 用途决定标定策略
如果你是给VINS-Fusion做双目+IMU标定,那RGB相机内参和双目外参是必需的;如果你跑的是ORB-SLAM3的Stereo-Inertial模式,那IMU和双目之间的外参就是刚需。不同的使用场景,对精度的侧重点不同,但流程上完全一样,所以按照“RGB内参+双目外参+IMU联合外参”这一条线走下去,所有主流VIO框架都能覆盖。
从我反复测过的经验来看,有一个通用的原则:标定所用图像分辨率必须和算法运行时的分辨率一致。比如VINS-Fusion配置里设置的是640x480,那你标定时也一定用640x480去录。因为内参和畸变系数是跟随像素坐标系的,分辨率一变,内参直接失效。
1.2 D435i的传感器特性对标定的影响
D435i的RGB相机是卷帘快门(rolling shutter),左右两个红外相机是全局快门(global shutter)。这两个快门类型在标定时要用不同的相机模型。
| 相机 | 快门类型 | Kalibr相机模型建议 |
|---|---|---|
| RGB(左/右彩色) | Rolling shutter | pinhole-equi(或pinhole-radtan,视畸变情况) |
| 左红外IR | Global shutter | pinhole-radtan或pinhole-equi |
| 右红外IR | Global shutter | pinhole-radtan或pinhole-equi |
我知道很多教程包括官方Wiki都会默认用pinhole-equi,因为D435i的广角镜头在边缘畸变比较大,等距畸变模型(equidistant)比radtan更好地描述这种大畸变。我实测下来的经验是:像D435i这种FOV比较大的镜头,pinhole-equi确实更稳定,Kalibr在优化时不容易发散;如果强行用pinhole-radtan,在图像边缘的投影误差会明显偏大。但pinhole-radtan在中心区域表达更简洁,所以也有人在中心裁剪后使用。这里我不做绝对结论,但默认pinhole-equi是D435i最稳妥的选择。
1.3 联合标定的物理前提:刚体连接要可靠
在做IMU-相机联合标定之前,你需要确保IMU和相机之间是刚性连接的。D435i出厂时已经把IMU固定在相机内部,这个刚性没问题。如果你用的是外置IMU(比如手持一个IMU模块贴在相机外壳上),一定要用胶水或机械结构固定牢靠,任何微小晃动都会直接变成联合标定里的残差。
另一个常被忽略的点是传感器之间的时间同步。D435i的ROS驱动会自动给图像和IMU数据打时间戳,但这只保证它们是同一个时钟域。Kalibr联合标定后还会优化出一个时间延时td,这个值通常很小(毫秒级),但如果你的驱动配置不当,延时可能达到几十毫秒,导致标定误差巨大。
2. Ubuntu20.04 + Noetic环境准备:依赖安装与Kalibr源码编译
Kalibr目前没有提供编译好的二进制包,必须从源码编译。在Ubuntu20.04上对应的ROS版本是Noetic,这里我直接给出整套可复现的步骤。
2.1 安装基础依赖
我假设你已经装好了Ubuntu20.04和ROS Noetic,如果还没有装ROS,建议先安装ros-noetic-desktop-full,然后按下面操作:
sudo apt update sudo apt install python3-rosinstall python3-catkin-tools python3-vcstool sudo apt install libeigen3-dev libboost-all-dev libopencv-dev sudo apt install libgoogle-glog-dev libsuitesparse-dev sudo apt install python3-pip pip3 install numpy scipy matplotlib这里有个经验细节:Kalibr依赖的OpenCV版本必须与ROS Noetic自带的版本兼容。Noetic自带OpenCV 4.2,如果你系统里还有Anaconda环境,务必小心,编译时会因为Python路径不同导致链接错误。我的做法是:只在系统Python3环境下操作,conda全部绕过,否则sip、numpy这些模块很容易莫名其妙地报错。
2.2 下载并编译Kalibr
cd ~ mkdir -p kalibr_ws/src cd ~/kalibr_ws/src git clone https://github.com/ethz-asl/kalibr.git cd ~/kalibr_ws catkin build -DCMAKE_BUILD_TYPE=Release source ~/kalibr_ws/devel/setup.bash编译时间取决于机器性能,大概5到15分钟。如果编译过程中出现fatal error: ...类的报错,绝大部分是依赖缺失,逐个apt install补上就行。
我遇到过比较典型的编译坑有两个:
- Boost版本冲突:
boost::format相关的编译错误,通常是系统存在多个Boost版本。这种情况建议把环境变量LD_LIBRARY_PATH里非系统的路径清干净再编译。 - Python模块找不到:Kalibr的脚本有一部分是Python写的,如果编译时报
No module named 'scipy',说明Python环境路径不对。执行which python3,确认是/usr/bin/python3,而不是conda路径。
2.3 安装Intel RealSense ROS驱动
这一部分D435i需要专门的驱动,不能只用普通的UVC协议。我用的是realsense-ros,它会发布相机内参、图像话题和IMU话题。
sudo apt install ros-noetic-realsense2-camera注意安装完毕后插上相机,运行:
roslaunch realsense2_camera rs_camera.launch然后用rostopic list确认以下话题存在:
| 话题名称 | 说明 |
|---|---|
/camera/color/image_raw | RGB图像 |
/camera/infra1/image_rect_raw | 左红外原始图像 |
/camera/infra2/image_rect_raw | 右红外原始图像 |
/camera/imu | IMU数据(陀螺仪+加速度计) |
在录制标定数据之前,必须确保这几个话题都正常发布。如果/camera/imu没有输出,多半是驱动配置问题,需要在launch文件里加上enable_imu:=true。
roslaunch realsense2_camera rs_camera.launch enable_imu:=true注意:D435i的IR图像虽然叫
infra,但它输出的是灰度图,Kalibr完全可以直接处理。RGB图像分辨率默认是1280x720,IR分辨率是1280x720。在标定前,最好把RGB和IR分辨率统一设置为640x480,这样计算更快,对结果也没有实质影响。
3. 标定板准备与数据采集:最容易翻车的环节
数据采集质量决定标定精度,这不是一句空话。我见过太多人辛辛苦苦编译完Kalibr,结果因为Aprilgrid图像不清晰或者运动方式不对,标定出来全是垃圾数据。这一节我把数据采集的每个细节讲透。
3.1 Aprilgrid标定板:尺寸与打印方式
Kalibr官方支持三种标定板:Aprilgrid、Checkerboard、Circlegrid。我强烈推荐Aprilgrid,原因有三:
- Aprilgrid每个角点带有独立编码,即使部分遮挡也能识别;
- 角点检测精度高,亚像素级别;
- Kalibr对Aprilgrid的鲁棒性最好,不容易出现“误匹配跳变”。
Aprilgrid的制作可以自己写Python脚本生成,也可以直接用Kalibr仓库里的PDF模板。网上有很多现成的在线生成器,但我更建议用官方生成的aprilgrid_6x6.yaml,保证尺寸准确。
这里有一个非常关键的参数:正方形边长和间距比。官方默认的Aprilgrid是每个Black square边长0.024m(即2.4cm),间距为边长的0.5倍,即1.2cm。在生成yaml文件时,你只需要改tagSize和tagSpacing两个参数,然后打印时量一下边长,确保打印出来后与标称一致(至少精确到毫米级)。如果打印机缩放导致实物尺寸和配置不一致,标定出来的焦距尺度会整体偏移,这个误差很难通过后续流程发现。
推荐用激光打印、硬纸板或泡沫板裱起来,避免表面反光和折痕。标定板表面一定要平整,稍微有一点弯曲,在图像边缘就会产生很大的投影误差。
3.2 数据录制:分辨率、帧率与时长
接下来是录制环节。你需要准备两个录包:一个给内参标定,一个给联合标定。这两个录包的要求不同,我先用表格给你梳理清楚:
| 配置项 | 内参标定(RGB/双目) | IMU-相机联合标定 |
|---|---|---|
| 分辨率 | 640x480(RGB和IR一致) | 640x480 |
| RGB帧率 | 15fps或30fps | 15fps |
| IR帧率 | 30fps | 30fps |
| IMU频率 | 不需要(或可忽略) | 200Hz左右,由驱动决定 |
| 录制时长 | 1-2分钟,包含20-30个有效位姿 | 1-2分钟,包含充分激励 |
| 标定板状态 | 静止或缓慢移动 | 相机运动,标定板静止或缓慢移动 |
这里的核心区别我再强调一下:内参标定时,相机尽量保持静止,转动标定板或者缓慢移动标定板,确保每一帧画面里的标定板出现在不同位置、不同角度,尤其是图像边缘要覆盖到;联合标定时,标定板尽量固定不动,相机要多种姿态运动。
3.3 运动激励设计:为什么“匀速转圈”是错的
这么多教程里反复强调“充分激励IMU”,但很少有人具体告诉你什么是“充分”。我用自己的踩坑经历来说。
第一次做联合标定时,我拿相机在桌子上匀速画圈,录了两分钟,数据看起来很多,但Kalibr优化出来td值很大,imu-cam外参的方差也很大,怎么看都不对劲。后来仔细分析IMU激励质量才发现问题——IMU标定需要的不是位移,而是角速度和线加速度的持续变化。匀速运动时加速度计输出基本恒定,陀螺仪也几乎没输出,高频信息极缺。
一个有效的激励序列可以这样设计,每个动作持续1-2秒,循环重复:
- 慢速旋转:分别绕X、Y、Z轴旋转,每次旋转至少90度;
- 快速翻转:加速翻转90度后减速停住,制造大幅度角加速度;
- 平移抖动:在X、Y、Z方向上快速平移并突然停止,制造线加速度冲击;
- 自由旋转:做“8”字绕动,让多个轴同时变化。
录制全程保持标定板在视野内,避免突然出画导致视觉跟踪断裂。Kalibr官方推荐的激励风格也是“慢动作+急停+大角度旋转”,而不是持续匀速转圈。
录制命令用:
rosbag record /camera/color/image_raw /camera/infra1/image_rect_raw /camera/infra2/image_rect_raw /camera/imu -O d435i_calib.bag录制完成后,立刻用下面命令检查bag里的话题频率和帧数,确保没有丢帧:
rosbag info d435i_calib.bag我常用的检查标准:1分钟bag里,color图像至少800帧(15fps),infra图像至少1600帧(30fps),IMU至少12000条(200Hz)。如果差太多,建议重新录制,不要抱着“可能也能用”的心态继续。
4. RGB与双目内参标定:第一次跑通Kalibr
Kalibr内参标定的本质是:从多个视角的Aprilgrid图像中提取角点,通过重投影误差最小化来估计相机内参和畸变系数。如果只有单目,则只估计一个相机的内参;如果是双目,还会联合优化左右相机之间的外参。
4.1 准备标定板描述文件
在~/kalibr_ws/src/kalibr下新建一个aprilgrid.yaml,内容类似:
target_type: 'aprilgrid' tag_cols: 6 tag_rows: 6 tag_size: 0.024 tag_spacing: 0.3注意这里tag_spacing是ratio(相对边长的比例),不同的Aprilgrid版本可能略有差异。如果你用的是官方6x6棋盘,tag_spacing=0.3是正确的;如果你自定义了间距,需要同步修改这里。一个良好的检查方法是:打印出来用尺子量10个格子的总宽,除以格子数得到的tagSize+spacing是否与配置文件一致。
4.2 运行RGB单目内参标定
先只标定RGB相机,命令如下:
source ~/kalibr_ws/devel/setup.bash rosrun kalibr kalibr_calibrate_cameras \ --bag ~/bagfiles/d435i_calib.bag \ --topics /camera/color/image_raw \ --models pinhole-equi \ --target ~/kalibr_ws/src/kalibr/aprilgrid.yaml \ --bag-from-to 5 80 \ --show-extraction几个重要的参数解释:
--models:相机模型,pinhole-equi表示针孔模型+等距畸变。如果畸变模型选择不当,解算出的内参会发散。D435i广角镜头用这个最稳。--bag-from-to:截取bag的时间段,单位秒。可以避开刚开始录制时相机参数未稳定的时段,也避开结束时手忙脚乱的一帧。--show-extraction:可视化角点提取结果,强烈建议加上,它可以让你看到每一帧里有没有误检。
运行后会在当前目录生成camchain-...-color.yaml、result-...-color.txt以及一张残差图。看到残差图里的reprojection error最好小于0.5像素。如果超过1像素,说明标定板角点提取质量不好或模型选择有误,需要重新检查。
4.3 双目内参+外参联合标定
现在把左右IR图像加进去,一次跑完双目标定:
rosrun kalibr kalibr_calibrate_cameras \ --bag ~/bagfiles/d435i_calib.bag \ --topics /camera/infra1/image_rect_raw /camera/infra2/image_rect_raw \ --models pinhole-equi pinhole-equi \ --target ~/kalibr_ws/src/kalibr/aprilgrid.yaml \ --bag-from-to 5 80 \ --show-extractionKalibr会同时估计两个相机各自的内参,以及两个相机坐标系之间的相对变换T_C1_C2。如果你后续还要做RGB+双目联合,也可以直接把三个话题一起传进去:
rosrun kalibr kalibr_calibrate_cameras \ --bag ~/bagfiles/d435i_calib.bag \ --topics /camera/color/image_raw /camera/infra1/image_rect_raw /camera/infra2/image_rect_raw \ --models pinhole-equi pinhole-equi pinhole-equi \ --target ~/kalibr_ws/src/kalibr/aprilgrid.yaml \ --bag-from-to 5 80 \ --show-extraction这里有一个D435i特有的细节:RGB相机和IR相机物理位置不同,RGB相机和IR相机的内参必须分别标定,不能互相替代。原因在于它们的镜头畸变差异很大,出厂标定文件里也分别是不同的参数。
4.4 内参结果质量判断
标定完成后,打开生成的result文本,重点看两个指标:
| 指标 | 合格标准 | 常见问题 |
|---|---|---|
| 重投影误差RMS | < 0.5像素 | 标定板不平整、模糊、识别错误 |
| 相机间外参的方差 | 旋转方差<0.01 rad²,平移方差<0.001 m² | 数据中两个相机共同视野不足 |
如果RMS偏高,我建议优先检查图像是否运动模糊。Kalibr对图像清晰度的敏感程度远超你想象,尤其是rolling shutter的RGB图像,快速移动时帧与帧之间会出现明显的“果冻效应”。所以内参标定时相机保持静止、标定板保持静止或极慢移动,是关键中的关键。
5. IMU误差模型的离线标定:imu_utils与Allan方差
很多人以为Kalibr只做视觉和IMU联合标定就够了,但Kalibr联合标定需要输入IMU的噪声密度和随机游走。这两个参数如果不准,联合标定得到的IMU-相机外参会“自我欺骗”——优化过程会通过外参吸收IMU误差,导致最终结果与实际物理变换明显不符。
5.1 为什么要先标定IMU噪声参数
IMU的噪声模型通常用Allan方差来提取:
- 噪声密度(noise density):代表白噪声水平,单位是
°/h或°/sqrt(h); - 随机游走(random walk):代表积分之后漂移的增加速率。
Kalibr在联合标定中需要这两个值作为优化先验,同时也作为对外参标定时IMU轨迹可信度的权重。如果你随便填一个经验值,不是不能用,但结果的可靠性会打折扣。既然这是“全流程标定”,就不能省这一步。
5.2 使用imu_utils采集静止数据
先从源码编译imu_utils:
cd ~/kalibr_ws/src git clone https://github.com/gaowenliang/imu_utils.git git clone https://github.com/gaowenliang/code_utils.git cd ~/kalibr_ws catkin build code_utils imu_utils source ~/kalibr_ws/devel/setup.bash编译过程中常见的坑是sensor_msgs/Imu头文件找不到,需要确认roscpp、sensor_msgs都已安装。如果code_utils编译报错,试着先单独编译code_utils,再把imu_utils加进去。
静止数据采集很简单,把相机放在稳定平台上(桌面即可),不要触碰,记录2小时IMU数据:
rosbag record /camera/imu -O imu_static.bag2小时听起来很久,但它影响的是Allan方差在低频段的置信度。如果你时间紧张,至少录1小时,但结果方差会稍大。我没开玩笑,很多实验室机器人标定间里就是这么扔着一台相机录一上午的。
5.3 运行imu_utils分析
在imu_utils的launch目录下创建一个imu_utils.launch:
<launch> <node pkg="imu_utils" type="imu_an" name="imu_an" output="screen"> <param name="imu_topic" type="string" value="/camera/imu"/> <param name="imu_name" type="string" value="d435i"/> <param name="data_save_path" type="string" value="$(find imu_utils)/data/"/> <param name="max_time_min" type="int" value="120"/> <param name="min_time_min" type="int" value="90"/> </node> </launch>然后用rosbag play回放采集的静止数据:
roslaunch imu_utils imu_utils.launch rosbag play imu_static.bag当bag回放结束后,imu_utils会生成一组YAML文件,里面包含了IMU的噪声密度和随机游走。打开d435i_imu.yaml,你会看到类似下面的内容:
gyr: noise_density: 1.5e-03 random_walk: 2.1e-05 acc: noise_density: 2.4e-03 random_walk: 1.2e-04这两个数值就是后续联合标定中imu.yaml里需要的参数。
注意:不同D435i个体、不同固件版本,IMU参数会有差异。不要直接抄别人博客里的数值,宁可多花2小时自己采,也不要省这一步。
6. IMU-相机联合标定:从外参到时间延时的完整输出
到了这一步,你已经有了双目/RGB内参、IMU噪声参数、一个包含丰富运动的录包。接下来就是Kalibr整套工具里最贵的一步——联合标定。
6.1 准备IMU描述文件
创建一个d435i_imu.yaml:
# 注意:这里的数值来自你imu_utils的结果,不要照抄 imu_model: calibrated gyroscope_noise_density: 1.5e-03 gyroscope_random_walk: 2.1e-05 accelerometer_noise_density: 2.4e-03 accelerometer_random_walk: 1.2e-04imu_model: calibrated表示我们假设IMU的尺度因子和轴间误差已经由出厂标定处理过,只需要估计噪声参数。如果你的IMU没有出厂校准数据,可以选择imu_model: scale-misalignment,让Kalibr额外估计尺度与轴间误差,但这样需要的激励更强、参数更多、优化更脆,所以我建议对D435i使用默认的calibrated。
6.2 联合标定命令与参数
rosrun kalibr kalibr_calibrate_imu_camera \ --bag ~/bagfiles/d435i_calib.bag \ --cam ~/kalibr_ws/src/kalibr/camchain-...yaml \ --imu ~/kalibr_ws/src/kalibr/d435i_imu.yaml \ --target ~/kalibr_ws/src/kalibr/aprilgrid.yaml \ --time-calibration \ --do-show-extraction其中:
--cam:指向第4步双目标定时生成的camchain文件。这个文件里包含左右相机内参、畸变系数、以及相机间外参。--imu:指向IMU描述文件。--time-calibration:让Kalibr同时估计视觉与IMU之间的时间延时td。如果你的驱动时间戳已经严格对齐,也可以不加;但加了通常无害,反而能在结果里给出一个参考值。--do-show-extraction:可视化角点提取效果,强烈推荐加。
运行过程一般需要几分钟到十几分钟。Kalibr会显示一个“Optimization problem”的输出,里面包含相机外参、IMU外参、重投影误差、IMU误差等多项指标。
6.3 解读标定结果
运行结束后,会生成camchain-imucam-...yaml和result-imucam-...txt。打开结果文件,关注几个关键信息:
| 输出项 | 含义 | 合理范围 |
|---|---|---|
T_ci | 相机到IMU的变换矩阵 | 平移最大几厘米,旋转基本为90度的组合 |
td | 相机与IMU时间差 | 绝对值小于0.02s |
Gravity | 重力向量 | 模长约9.8左右 |
Reprojection error | 角点重投影误差 | < 1像素 |
实际操作中,我遇到过td输出为0.08秒甚至更大的情况。这种结果通常说明时间戳同步有问题或者bag录制过程中有话题延迟,建议先检查驱动参数,而不是强行信任这个结果。D435i的驱动在正常配置下td应该在毫秒级。
D435i的IMU坐标系朝向和相机坐标系有明确的物理关系:IMU的X轴一般指向相机前方,Y轴指向下方,Z轴指向右方。标定结果里的旋转矩阵如果和你预期差异很大,先怀疑数据问题,再去检查机械安装。
6.4 外参转换:从Kalibr到VINS-Fusion
联合标定完的T_ci是IMU到相机的变换,但很多框架(比如VINS-Fusion)内部使用的是相机到IMU的变换,也就是T_ic。两者是逆矩阵关系:
python3 -c " import numpy as np # 从yaml里读到的T_ci(4x4矩阵) T_ci = np.array([...]) T_ic = np.linalg.inv(T_ci) print(T_ic) "VINS-Fusion的配置文件中body_T_cam填的就是T_ic,即从相机坐标系到IMU(body)坐标系的变换。这个方向搞反是新手最容易犯的错误,也是标定结果“看起来对但实际跑起来完全乱飘”的终极原因之一。
7. 排错实录:我见过的Kalibr各种翻车现场
这一节不按套路总结,我直接把真实踩过的坑和排查链路写出来,每个问题都配了排查思路,希望对你有直接借鉴。
7.1 角点检测数量为零
现象:--show-extraction窗口里,图像上没有任何Aprilgrid角点被画出来,终端提示检测到0个角点。
排查链路:
- 先看图像是否过暗或过曝。Aprilgrid是黑底白块,如果标定板区域反光严重,检测器很容易失败。解决方法是调整相机曝光,或者换个角度打光;
- 检查yaml文件里的
tagCols和tagRows是否和真实打印板一致。如果你打印的是6x6,配置里写成了6x7,那任何一帧都检测不出来; - 检查bag里的图像话题是否真的是原始图像,而不是已经做过rectify的图像。D435i的
image_rect_raw理论上是未矫正的原始图像,如果误用了image_color_rect,图像已经被厂商畸变矫正过,Kalibr再做一次标定会得到错误内参。
7.2 标定过程中突然发散,残差图出现“飞点”
现象:优化过程中loss下降了一段后突然增大,残差图出现大量离群点。
排查链路:
最直接的原因是bag中包含了几帧运动模糊严重的图像。内参标定时相机应该尽量静止,联合标定时相机虽然运动但画面中Aprilgrid不能大面积模糊。我习惯在用--show-extraction时通过键盘暂停回放,逐帧查看哪些帧的角点质量差,然后在--bag-from-to或额外的帧过滤选项中剔除它们。
其次要检查标定板是不是距离相机太远。Aprilgrid的每个黑色方块在图像中至少要占10个像素以上,否则角点提取的亚像素精度急剧下降。D435i的640x480分辨率下,标定板边长2.4cm,距离相机0.5到1.5米范围内通常表现最好。
7.3 联合标定后外参平移量为零
现象:优化后的T_ci平移部分几乎为0,明显不符合物理实际(D435i的RGB和IMU物理距离至少有几厘米)。
排查链路:
出现这种情况,十有八九是传入的camchain文件里只有单目信息,Kalibr找不到双目约束,导致平移自由度没有被充分约束。解决方法是确认--cam指向的文件确实包含双目外参,而不是单目标定结果。还有就是联合标定录包中,相机必须做充分的旋转运动,如果全程几乎只有平移没有旋转,平移量也会退化到不可观测。
7.4 与D435i出厂内参对不上
现象:标定出来的焦距、主点和官方rs-enumerate-devices输出的内参有明显差异,怀疑自己标错了。
排查链路:
先确认你标定时用的分辨率和官方内参的分辨率一致。D435i出厂内参通常是1280x720的,而你标定时用640x480,换算关系是官方焦距/2。如果按比例换算后误差仍在5%以上,再检查是否在rs_camera.launch里误开了enable_sync或align_depth,这会影响图像时间戳和尺寸。
另一个容易被忽视的点:D435i的RGB相机视场角和左右IR相机不同。RGB镜头在1280x720下水平视场约69度,IR镜头约87度。如果你在标定IR相机时用RGB的内参来近似,误差会很大。所以一定要分开标定。
7.5 时间戳精度问题
现象:联合标定结果的td很大,或者优化结果里IMU误差项残差一直下不来。
排查链路:
检查realsense2_camera驱动的参数,确保没有开启enable_sync之外的其他时间戳处理选项。同时查看bag里/camera/imu的topic频率是否稳定。如果IMU发布频率在150Hz到250Hz之间大幅波动,说明驱动或者USB带宽出了问题。D435i是USB3.0设备,建议插在USB3.0接口上,不要用USB2.0转接或HUB,否则带宽不够会导致图像和IMU时间戳全部混乱。
有条件的可以用rostopic hz /camera/imu实时观察频率,正常情况应稳定在200Hz左右。
7.6 Kalibr结果应该怎么验证
标定完成不代表万事大吉。我每标完一次,都会做下面三个验证:
- 残差验证:直接看
result-imucam-...txt里的重投影误差和IMU误差,如果比官方示例大一个数量级,基本可以判断标定失败,重新录数据再试; - VIO实跑验证:把标定结果写入VINS-Fusion配置,在纹理丰富、光线良好的环境跑10分钟,观察相机位姿和IMU位姿是否一致。如果轨迹起飞、漂移巨大,问题大概率在外参或者时间延时;
- 静止验证:把相机放在桌面静止不动,观察VIO输出的速度和位姿是否接近零。如果静止时速度漂移严重,说明IMU噪声参数或外参有一项不准。
这三个验证都不复杂,但能帮你拦住90%以上的标定质量问题。
8. 回调一个小细节:把标定结果归档好
标定完成后,建议把camchain、imu.yaml、以及最终联合标定的result-imucam.yaml放到一个固定目录,并记录相机的SN号、固件版本、分辨率、日期。不要小看这个习惯,因为D435i的固件升级后,内参和IMU噪声特性可能发生轻微变化,一个良好的归档能让你快速识别“是不是这个相机标定没跟上,导致VIO表现变差的”。对于长期维护机器人系统的人来说,这比多跑一次标定重要得多。
就我个人经验来说,标定这事看起来繁琐,但一次完整流程跑下来之后,你对Kalibr的底层逻辑和相机传感器的理解会深很多。后面再遇到双目、多目相机或者其他IMU传感器,其实都是同一套思路:数据质量是上限,工具是实现手段,理解才是长期收益。希望这篇能帮你少走几趟弯路,把时间花在真正有意思的算法和工程问题上。