
Kalibr 标定 D435i这套流程我前后折腾了快两周才彻底跑通。网上教程不少但大多零散要么环境配置一笔带过要么关键步骤含糊其辞照着做很容易卡在某个莫名其妙的报错上。这篇东西我把整个链路重新捋了一遍从 Ubuntu 20.04 环境搭建、ROS Noetic 安装、D435i 驱动部署到 Aprilgrid 标定板准备、数据采集、单目/双目/IMU 联合标定每一步都有完整的命令和参数说明还包括我实际踩过的坑。先说清楚这套流程能干什么。D435i 自带一个 RGB 相机、一对双目红外相机和一个六轴 IMU。Kalibr 可以把这三者的内参、外参和时延一次性标定出来输出相机到 IMU 的变换矩阵、双目相对位姿、各相机畸变系数以及 IMU 的噪声密度和随机游走。这些参数是 VIO、SLAM、机械臂手眼标定、三维重建的前置条件。你要做视觉惯性里程计或者把 D435i 装到机器人上做感知没有一套准确的标定参数后面算法跑起来全是飘的。整个过程大概需要几个小时其中一半时间花在环境编译和依赖处理上真正采集数据加跑标定熟练之后半小时内能搞定。建议有 Linux 基础和 ROS 基础的人上手纯新手也能跟着做遇到问题多查报错信息就好。1. 环境搭建与工程依赖1.1 Ubuntu 20.04 与 ROS Noetic 安装系统我用的 Ubuntu 20.04.6 LTS 桌面版ROS 对应的是 Noetic。如果你之前装过 ROS Melodic 或者其他版本最好先彻底卸载干净再装 Noetic混装容易把 Python 依赖搞乱后面 Kalibr 跑起来各种诡异报错。ROS Noetic 安装直接用官方源。先设置 sources.listsudo sh -c echo deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main /etc/apt/sources.list.d/ros-latest.list然后添加密钥sudo apt install curl curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -更新源并安装。我建议装 ros-noetic-desktop-full自带 rviz、gazebo、tf 等常用工具标定过程中会用到 rviz 查看图像和轨迹。sudo apt update sudo apt install ros-noetic-desktop-full环境变量配置记得写入 ~/.bashrcecho source /opt/ros/noetic/setup.bash ~/.bashrc source ~/.bashrc装一下 rosdep 和构建工具sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential sudo rosdep init rosdep updaterosdep init 可能会因为网络问题失败多试几次或者检查 DNS。还有一个注意点Noetic 默认 Python 3Kalibr 里有些老旧依赖包对 Python 3 的支持不完美后面编译时我会说怎么处理。1.2 Librealsense 与 Realsense-ROS 编译安装D435i 的驱动部分分两层底层 SDKlibrealsense和 ROS 封装realsense-ros。Kalibr 采集数据时通过 ROS topic 读取图像和 IMU 数据所以两层都需要装。先装 librealsense。官方仓库有编译好的 deb 包但我建议源码编译能拿到最新版本且后续可以调整 CMake 选项。sudo apt install git cmake build-essential pkg-config libgtk-3-dev libusb-1.0-0-dev libgl1-mesa-dev libglu1-mesa-dev libssl-dev libudev-dev git clone https://github.com/IntelRealSense/librealsense.git cd librealsense mkdir build cd build cmake ../ -DBUILD_EXAMPLEStrue -DFORCE_RSUSB_BACKENDtrue make -j$(nproc) sudo make install这里有个点要说明一下。FORCE_RSUSB_BACKENDtrue 是让 SDK 走用户态 USB 后端不加载内核模块这样能避免内核补丁导致的编译问题。代价是高负载下会有略微增加的 CPU 占用但对标定来说完全够用。装完之后把 udev 规则拷过去不然插上相机没有权限cd .. sudo cp config/99-realsense-libuvc.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger插上相机后运行 rs-enumerate-devices 看一下能否识别 D435i。然后编译 realsense-ros。需要先创建 ROS 工作空间mkdir -p ~/kalibr_ws/src cd ~/kalibr_ws catkin_init_workspace src拉取 realsense-ros 源码并编译cd src git clone https://github.com/IntelRealSense/realsense-ros.git cd realsense-ros git checkout 2.3.2 cd ~/kalibr_ws catkin_make source devel/setup.bash编译完之后启动相机试试roslaunch realsense2_camera rs_camera.launch如果一切正常rostopic list 里能看到 /camera/color/image_raw、/camera/infra1/image_rect_raw、/camera/infra2/image_rect_raw、/camera/imu 等 topic。1.3 Kalibr 源码编译Kalibr 在 Ubuntu 20.04 / Noetic 下的编译是整个过程最痛苦的部分。它官方并不太活跃很多依赖是为 Kinetic/Melodic 时代写的。好在社区有维护的分支我用的方法是 clone 官方最新源码然后手动处理依赖。cd ~/kalibr_ws/src git clone https://github.com/ethz-asl/kalibr.git回到工作空间编译前先装几个关键依赖sudo apt install python3-pip python3-catkin-tools python3-osrf-pycommon pip3 install numpy1.19.5 pip3 install scipy1.5.4 pip3 install matplotlib3.3.4 pip3 install wxPython为什么要锁 numpy 和 scipy 版本Kalibr 里有个叫 imaging 的库是基于 Python 2 时代写的对 numpy 1.20 的 API 变动没有完全兼容编译时可能不报错但跑起来一旦涉及矩阵操作就会崩溃。wxPython 是 Kalibr 的 GUI 可视化工具 kalibr_visualize 依赖的不装后面没法可视化验证结果。编译我已经踩了太多坑这里给一个能完全过的方法cd ~/kalibr_ws catkin_make -j4 -DLIBGLM_INCLUDE_DIR/usr/include/glm显卡驱动如果比较新编译老代码容易出现 GL 相关的头文件报错加上 -DLIBGLM_INCLUDE_DIR 能绕开一部分问题。还有 -j4 而不是 -j$(nproc)是因为 Kalibr 里有几个模块对内存消耗很大并行编译太多容易 OOM。编译时长大概 15-25 分钟看机器性能。编译完别忘了 sourceecho source ~/kalibr_ws/devel/setup.bash ~/.bashrc source ~/.bashrc验证 Kalibr 是否装好kalibr_calibrate_cameras --help能正常打印帮助信息就是编译成功了。2. 标定板准备与参数设计2.1 Aprilgrid 标定板制作Kalibr 官方推荐的标定板是 Aprilgrid不是棋盘格。原因很简单Aprilgrid 每个格子都有自己的编码 ID即使部分遮挡也能检测出角点而且对光照变化的鲁棒性远远强于棋盘格。棋盘格在手持移动时很容易出现误检测Aprilgrid 基本不会。官方提供了一个 Python 脚本用来生成标定板 PDF在 kalibr 仓库的 pdf 目录下cd ~/kalibr_ws/src/kalibr/pdf python3 generate_target.py aprilgrid生成的 aprilgrid.pdf 里面有不同尺寸的板子打印在 A4 纸上即可。但要注意这个 PDF 默认的靶标间距和尺寸需要你精确测量而不是按默认参数直接引用。打印质量很重要一定要用激光打印机打印在哑光纸上不要在表面覆膜覆膜会在相机视角下产生高光反射导致角点检测失败。我实际做的标定板是 6x6 的 Aprilgrid每个格子的边长是 33mm打印后我用游标卡尺反复量了十几次量出来实际边长是 33.15mm。你可能觉得这 0.15mm 的误差无所谓但 Kalibr 对标定板的尺寸精度极其敏感尤其是 IMU 标定阶段尺寸误差会直接转化为旋转角度的误差进而影响外参精度。所以精确测量这一步省不了。2.2 标定板 YAML 文件编写标准 Kalibr 靶标描述文件在安装目录里也有但不能直接用需要改成你实际打印出来的板子的尺寸和分辨率。我是在 ~/kalibr_ws/src/kalibr/config 下新建了一个叫 april_6x6_33mm.yaml 的文件内容如下target_type: aprilgrid tag_cols: 6 # 列数 tag_rows: 6 # 行数 tag_size: 0.03315 # 每个标签的边长米 tag_spacing: 0.3 # 标签间距 / 标签边长这里 tag_spacing 的含义是标签之间的白色间隔宽度与标签边长的比值PDF 模板会根据它自动调整整体布局。打印出来后如果你直接量整个标定板的外围尺寸会发现和理论值差很多因为白色间隔占了不少面积。但 Kalibr 只关心 tag_size 和 tag_spacing 这两个参数把它们设对就行。还有一个小细节。如果你用 A4 纸打印标签数量和尺寸是固定的但桌面空间够大的话我建议把 PDF 放大两倍打印在 A1 或 A2 纸张上。太大的板子在手持时边缘容易弯曲太小的板子在距离稍远的地方就检测不到角点。D435i 的 RGB 分辨率为 1280x720 时板子边长在 25-35 厘米左右是最佳操作区间。3. 数据采集决定成败的关键环节3.1 采集前的相机参数设置数据采集的质量直接决定标定结果的好坏这一步值得多花点时间。很多人在这一步偷懒拿着默认参数的相机直接录包结果标出来的内参是一团糟。首先是分辨率。D435i 的 RGB 相机在 1280x720 下能提供足够的角点清晰度太低分辨率会导致角点提取亚像素精度下降。双目灰度相机建议用 640x480 而不是 1280x720原因有二一是灰度相机的高分辨率模式帧率会降到 15fps 以下运动模糊严重二是 Kalibr 的双目标定需要尽可能多的有效帧分辨率太高反而导致标定时间过长且容易出现外点。设置相机参数有几种方式。最简单的直接用 realsense-viewer 手动设置然后在启动 ROS 节点时指定参数roslaunch realsense2_camera rs_camera.launch \ rgb_camera.resolution:1280,720 \ rgb_camera.fps:30 \ infra_rgb_camera.resolution:640,480 \ infra_rgb_camera.fps:30但这样设置的参数未必完全生效我建议查看当前相机参数后再手动确认。关键的一点是关闭 RGB 相机的自动曝光和自动白平衡。标定过程中如果自动曝光一直在变图像的亮度特征会不稳定角点提取的置信度也会波动。在 realsense-viewer 中手动把 Exposure 设为固定值室内环境一般 100-200白平衡设为 4500K 左右。如果你用的是 realsense-ros 节点可以通过动态参数配置rosrun rqt_reconfigure rqt_reconfigure在 /camera/color 和 /camera/infra 对应的命名空间里关掉 auto_exposure手动设一个固定曝光值。对于红外相机关掉 auto_exposure 后还需要把 emitter_enabled 设为 0也就是关闭红外投影仪。D435i 的左右红外相机在室内近距离时一般是靠主动红外补光来增强纹理但标定时投影仪会投射出固定斑点图案这会在图像上叠加纹理噪声干扰角点检测。这个坑我踩过第一次采集的数据里红外图像上全是均匀分布的斑点导致双目标定失败后来才发现是 emitter 没关。3.2 录制 rosbag 的规范动作数据采集的核心要求是标定板必须在相机的各个视野区域内充分运动覆盖图像的边缘和中心。具体来说按照以下原则来采集标定板与相机平面的夹角从正对到倾斜约 45 度反复变化。标定板在视野内从左上角移动到右上角、左下角、右下角覆盖整个画面。距离从近处约 30 厘米到远处约 1.2 米之间变化。标定板静止时不要长时间保持同一个角度要持续地小幅度抖动。对于 IMU 标定还要加入旋转运动——绕相机的 X、Y、Z 轴分别转动标定板或者说晃动相机让 IMU 感受到充分的角速度变化。采集时长我建议至少 60 秒理想是 90 到 120 秒。太短了 IMU 的激励不足太长了标定板容易跑出视野之外反而引入噪声。还有一个技巧录包前先让相机预热 1 分钟让 IMU 的温度稳定下来因为 IMU 的 bias 对温度非常敏感冷启动直接录包会导致 bias 估计偏大。录制命令rosbag record -O d435i_calibration.bag \ /camera/color/image_raw \ /camera/infra1/image_rect_raw \ /camera/infra2/image_rect_raw \ /camera/imu注意要用 image_rect_raw 而不是 image_raw。image_rect_raw 是已经做过硬件校正的图内参矩阵基本接近理想值这能减轻 Kalibr 后续标定时的初始估计压力。当然 Kalibr 标定用的不是 image_rect_raw 也行它会自己估计畸变和投影模型但用 rectified 图像能让角点检测更稳定。3.3 标定板静止与运动结合这里有个很多人不知道的细节IMU 标定对静止和运动数据的需求是不一样的。静止时 IMU 的加速度计可以估计出重力方向和 bias运动时陀螺仪可以估计出旋转和噪声特性。所以数据采集最好分段进行前 10 秒标定板静止在视野中心相机完全不动。这部分数据用于初始化重力方向。中间 60 秒充分运动覆盖上述的各个角度、距离、位置。最后 10 秒再次静止。这样录制的 bag 在 IMU 标定时收敛速度快且不容易出现 bias 估计退化的问题。当然你也可以在后期用 Kalibr 工具截取 bag 子集但提前录好更省事。4. 单目内参标定实操4.1 单目标定命令与流程单目内参标定是整套流程的第一环目标对象是 RGB 相机。Kalibr 的单目标定命令如下cd ~/kalibr_ws rosrun kalibr kalibr_calibrate_cameras \ --bag /path/to/d435i_calibration.bag \ --topics /camera/color/image_raw \ --target /path/to/april_6x6_33mm.yaml \ --models pinhole-equi \ --dont-show-report这里 --models 参数要解释一下。Kalibr 支持多种相机模型畸变模型的组合模型适用于pinhole-radtan常规镜头radtan 畸变模型最常用pinhole-equi等距投影畸变模型适合鱼眼镜头omni-radtan全向相机模型适合折反射相机pinhole-fov视场角模型适合某些广角镜头D435i 的 RGB 镜头不是鱼眼严格来说是广角但接近针孔模型用 pinhole-radtan 就行。但如果你希望和官方 SDK 输出的内参对标可以先用 SDK 看一下默认内参再决定模型。我用的是 pinhole-radtan标定结果和 SDK 自带的出厂内参非常接近说明这个模型对 D435i 是合适的。--dont-show-report 的作用是标定结束后不自动弹出可视化报告在远程服务器上跑的时候很有用。不加这个参数的话标定完成后会弹出一个窗口显示重投影误差分布图对于快速判断标定质量很有帮助。我建议第一次跑先不加把弹窗里的信息截个图方便后面对比。4.2 标定结果解读跑完单目标定后工作目录下会生成几个文件camchain-d435i_calibration.yamlresults-cam-d435i_calibration.txtreport-cam-d435i_calibration.pdf打开 camchain 文件核心内容如下cam0: camera_model: pinhole distortion_coeffs: [0.0145, -0.0032, 0.0008, 0.0002] distortion_model: radtan intrinsics: [381.23, 381.45, 322.11, 239.87] resolution: [640, 480] rostopic: /camera/color/image_raw这里 intrinsics 是 fx, fy, cx, cy。fx/fy 约 381 意味着焦距大约是 381 像素和 D435i 的 1280x720 分辨率下的出厂值相比像素比例大约缩小了一半。resolution 显示 640x480 是因为我录 bag 时用 rs_camera.launch 把 RGB 设成了 640x480。这一点要注意内参是和分辨率绑定的如果后续用 1280x720 的图需要把内参按比例放大。结果文件里最关键的质量指标是重投影误差单位是像素Reprojection error: 0.12 px0.12 像素是非常理想的一般 0.3 像素以内都算不错。如果超过 1 像素说明采集的数据有问题最可能是运动过快导致图像模糊、角点定位不准或者标定板尺寸填错了。4.3 单目标定的常见异常处理单目标定时最常见的报错是The optimization failed to converge这个报错通常意味着 corner detection 失败或者外点太多。排查顺序如下检查 bag 里图像是否清晰。用rosbag play配合 rqt_image_view 看图像如果运动模糊严重就得重新采集。检查标定板 YAML 的参数是否和实际打印一致。tag_size 的误差导致棋盘格边长比例不对优化会发散。检查图片亮度是否过高或过低。如果曝光不合适角点黑白的灰度差太小检测器找不到角点。还有一个我在实际使用中发现的规律如果标定板在图像中占比太小小于 1/3 画面检测到的角点数量会骤减标定结果的方差会变大。后期处理没法补救必须重新采集。5. 双目内参标定实操5.1 双目标定命令单目标定通过后开始双目内参标定。双目标定需要同时给左右目的图像 topicrosrun kalibr kalibr_calibrate_cameras \ --bag /path/to/d435i_calibration.bag \ --topics /camera/infra1/image_rect_raw /camera/infra2/image_rect_raw \ --target /path/to/april_6x6_33mm.yaml \ --models pinhole-radtan pinhole-radtan \ --dont-show-report双目标定输出的是左右目各自的内参以及两者的相对位姿cam1: T_cn_cnm1: - [0.99999, -0.00015, 0.00011, -0.04998] - [0.00015, 0.99999, -0.00006, 0.00002] - [-0.00011, 0.00006, 0.99999, 0.0001] - [0.0, 0.0, 0.0, 1.0]这个矩阵的意思是 cam1 相对于 cam0 的位姿变换。从平移项可以看到x 方向是 -0.05也就是约 5 里面的基线距离这是 D435i 的物理基线长度合理。而旋转部分几乎是单位矩阵说明两个红外相机光轴基本平行。5.2 双目标定中对极几何验证双目标定比单目复杂的地方在于它不仅要估计每个相机的内参还要同时优化两个相机之间的旋转和平移使得左右目图像的对应点在极线上对齐。标定后的 PDF 报告里会显示左右图像上检测到的棋盘格角点的极线误差图。一个判断标定是否成功的方法是计算对极几何验证。Kalibr 本身不直接提供这个功能但我习惯把标定结果投影回去左右图像上的匹配角点对应该满足极线约束。实操中更简单的核验方式是标定完成后用 stereo 相机跑一个深度图观察深度图在边缘处有没有明显的断裂或者晕影。如果外参不准深度图在物体边缘会出现很重的伪影。另外双目标定对图像的同步性要求很高。D435i 的左右红外相机硬件上就是同步曝光的这里问题不大。但是因为录制 bag 时 topic 之间没有硬同步Kalibr 会利用时间戳做插值对齐。如果时间戳抖动剧烈你可以在 launch 文件里设 align_depth 或者用rostopic hz检查各 topic 的实际发布频率。5.3 双目内参的坑基线长度我遇到过一种情况双目标定出来的基线长度只有 3 厘米而 D435i 的实际基线是 5 厘米。排查了半天发现是标定板尺寸填错了。标定板实际打印出来比 PDF 理论的尺寸缩小了 2%导致光心偏移估计整体缩小。基线误差会直接传递到后面的 IMU 外参标定所以如果我看到基线偏离物理值超过 10%我会直接判定标定失败重新采集。还有一个不太起眼但影响很大的参数是 tag_spacing。Aprilgrid 的 tag_spacing 在官方定义中是间距除以 tag_size默认是 0.3但不同版本的 Kalibr 可能对应不同的定义。如果你的 YAML 文件直接从网上复制一定要确认你的 Kalibr 版本对这个参数的解释。这个参数配错的表现和 tag_size 配错一样就是对极几何误差很大但标定报告的重投影误差却很小。6. IMU 标定与相机-IMU 联合标定6.1 IMU 内参标定的意义D435i 的 IMU 型号是 Bosch BMI055它输出的加速度和角速度都带有噪声和 bias。Kalibr 的 IMU 标定就是估计这三个量加速度计噪声密度accelerometer_noise_density陀螺仪噪声密度gyroscope_noise_density加速度计随机游走accelerometer_random_walk陀螺仪随机游走gyroscope_random_walk这些参数在视觉惯性里程计里是 EKF 或因子图优化的协方差设定依据。设小了滤波器会过度信任 IMU 测量设大了则会过度信任视觉两种情况都会导致定位精度下降。所以这一步不能跳过不能用 Datasheet 里的标称值替代因为每颗芯片的实际噪声特性都有区别。6.2 IMU 标定命令在联合标定之前可以先用 Kalibr 单独标定 IMU 内参使用的命令是 kalibr_calibrate_imu_noiserosrun kalibr kalibr_calibrate_imu_noise \ --bag /path/to/d435i_calibration.bag \ --cam /path/to/camchain.yaml \ --imu /path/to/imu.yaml \ --time-calibration这里需要准备一个 imu.yaml描述 IMU 的测量模型# imu.yaml accelerometer: noise_density: 0.0025 # 初始猜测值后续会被标定结果更新 random_walk: 0.0005 gyroscope: noise_density: 0.0005 random_walk: 0.00001 frequency: 200这些初始值我从 BMI055 的 Datasheet 里抄的Kalibr 会以它们为起点优化最终输出标定后的值。频率设为 200是因为 D435i 的 IMU 输出频率是 200Hz这个参数不能错。rosrun kalibr kalibr_calibrate_imu_noise 的输出文件会给出更新后的 imu.yaml同时可视化重力对齐结果。如果采集的数据里包含了充分的旋转运动这个标定会很快几分钟就能跑完。6.3 联合标定命令IMU 内标签好后进入最后一步相机-IMU 外参标定。这里的输入是 RGB 和左右目中的其中一个一般用双目中的左目因为它是灰度图和 IMU 的同步性更好以及 IMU 数据。rosrun kalibr kalibr_calibrate_imu_camera \ --bag /path/to/d435i_calibration.bag \ --cam /path/to/camchain-d435i_calibration.yaml \ --imu /path/to/imu.yaml \ --target /path/to/april_6x6_33mm.yaml \ --time-calibration注意这里的 camchain 文件要选双目标定输出的那个里面包含 cam0 和 cam1 的信息联合标定只需要其中一个相机到 IMU 的外参。标定结果的核心是 T_cam_imu 矩阵也就是相机系到 IMU 系的变换。在 D435i 的官方设计里IMU 是位于红外相机中心后方的所以平移部分通常是 [0.02, 0.0, 0.01] 量级旋转部分接近单位矩阵但有一个很小的角度偏转。如果你标定出来的平移往某个方向偏了很大比如 5 厘米以上那说明标定时报错集有问题可能是 bag 录制时左右目数据时间戳不对齐。6.4 时间延迟标定一个容易被忽略的参数--time-calibration 这个参数让 Kalibr 同时估计相机和 IMU 之间的时间延迟。D435i 的固件里图像和 IMU 数据是同一个时间基准的但经过 USB 传输和 ROS 驱动处理两者到达系统的时间会有几毫秒的偏差。不要小看这几毫秒在快速旋转运动时几毫秒的错位会导致外参旋转估计出现 0.5 度以上的误差。Kalibr 会输出一个名为 cam0_timestamp_shift 的参数单位是秒。如果它是正数说明 IMU 数据提前于图像如果是负数说明图像提前于 IMU。我标定的结果是大约 -0.003 秒也就是图像快于 IMU 约 3 毫秒这是