用 D435i 做室内避障:从接线到跑通的快速路径
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
场景很具体:机器人向前开,前方桌角离它 60 厘米,你得拿到一个能用的"60 厘米";它转头时,还得有陀螺仪角速度。Intel RealSense D435i 配合开源 SDK librealsense(即 RealSense SDK 2.0)干的就是这件事——立体测深加 6 轴 IMU,同一套坐标系、同一个硬件时钟出数据。
装好 SDK 并确认设备可见
这节只回答一个问题:在你的 Linux 机器上敲哪些命令,才能让相机出现在设备列表里。
git clone https://gitcode.com/GitHub_Trending/li/librealsense cd librealsense && mkdir build && cd build sudo apt install build-essential cmake libusb-1.0-0-dev libudev-dev pkg-config v4l-utils sudo ../scripts/setup_udev_rules.sh # ← 坑:必须 sudo 跑,否则设备列表是空的 cmake .. -DBUILD_EXAMPLES=ON -DCMAKE_BUILD_TYPE=Release make -j$(nproc) && sudo make install装完用一条命令验证:rs-enumerate-devices。看到Name: RealSense D435i和Name: IMU(gyro、accel 各 200Hz)两组条目,说明深度和运动两条链路都通了。顺手把realsense-viewer跑起来,有画面再进下一步。
两个提醒:Ubuntu 20.04/22.04 上如果你要用 IMU 的硬件级时间戳,先执行scripts/patch-realsense-ubuntu-lts-hwe.sh打内核补丁;其余主流发行版即插即用。
读懂一帧深度:数据在说什么
这节讲清楚深度数据拿到手到底代表什么,后面的代码才不会只是背。
先看最短能跑的程序(仓库里 examples/hello-realsense/ 目录就是它的完整版):
rs2::pipeline pipe; rs2::config cfg; cfg.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30); pipe.start(cfg); while (true) { auto batch = pipe.wait_for_frames(); // ← 注意:等的是"一组帧",不是一帧 auto depth = batch.get_depth_frame(); if (depth) printf("画面中心距离: %.2f m\n", depth.get_distance(320, 240)); }wait_for_frames()返回的不是单帧,而是一批:SDK 把时间戳足够接近的各传感器帧归成一个 frameset,同一批里的深度和 RGB 可以当成"同一瞬间",这是后面所有对齐操作的地基。
开发时高频要记住的约定就这几条:
- 深度像素是 16 位整数,乘 scale 才是米,scale 默认 0.001,运行时用
get_scale()读,别写死; - 坐标原点在深度相机光心,X 向右、Y 向下、Z 朝前,和 OpenCV 针孔模型同一套约定,投影公式可以直接套用;
- 时间戳单位是毫秒,深度、RGB、IMU 共享同一个硬件时钟;
- IMU 是 200Hz,比深度快得多,frameset 里的 IMU 帧是"最接近那帧深度的那一条",不是唯一一条;
- 加速度计静止时 Y 轴读数约 -9.8 m/s²,它测的是惯性力、方向与重力相反,这是正常现象,别当故障处理。
上图是帧的生命周期:从传感器曝光到 SDK 交付,每帧都带着同一个时钟的时间戳。你要信任的永远是时间戳,而不是"第几帧"。
三个核心模式:采集、对齐、处理
这节把真实项目里的数据链路拆成三步,每步给一段带注释的短代码,外加一句"为什么这样写"。
采集:所有流进同一个 pipeline
rs2::pipeline pipe; rs2::config cfg; cfg.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30); cfg.enable_stream(RS2_STREAM_ACCEL); // ← 坑:运动流不写分辨率,设备自定 cfg.enable_stream(RS2_STREAM_GYRO); pipe.start(cfg); auto batch = pipe.wait_for_frames(); if (rs2::motion_frame motion = batch.first_or_default(RS2_STREAM_ACCEL)) { rs2_vector sample = motion.get_motion_data(); // x/y/z,单位 m/s² auto t_ms = motion.get_timestamp(); // 毫秒级硬件时钟 }为什么这样写:所有流走同一个 pipeline,SDK 按硬件时钟分组,你不用自己写同步;IMU 这种"来一条算一条"的流,正确姿势就是 first_or_default 取最近一条,而不是指望每批都有。
对齐:把彩色投进深度坐标系
RGB 和深度是两个镜头,光心位置不同,同一个物体在两幅图里的像素位置不一样。要在点云上贴颜色、或做像素级的颜色-深度比对,得先把一个投到另一个的坐标系里:
rs2::align to_depth(RS2_STREAM_DEPTH); // ← 注意:参数是目标流,这里表示"投到深度坐标系" auto aligned = to_depth.process(batch); auto color_on_depth = aligned.get_color_frame(); // 此后 color_on_depth 与 depth 的同一像素对应同一点为什么这样写:对齐是合成操作,产出的是新帧,原 RGB 帧不动;而且对齐帧里存在无效像素(遮挡导致原视野没拍到),后续处理必须重新判有效性。
处理:滤镜链加一点云
原始深度有噪点、有洞。SDK 内置四个标准滤镜,推荐顺序是空间在前、时间随后、填洞收尾:
rs2::spatial_filter smooth; // 保边空间平滑 rs2::temporal_filter persist; // 时间滤波,压帧间抖动 rs2::hole_filling_filter patch; // 空洞填充 smooth.set_option(RS2_OPTION_FILTER_SMOOTH_ALPHA, 0.5f); persist.set_option(RS2_OPTION_FILTER_SMOOTH_ALPHA, 0.4f); auto clean = patch.process(persist.process(smooth.process(raw_depth)));为什么这样写:时间滤波放前面会把抖动"记住"再抹,空间滤波放最后会把已填的洞再抹糊。滤镜链跑完就能出点云:
rs2::pointcloud cloud; cloud.map_to(color_on_depth); // ← 注意:纹理取彩色、几何取深度 auto pts = cloud.calculate(clean);让帧率和质量达标
这节只碰三个最影响体验的点:深度噪声、近距质量、主循环卡顿。每个都是"现象 → 原因 → 改法"。
深度图发花、边缘抖动。现象:物体轮廓每帧跳几个像素;原因:原始深度没过滤;改法:按上一节挂上空间 + 时间 + 填洞三件套。before 是 raw 帧,after 是过完滤镜的输出,边缘立刻稳定,代价是轮廓略糊一档。
近处(2 米内)深度空洞多、数值跳。现象:测近物时成片无效点;原因:默认视觉预设为远距离稳定优先;改法:开高级模式换高精度预设:
rs400::advanced_mode am(device); am.toggle_advanced_mode(true); // ← 坑:必须先 toggle 再改参数 auto d_sensor = device.first<rs2::sensor>(RS2_SENSOR_DEPTH); d_sensor.get_option(RS2_OPTION_VISUAL_PRESET) .set(RS2_RS400_VISUAL_PRESET_HIGH_ACCURACY);before 是默认预设下近处稀疏的深度图,after 是切到 HIGH_ACCURACY 后同一场景的连续深度图;代价是最大测距略缩,室内场景无所谓。
主循环卡顿、窗口一顿一顿。现象:主线程里一边 wait 一边重算,来帧时界面冻住;原因:轮询和重计算挤在同一线程;改法:切回调模式,让 SDK 的传感器线程推数据:
rs2::pipeline pipe; pipe.start([](const rs2::frame& f) { // ← 注意:回调在传感器线程执行 if (auto batch = f.as<rs2::frameset>()) // 视频流以 frameset 成组到达 process(batch.get_depth_frame(), batch.get_color_frame()); else if (auto imu = f.as<rs2::motion_frame>()) // IMU 以单帧到达 ingest(imu.get_motion_data(), imu.get_timestamp()); });before 是主线程每帧阻塞等待再同步计算,after 是到达与处理彻底分离,界面线程只管绘制,回调里碰共享数据记得加锁。
按踩坑决策树排查故障
这节覆盖四个最高频故障,按"如果……那么……"往下走。
如果 rs-enumerate-devices 输出为空,那么先查权限再查线。敲lsusb | grep -i realsense:系统能看到但 SDK 看不到,是 udev 规则没生效,sudo ./scripts/setup_udev_rules.sh之后重新插拔;lsusb 也看不到,查物理连接,优先 USB 3.0 口或有源集线器,D435i 多路流同开时吃电不低。
如果设备可见但帧率远低于标称,那么瓶颈在带宽或供电。用lsusb -t确认设备挂在 SuperSpeed 口上;掉到 USB 2.0 时,720p 深度加 RGB 的组合发不满。改法:换口,或把深度降到 640x480@30。
如果 IMU 静止时角速度不为零、加速度稳不住 9.8,那么 IMU 没校准。D435i 的 IMU 出厂不带内参校准,用 SDK 自带的 rs-imu-calibration 工具(源码见 tools/rs-imu-calibration/)把内参写进设备 NVRAM,SDK 启动时自动应用,你自己的代码一行都不用改。
如果场景边缘、深色区域无效点密集,那么这是深度算法的边界行为,不是硬件坏。先用 threshold 类滤镜把超范围值剔掉,再上空间滤镜补小洞;大面积缺失就别硬填,补光源或接受它。
接着做:录制回放、对接 ROS 与推理
这节指三条最自然的下一步路。
录制回放。开发期不想每次挂相机,就用 SDK 内置的 recorder 把深度、RGB、IMU 一起存成一个 .rs 文件,之后把这个文件名直接传给 pipeline 就能回放,采集代码一行不用改,doc/record-and-playback.md 有完整操作说明。
对接 ROS。目标是 SLAM 或导航的话,主流做法是通过社区维护的 librealsense-ros 封装把 SDK 输出投进 ROS,ROS 1 和 ROS 2 都有对应版本;仓库 doc/ 目录里也有 ROS 环境下录制回放的记录可参考。
接推理。想在深度和 RGB 上做检测、分割,仓库自带 OpenVINO 推理集成示例,照 wrappers/openvino/ 目录里的 demo 换模型即可,推理结果帧与视频帧共享同一时间戳,可以塞进同一个 frameset 一起用。
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考