ARTICLE DETAIL

资讯详情

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

Realsense D435i标定全攻略:内参外参与IMU联合标定实战

Realsense D435i标定全攻略:内参外参与IMU联合标定实战 1. D435i不是“一个相机”标定之前先搞清楚有哪几套内外参很多人第一次拿到Realsense D435i第一反应就是“这是一台RGB-D相机”然后打开realsense-viewer看到彩色图和深度图就把它当普通单目相机去标定。这个理解会直接导致后面出现一个很麻烦的现象RGB内参标得漂漂亮亮深度图和彩色图却依然对不齐机械臂抓取时误差肉眼可见。原因很简单——D435i从来不是“一个相机”而是至少四个成像/测量单元装在一起的多传感器系统。1.1 相机模组的真实组成D435i正面包含一枚RGB相机、左右两枚红外相机、一个红外点阵发射器就是那个十字形的投射窗口内部还有一颗Bosch BMI055六轴IMU。这里面需要区分清楚RGB相机负责彩色图像卷帘快门取景视场比深度小一圈。左右红外相机组成深度立体视觉全局快门主要工作在近红外波段。红外点阵发射器投射不可见纹理帮助左右目在弱纹理表面匹配出深度。BMI055 IMU三轴加速度计加三轴陀螺仪为视觉惯性里程计、机械臂抖动补偿提供运动测量。所以“内参”并不是一套至少是RGB、左红外、右红外各一套。每套内参都包含焦距、主点、畸变系数这些我们熟悉的量。“外参”也不是一段而是描述RGB相机、深度相机以左红外坐标系为基准、IMU三者之间刚体变换的多段位姿。日常说“标定D435i”实际上可能是四件事深度自校准、RGB内参复标定、RGB-D外参标定、IMU与相机联合标定。先想清楚你要做哪一件才能选对工具。1.2 什么情况下需要自己动手标定出厂时D4视觉处理器已经把标定结果写进相机ROMSDK启动时自动加载所以绝大多数应用直接用就行。我自己的项目经验是真正需要人工介入标定的场景主要有三类第一设备经历过跌落、拆装、镜头受力出厂内参出现偏差第二应用对绝对精度要求很高例如三维测量、高精度引导需要把内参和重投影误差控制在零点几个像素第三要把D435i和机械臂、其他相机、激光雷达等传感器标定到一起——这种情况外参标定是躲不掉的。判断你的相机是否“还正常”有一个简单的方法在realsense-viewer里同时打开彩色图、深度图和对齐后的彩色图把它对准一个有清晰边缘的物体观察边缘是否错位、深度填充是否正常。错位超过几个像素或者深度有明显的系统性偏移就该进入下面的标定流程了。2. realsense-viewer里的校准选项能修什么不能修什么realsense-viewer是Intel官方工具很多人只拿它来看图像、录bag文件其实它的Calibration页签藏着不少维护手段。不过这个工具能做的和大众理解中的“相机标定”并不完全是一回事搞清楚了才不会绕弯路。2.1 深度自校准与on-chip动态校准在realsense-viewer的Calibration页签里D435i最常用的是深度自校准操作方法很简单把相机正对一面均匀的白墙或平坦表面调整曝光让图像不过曝然后点Calibrate。它会采集几张图计算深度缩放的修正量并写回设备。这套机制解决的是深度度量误差比如x、y、z方向的比例偏移它不会给你一份可以带走的OpenCV内参文件。另一项是on-chip动态校准需要打印Intel官方提供的专用靶标在接近暗室的受控条件下运行耗时较长适合产线或出厂前校准普通项目里很少用到。这里要特别提醒viewer里的这些校准选项都不能替代RGB内参和RGB-D外参标定。我见过不止一个朋友在viewer里跑完“Calibration”后以为RGB和深度就对齐了但一用点云配准就露馅。它们修的侧重完全不同前者是深度模组自身的度量精度后者是不同传感器之间的外参关系。2.2 ARM64平台上的viewer限制搜索“realsense viewer arm64”的人越来越多因为D435i在Jetson、树莓派这类ARM64平台上非常常见尤其是做机械臂和轮式机器人的项目。实测下来的情况是官方预编译的realsense-viewer一般只提供x86_64版本ARM64平台上要么用SDK源码自己编译要么放弃图形界面直接用命令行工具和pyrealsense2。我在Jetson Orin上编译过一遍完整SDK流程本身不复杂但很耗时而且需要画面环境才能把viewer跑起来。如果只是读参数、做深度对齐、录bag完全没有必要在ARM上折腾viewer直接装Python的pyrealsense2即可。还有个小坑老固件在ARM平台上的自校准接口表现不稳定操作前先在x86电脑上升级固件再回到ARM机器上验证参数是否正常读取。3. 从SDK直接读出厂内外参代码、坐标系与畸变模型如果你不想重复造轮子第一件事其实不是去标定而是先知道D435i出厂时已经存了什么参数。通过pyrealsense2读出来的内参和外参能直接用在很多视觉流程里。3.1 内参结构与畸变模型判读用pipeline启动一个profile之后每个视频流都带Intrinsics一个典型的读取脚本长这样import pyrealsense2 as rs pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipeline.start(config) for stream_profile in profile.get_streams(): vs stream_profile.as(rs.video_stream_profile()) intr vs.get_intrinsics() print(fstream{stream_profile.stream_type()}) print(f resolution{intr.width}x{intr.height}) print(f fx{intr.fx:.4f}, fy{intr.fy:.4f}) print(f ppx{intr.ppx:.4f}, ppy{intr.ppy:.4f}) print(f model{intr.model}) print(f coeffs{intr.coeffs}) pipeline.stop()读取本身不难难点在于畸变模型。pyrealsense2返回的intr.model可能是Brown-Conrady也可能是Inverse Brown-Conrady不同流、不同固件版本对同一型号设备的返回值都可能不一样。OpenCV的cv2.undistort默认对应的是Brown-Conrady也就是plumb_bob模型如果你拿到的model是inverse brown conrady直接用OpenCV去畸变边缘区域误差会很可观。我踩过这个坑之前某版固件下读取的红外流model就是inverse brown conrady同事按RGB那套方法直接去unidistort红外图结果边缘坐标偏了近两个像素。遇到这种model更稳妥的做法是走SDK自己的重投影接口或者将系数按模型定义转换成OpenCV格式后再用。3.2 外参读取与坐标系约定外参要通过get_extrinsics_to拿它返回一个rotation数组和一个translation数组。语义是“从当前坐标系变换到目标坐标系”也就是p_target R * p_current t。depth_profile profile.get_stream(rs.stream.depth).as(rs.video_stream_profile()) color_profile profile.get_stream(rs.stream.color).as(rs.video_stream_profile()) ext depth_profile.get_extrinsics_to(color_profile) print(rotation:, ext.rotation) print(translation:, ext.translation)这里方向极其容易搞反。很多人看到“depth到color”就以为translation是depth原点相对color原点的偏移动手时发现点云怎么都对不齐回头一看是p_color R * p_depth t和p_depth R * p_color t用混了。建议每次使用前先打印一段已知坐标验证比如深度坐标系里的原点用外参变换到彩色坐标系后应该落在RGB画面中心附近靠近左下方的位置而不是飞出画面。RealSense坐标系的约定是Z轴朝前、X轴向右、Y轴向下和计算机视觉常用的optical frame一致。在机械臂场景里从深度坐标系到机械臂基座的变换不能直接用这只外参替代它只是传感器之间的小外参手眼标定负责的是另外一层大外参。3.3 IMU外参怎么拿IMU和相机的外参Python SDK在不同版本下可用接口不一致直接调get_extrinsics_to不一定拿到完整结果。更省心的方式是借助realsense-ros启动驱动后它会以TF树的形式发布camera_imu_optical_frame到camera_depth_optical_frame的变换用tf2直接查就行。如果不想引入ROS还可以尝试rs-enumerate-devices查看设备完整信息或者从设备校准文件中解析。需要提醒的是IMU外参在标定前最好不要轻信任意一种方法的原始值先用静态放置试验确认方向合理再往下走。4. 棋盘格复标定RGB与红外内参采集纪律与OpenCV流程当生产环境对精度要求高或者设备出过故障就需要用传统棋盘格做一次完整的复标定。这一步对应的是“d435i相机标定”里最常见的那批需求。4.1 采集样本的三个纪律内参标定的结果高度依赖采集质量我建议坚持三条纪律。第一标定板要贴在完全平整的硬板上不要用褶皱的A4纸。棋盘格印刷尺寸必须准确代码里的square_size要和实测一致差一毫米都会让焦距和主点偏掉。推荐9x6或者12x9这种非对称格子提取角点时不会产生方向歧义。第二相机固定在一个位置人移动标定板让标定板在画面里覆盖不同位置、不同距离、不同角度总共拍15到25张。不要一直把标定板放在画面正中央边缘区域覆盖不够畸变系数就估计不准。第三固定相机曝光。RGB相机要手动关掉自动曝光和自动白平衡快门适当调快避免卷帘快门在移动标定板时产生斜向模糊。IR相机在标定前要把红外发射器关掉不然激光散斑会严重干扰角点提取如果关掉发射器后红外图太暗可以在附近加一个红外补光灯我用普通LED灯在1.5米距离上照棋盘格效果也够用。4.2 标定脚本与重投影误差判断OpenCV的标定流程已经很成熟脚本骨架如下import numpy as np import cv2 import glob chess_size (9, 6) square_mm 25.0 objp np.zeros((chess_size[0] * chess_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:chess_size[0], 0:chess_size[1]].T.reshape(-1, 2) * square_mm obj_points [] img_points [] for f in sorted(glob.glob(rgb_calib/*.png)): img cv2.imread(f) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, chess_size, None) if not ret: print(跳过:, f) continue criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None) print(RMS:, ret) print(内参矩阵:\n, mtx) print(畸变系数:, dist.ravel())RMS的单位是像素。RGB标定到0.2到0.5像素是比较健康的水平如果RMS超过0.8像素基本可以断定采集环节出了问题比如标定板不平、有模糊帧、或者某些帧的角点被误检。这时候要先把明显差的帧删掉再重跑而不是直接拿结果去用。IR相机因为分辨率低、角点定位精度天然差一点0.5到0.8像素都算可接受。4.3 红外相机标定的特殊处理红外内参标定的操作流程与RGB相同但有两点需要注意。一是发射器必须关闭否则生成的散斑纹理会让角点提取结果跳动二是在有些固件版本下红外图默认带有一些自动增益需要手动固定曝光和增益否则不同亮度帧的内参解算会不一致。标定完成后记得把左红外、右红外两套内参分别存好不要只用其中一套代替深度相机内参——虽然深度计算以左红外为基准但右红外参与立体匹配它在做极线校正时也需要自己的内参。5. IMU与相机联合标定Kalibr那条路的完整流程如果你的应用是视觉惯性里程计或需要相机与IMU数据融合比如机械臂的震动补偿单靠出厂自校准是不够的。你还得知道IMU坐标系相对相机的旋转和平移以及IMU数据相对图像的时间延迟。这套工作通常交给Kalibr这类工具。5.1 准备工作ROS驱动与噪声参数系统需要先装好ROS、realsense-ros和Kalibr。启动相机驱动时IMU相关的关键参数要打开roslaunch realsense2_camera rs_camera.launch \ enable_gyro:true enable_accel:true \ unite_imu_method:2 \ rgb_camera.profile:640x480x30unite_imu_method:2会输出一个合并了加速度计和陀螺仪数据的/camera/imu话题Kalibr可以直接订阅。如果设成0就只会有单独的gyro和accel话题处理起来麻烦一些。IMU噪声参数有两种获取方式。严格做法是用imu_utils记录一小时以上的静置数据跑Allan方差得到噪声密度和随机游走时间紧时也可以用BMI055的典型量级做初值。这里给一个参考数量级陀螺噪声密度约0.002到0.004 rad/s/sqrt(Hz)随机游走约0.00005到0.0001 rad/s^2/sqrt(Hz)加速度计噪声密度约0.002到0.005 m/s^2/sqrt(Hz)随机游走约0.0001到0.0003 m/s^3/sqrt(Hz)。但这些只是量级参考正式项目务必实测。5.2 采集动态数据并执行标定命令标定板建议用aprilgrid把它固定在墙上。相机握在手里做六自由度运动让平移和旋转都充分激励每个轴都转一转标定板尽量保持在画面内录30到60秒。如果轨迹太单一比如只绕一个轴转解算出来的旋转外参会非常不稳定。先标定相机内参和目标板位姿kalibr_calibrate_cameras \ --target aprilgrid_6x6.yaml \ --bag dynamic.bag \ --topics /camera/color/image_raw \ --models pinhole-radtan再联合标定IMU与相机kalibr_calibrate_imu_camera \ --target aprilgrid_6x6.yaml \ --cam camchain-dynamic.yaml \ --imu imu.yaml \ --bag dynamic.bag \ --time-calibrationimu.yaml里update_rate要填实际发布的IMU频率不要照抄网上示例先rostopic hz /camera/imu查一下真实值。5.3 结果解读与验证输出结果yaml里包含相机与IMU之间的旋转和平移以及时间偏移td。不同版本Kalibr的变换记法有差别有的写T_i_c有的写T_cam_imu含义方向也可能不同。拿到结果后先别急着写死到代码里我的习惯是做一个静态验证把相机固定在桌上同时读IMU的姿态增量积分和视觉里程计的姿态变化如果两者方向一致说明旋转外参方向对了如果反了旋转部分多半需要转置或反转。这一步能省掉后面大量的排查时间。6. 多相机与机械臂场景外参校准从离线到在线D435i经常出现在机械臂抓取、多相机拼接这类场景外参标定的需求也从“标一次用一年”变成了“每隔一段时间就要重新验证”。这里说几条被问得最多的路子。6.1 双相机刚性外参靶标共视法如果两个D435i有共同视野最直接的离线标定是靶标共视法。固定一个ArUco或ChArUco标定板让它在两台相机中都能完整看到先用solvePnP求出标定板到每台相机的位姿然后利用刚体变换闭合关系T_cam1_to_cam2 T_board_to_cam2 * inv(T_board_to_cam1)多换几个标定板姿态用SVD或中值滤波把多组结果融合成最终外参。要注意的是多组姿态之间要有足够的旋转差异否则标定板的平面与相机光轴近似平行时解算出来的外参会退化。6.2 机械臂手眼标定的实操要点D435i装在机械臂末端属于典型的eye-in-hand问题固定在外侧观察末端属于eye-to-hand问题。两种问题本质都是求解AXXB只是A、B的定义方式不同。我自己在机械臂项目里常用ArUco标定板加cv2.calibrateHandEye代码核心就一段# R_gripper2base, t_gripper2base: 机器人控制器给出的末端到基座位姿序列 # R_target2cam, t_target2cam: ArUco/solvePnP解出的标定板到相机位姿序列 R_cam2gripper, t_cam2gripper, _ cv2.calibrateHandEye( R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, methodcv2.CALIB_HAND_EYE_TSAI )采集时有几个关键点末端姿态至少要15到20组旋转角度范围越大越好不要全部是平移运动纯平移时AXXB约束不足每次运动后机器人要停稳再触发采集不要把运动中的位姿采进去标定板要固定在环境中不要跟着末端动。标定完成后把T_cam2gripper和机器人实时发布的T_gripper2base合成得到相机到基座的变换然后找一个已知标志物在几个新位置做验证投影。投影误差在几个像素以内才算合格。6.3 在线外参校准思路被搜得比较多的“fast-ndt配准与在线外参校准”本质上是一种在线外参优化思路当多传感器刚性安装但无法随时摆靶标时利用点云配准的方式不断修正外参。具体做法是先离线给一个较好的初值然后对两个传感器同时采集的场景点云做NDT配准把配准得到的增量变换当作外参修正量叠加到当前外参上。这种做法用在两个D435i共同观察同一场景时很有效尤其是机械臂应用里每次上电后自动跑一次在线校准能补偿安装松动带来的微小偏移。但要注意NDT配准对初值敏感外参偏了几厘米或几度时就容易配错所以它适合“微调”而不是“从零标定”。初值还是老老实实用靶标法或出厂数据拿到。7. 标定质量验证与常见坑标定做完了不能直接上生产先验证再谈部署。这一节把我在实际项目里用来把关的指标和踩过的坑一并列出来。7.1 用三类指标确认标定合格衡量标定质量的指标我通常分三层看。内参重投影误差RGB控制在0.5像素以内IR控制在0.8像素以内。RGB-D对齐误差在1米的距离上把深度图投影到彩色图观察物体边缘错位。正常情况下错位应小于3个像素如果大于这个数优先查depth到color的外参方向是否用反。IMU状态估计一致性静态放置时加速度计和陀螺仪输出应有明显的重力分量和接近零的角速度联合标定后短时间视觉惯性里程计漂移不应出现方向性发散。三个指标都过了标定结果基本可靠。7.2 高频踩坑清单坑现象原因与对策标定板尺寸填错焦距和主点结果离谱用卡尺测实际方格边长不要相信打印设置IR标定时发射器没关角点提取抖动在viewer或代码里关闭发射器再采集曝光没有固定每次采集亮度差异大手动锁定曝光、增益、白平衡分辨率混用内参张冠李戴标定用哪个分辨率部署就用哪个分辨率外参方向搞反点云系统性偏移用已知点验证p_target R * p_current tARM平台强行跑viewer编译失败或界面卡死更新固件必要时用Python API代替图形工具7.3 标定文件管理的工程习惯最后说一个容易被忽略但很重要的工程习惯标定结果文件里一定要带上设备序列号、固件版本、标定日期和标定分辨率单独存成json或npz。因为D435i在不同固件版本下甚至每次开机时读到的深度内参都可能有一点点差异如果文件里没有版本信息出问题时完全没法回查。换固件后也要重新做一次验证不要默认“标过一次永远有效”。我个人的做法是给每台设备建立一个标定档案任何一个标定文件的改动都留记录这样机械臂项目里几十台D435i一起跑出了问题也能快速定位是哪一批次哪一台设备的参数异常。
返回列表