RealSense D405 如何标定:基于 librealsense 的 5 步相机校准流程与精度验收标准
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
Intel RealSense D405 深度相机标定,是让你的三维测量从"大致可信"变成"可交付"的关键一步。开源项目librealsense(Intel RealSense SDK)把 D400 系列的完整标定能力封装成了一套内置 API:芯片内标定(On-Chip Calibration,下称 OCC)、焦距标定和 Tare 零点标定,全部可以直接调用,不需要自己写优化算法。配合 doc/depth-from-stereo.md 里讲解的双目深度成像原理,你大概十分钟就能完成一次完整的标定并验证结果。
为什么标定结果会"飘":D405 深度图的 3 个误差来源
在动手之前,先搞清楚深度数据为什么会不准,你就知道每一类标定在补什么短板。
D405 靠左右两个红外相机做视差匹配来算深度,这个过程中有 3 个位置会引入系统性误差:
- 左右相机没对齐(外参误差):出厂标定受温度、应力影响会漂移,左右图像基线一旦偏移,深度图整体出现倾斜或错位
- 焦距与标定值不一致:镜头装配公差导致实际焦距偏离内参矩阵里的
fx,深度值出现固定比例偏差,画面边缘最明显 - 深度零点偏置(Tare 误差):传感器本身存在毫米级系统偏置,测 1 米可能稳定地报 1.02 米——这种误差靠滤波永远去不掉,只能靠标定修正
避坑提示:不要指望后处理滤波"洗"掉系统误差。低通、空洞填充这类滤波器只能解决噪声,解决不了比例性偏差。先标定,再滤波。
🔧 标定前必须确认的 3 个前置条件
标定失败大多不是算法问题,而是环境不满足。librealsense 官方 UCAL 示例(wrappers/python/examples/depth_ucal_example.py)在开头就强制检查了 3 项前提,你可以直接对照:
- USB 3.0 连接:标定流程(尤其是焦距标定要同时开两路 1280×720 红外流)对带宽要求高,USB 2.0 会直接流不起来
- Advanced Mode 已开启:多数标定需要调整相机内部预设,必须先进入高级模式
- 深度流有效点数充足:OCC 运行前,画面里有效深度点通常要超过 50%,否则扫描无法收敛
第一段代码用来验证前两项:
import pyrealsense2 as rs ctx = rs.context() device = ctx.query_devices()[0] print(device.get_info(rs.camera_info.name)) # 确认 USB3 连接:UCAL 流程假设 USB3 带宽 usb = device.get_info(rs.camera_info.usb_type_descriptor) print("USB 类型:", usb)验证没问题后,用下面 4 行代码开启 Advanced Mode:
# 标定需要 Advanced Mode(注意:该调用会复位相机,之后需重新枚举设备) am = rs.rs400_advanced_mode(device) if not am.is_enabled(): am.toggle_advanced_mode(True)效率技巧:把相机放在通风良好的位置预热 10 分钟再开始。温度波动大的过程中标定,得到的参数本身就在漂移,等于白标。
📷 分步实操:从开启高级模式到写入 Flash
前置条件确认完毕,下面按时间顺序走完 5 个步骤。每一步都遵循"操作 → 立即验证"的闭环,完整可运行版本见 examples/on-chip-calib/ 和上面的 UCAL 示例脚本。
第一步:跑芯片内标定(OCC)
OCC 利用相机自身的深度流扫描左右相机的内参与外参,不需要任何标定靶板。官方示例推荐用低分辨率高帧率的特殊配置(256×144、Z16、90fps),速度档位 0~4 可调,数字越大耗时越长、精度越高:
import json cfg = rs.config() cfg.enable_stream(rs.stream.depth, 256, 144, rs.format.z16, 90) pipe = rs.pipeline(ctx) pp = pipe.start(cfg) pipe.wait_for_frames() adev = pp.get_device().as_auto_calibrated_device() # speed 0~4 速度档位,scan parameter 0 = 内参扫描,1 = 外参扫描 table, health = adev.run_on_chip_calibration( json.dumps({"calib type": 0, "speed": 2, "scan parameter": 0}), progress_callback, 30000)验证:回调打印到 100% 且table非空即成功,health是设备给出的健康度评分,记录下来备查。
第二步:焦距标定(需要一张 175×100mm 的靶板)
OCC 修正的是"对齐",焦距标定修正的是"比例"。它需要左右红外原始流(stream index 1 和 2)同时采集约 25 帧,用靶板真实尺寸约束出正确焦距:
# 焦距标定:左右红外原始流各取约 25 帧(lq/rq 为帧队列,此处省略入队细节) cfg = rs.config() cfg.enable_stream(rs.stream.infrared, 1, 1280, 720, rs.format.y8, 30) cfg.enable_stream(rs.stream.infrared, 2, 1280, 720, rs.format.y8, 30) pp = rs.pipeline(ctx).start(cfg) adev = pp.get_device().as_auto_calibrated_device() # 靶板真实尺寸 175mm x 100mm,adjust_side 传 0 表示只修右侧 table, ratio, angle = adev.run_focal_length_calibration( lq, rq, 175, 100, 0, progress_callback)验证:返回的ratio越接近 1、angle越接近 0,说明出厂标定与你当前装配状态越一致;偏差大就值得写入新表。
第三步:Tare 零点标定
把靶板放在一段已知真实距离上(例如 450mm),让传感器用"测量值 vs 真实值"的差去修正深度零点:
# Tare 标定:消除深度测量的固定系统偏置 table, health = adev.run_tare_calibration( 450, # 靶板真实距离,单位 mm json.dumps({"accuracy": 2, "scan parameter": 0}), progress_callback, 30000)验证:标定前后各测 5 次同一距离,均值偏差应从"稳定偏大/偏小"收敛到接近 0。
第四步:写入 Flash,让结果永久生效
这是新手最容易漏的一步——标定结果默认只在当前会话有效,断电即失。官方示例会交互询问你是否保留,保留时要显式写表:
# 标定结果只对当前会话有效,必须显式写入 Flash 才永久生效 adev.set_calibration_table(table) adev.write_calibration()如果标砸了想回退:标定前先用get_calibration_table()把旧表存下来,再set_calibration_table(旧表)+write_calibration()写回即可,也可以直接断电复位。
第五步:录一段数据做留档验证
标定完成后,用 SDK 自带的录制工具存一段.bag文件作为"标定快照",下次怀疑相机跑偏时直接回放对比。工具用法见 doc/record-and-playback.md:
回放界面长这样,可以逐帧检查标定后深度图的纹理与边缘质量:
⚠️ 调优与避坑:标定失败的 4 种典型症状
按"现象 → 原因 → 解决"排查,能覆盖绝大多数翻车场景:
1. 启动管道就报 "Couldn't resolve requests"原因:请求的分辨率/帧率组合设备不支持,最常见于 USB 2.0 接口带宽不够。 解决:换 USB 3.0 口(或缩短线材、换主动集线器);仍失败就降低分辨率或帧率再试。
2. 标定明明成功,重启后精度又变回去了原因:只拿到了新标定表,没有写 Flash(见第四步)。 解决:补一次set_calibration_table+write_calibration。
3. 某一固定距离段测量值整体偏大或偏小原因:Tare 零点偏置没修,或只做了 OCC 漏了后两步。 解决:按 OCC → 焦距 → Tare 的顺序补齐,官方推荐顺序就是这三步。
4. OCC 扫描卡住、进度条不涨或 health 很差原因:有效深度点不足(目标过暗/过曝/镜面反光),或环境温度在漂移。 解决:把相机对准有纹理的中灰度平面;UCAL 流程中示例会先把emitter_enabled与thermal_compensation置 0,避免纹理投影器和温度补偿干扰扫描,照做即可;等相机恒温 10 分钟再跑。
✅ 验收标准:用 3 个数字判断标定是否合格
| 检查项 | 合格线 | 优秀线 |
|---|---|---|
| 有效深度点占比(OCC 运行前提) | ≥ 50% | ≥ 80% |
棋盘格重投影误差(用 OpenCVcalibrateCamera复核内参) | < 0.5 像素 | < 0.3 像素 |
| 定点测距偏差(对真实距离,1m 以内) | < 距离的 2% | < 距离的 1% |
标定后可以用 tools/depth-quality/ 工具生成精度曲线,把测距误差随距离的分布画出来,和上表逐条比对:
下一步行动:现在就跑一遍 wrappers/python/examples/depth_ucal_example.py——它是三条校准流程的官方参考实现,支持--onchip-speed、--tare-gt等命令行参数,跑完对照上面的验收表打勾。之后建议在设备每次经历运输、跌落或环境温度跨度过大(如从冷库到车间)后,重复一次这套流程。标定核心实现位于 src/ds/d400/d400-auto-calibration.cpp,IMU 相机的标定差异见 doc/d435i.md。
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考