ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ROS2 Humble 下 RealSense T265 与 D435 多相机驱动实战指南

ROS2 Humble 下 RealSense T265 与 D435 多相机驱动实战指南 把 RealSense T265 和 D435 同时挂到 ROS2 Humble 底下跑这事儿说难不难但要是没提前做功课踩坑踩到怀疑人生。去年我做室内移动机器人的视觉导航那阵子机器人需要一套既能给自己定位、又能感知周围环境的视觉方案最后选的就是 T265 D435 这个组合T265 负责视觉惯性追踪输出 6DOF 里程计D435 负责出深度图用来避障和建图。两块相机同时插在同一个 Ubuntu 22.04 主机上在 ROS2 Humble 环境下跑通前后折腾了大概两天今天就趁热把整套流程整理出来给准备用这套组合做机器人的人当个参考。先说一下适用人群如果你是在做移动机器人、AGV 小车、四足机器人或者正在搞 VIO、SLAM、目标检测这类视觉感知方向并且打算用 ROS2 Humble RealSense 开干那这篇文章基本就是照着抄就能用的那种。里面不会绕弯子我会把硬件连接、SDK 安装、驱动配置、双相机识别、标定同步、常见问题全部摊开讲每一处关键步骤都说明白为什么这么做省得你一个个论坛去翻那些零散帖子。1. 方案思路拆解为什么要用 T265 D435 这一套组合1.1 T265 和 D435 各干各的活先得说清楚这两颗相机在系统里扮演什么角色因为很多人一开始就搞混了以为 T265 也是个深度相机或者以为 D435 能输出里程计实际上两者的分工非常明确。T265 本质上是一颗视觉惯性追踪模块它有两个鱼眼镜头和一颗内置 IMU通过视觉加惯导的融合算法直接输出相机在空间里的 6DOF 位姿也就是 x、y、z 三个平移量和 roll、pitch、yaw 三个旋转量。它不输出深度图像也不输出彩色图像它的核心价值是告诉你“我在哪里、我朝哪个方向”。D435 是一颗主动光双目深度相机通过红外投射器和红外双目传感器计算深度输出深度图、彩色图、红外图它回答的问题是“我周围的东西离我多远、长什么样”。这两个问题正好是一个移动机器人导航系统里最基础的两个需求定位和感知。放在实际机器人上T265 的数据可以直接充当里程计输入给导航栈D435 的深度图则用来做障碍物检测或者局部代价地图更新。两个相机一结合机器人既知道自己往哪儿走又知道哪儿不能走。1.2 选这套组合的核心理由和替代方案为什么不直接用带 IMU 的 D435i 一把梭这是个好问题也确实是我在实际选型时纠结过的地方。D435i 在 D435 基础上内置了一颗 IMU理论上可以做视觉惯性里程计但它的 IMU 输出需要自己写融合算法或者对接开源 VIO 方案比如 VINS-Fusion 这类框架整个链路要自己搭。而 T265 内部已经做好了融合驱动上一跑直接就有里程计话题出来省了很大一块算法工程量对于做应用层开发的团队来说这是非常关键的优势。还有一个现实原因D435i 的这个 IMU 方案在长时间运行时温漂和噪声还是需要额外处理的电机振动、机器人自身抖动都会给 IMU 数据带来干扰而 T265 因为是专门为追踪设计的模块在鲁棒性上明显更强。当然T265 现在 Intel 已经宣布停产了软件支持到 2025 年底就结束所以现在买不到全新正品只能在二手市场收或者用存货。这个情况我会在后面的章节里专门说因为它确实会影响到启动时固件、驱动的兼容性处理。如果你没买到 T265替代方案就是 D435i VINS-Fusion 自己做 VIO或者干脆上 Livox 雷达 FAST-LIO 那种激光惯性方案但成本和工程量都是另一套账了。就我实际体验来说只要还能买到 T265做室内轮式机器人导航用 T265 D435 仍然是性价比很高的一个组合。2. 环境准备硬件接线与 ROS2 Humble 基础环境2.1 硬件清单与接线注意事项硬件部分我踩过一个很大的坑就是 USB 接口带宽和供电。T265 和 D435 都是 USB3.0 设备里面跑的数据量都不小D435 一旦开启深度流加彩色流加红外流带宽占用能达到几百 MB/s。两个相机同时插的时候最好分别插在主板背后两个不同的 USB3.0 控制器上即插在直连 CPU 的 USB 接口上而不是机箱前面板那种经过延长线的口。如果插在同一颗 USB HUB 上非常容易出现数据丢帧、设备掉线甚至驱动直接崩溃的情况。我一开始图方便把两块相机都接到一个 USB3.0 拓展坞上结果 T265 的鱼眼画面老是马赛克D435 深度图时不时断流后来把拓展开拆了、直插主机背面两个口问题立刻消失。供电也是一样T265 的功耗虽然不大但劣质 USB 延长线压降明显会让设备在启动时识别异常或者运行一会就掉线。所以我的建议是能不延长就不延长必须延长的话选 50cm 以内的品牌线并且不要接 USB 拓展坞。系统方面我用的是 Ubuntu 22.04 LTSROS2 发行版是 Humble Hawksbill这也是官方长期支持的稳定版本和 Ubuntu 22.04 搭配是官方推荐方案。如果你用的是其他系统版本后面步骤里的 ROS 源配置就按对应版本来但整体思路不变。2.2 安装 librealsense SDK两块相机的底层驱动都是 librealsense SDK所以第一步是先把 SDK 装好。这里官方有两条路径一条是直接用 Intel 提供的 apt 源安装预编译包另一条是自己从源码编译。预编译包对绝大多数人来说够用而且省时间我推荐先用这种方式。先把官方 apt 源加进来这里的公钥我已经验证过多次大家可以放心用sudo mkdir -p /etc/apt/keyrings curl -sSf https://librealsense.intel.com/Debian/librealsense.pgp | sudo tee /etc/apt/keyrings/librealsense.pgp /dev/null echo deb [signed-by/etc/apt/keyrings/librealsense.pgp] https://librealsense.intel.com/Debian/apt-repo jammy main | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt update注意里面 jammy 对应的是 Ubuntu 22.04如果你是 20.04 就换 focal。随后安装 SDK 库文件和开发头文件sudo apt install librealsense2-dev librealsense2-utils librealsense2-dkms装完以后插上相机跑一下realsense-viewer如果能看到两个相机并出画面SDK 就算装好了。这一步一定不要跳过因为后面驱动编译完如果显示有问题你很难分清是 SDK 的问题还是 ROS 驱动的问题。2.3 编译安装 realsense-ros 驱动realsense-ros 就是 ROS2 的官方驱动包Humble 版本用的是 4.x 分支。源码编译的过程不复杂但有几个细节值得多说一句。我建议把驱动放在单独的工作空间里不要跟自己的业务代码混在一起。因为 realsense-ros 是老牌包依赖稳定单独放一个工作空间哪怕后面你自己的工作空间编译炸了也不影响相机出流。mkdir -p ~/realsense_ws/src cd ~/realsense_ws/src git clone https://github.com/IntelRealSense/realsense-ros.git -b ros2-development cd ~/realsense_ws rosdep install --from-paths src --ignore-src -r -y colcon build --symlink-install编译完记得 source 环境可以把下面这句加到 ~/.bashrc 里echo source ~/realsense_ws/install/setup.bash ~/.bashrc source ~/.bashrc这里有个经验值得分享编译的时候如果提示找不到 cv_bridge 之类的一堆依赖八成是 rosdep 没跑到或者你的 ROS2 环境没有 source。还有一个小坑有些老版本教程会让你装 OpenCV 的 dev 包其实现在 realsense-ros 4.x 已经不需要手动装了rosdep 会自动处理你没必要多此一举。如果你不需要在 ROS2 里用只想用 librealsense 命令行工具测试相机前面 SDK 装完其实已经可以用了。但我们要做机器人系统肯定要在 ROS 框架里跑所以驱动这步别偷懒。3. 让两台相机同时出流的完整驱动配置3.1 多相机识别最关键的一步serial_no很多人 T265 和 D435 单独拿出来都能跑一放到一起就出问题。原因很简单两块相机默认启动时都会生成 camera 开头的命名空间话题名、TF 树全部冲突最后你只管拿到一方的数据另一方的数据看着像被覆盖了实际上是因为两个进程在抢同一个命名空间。解决这个问题的核心参数就是 serial_no也就是相机序列号。realsense-viewer 里可以查到更直接的方式是用命令行工具rs-enumerate-devices输出里每个设备的 Device Info 部分有 Serial Number。拿到序列号以后分别给两个相机指定 serial_no 参数这样驱动节点就能精确锁定对应的物理设备避免错乱。我自己用的两块相机一台序列号尾号是 8371 的 T265一台尾号是 6192 的 D435。后面所有配置都以这两个序列号为例你实际部署时替换成自己的设备号即可。3.2 多相机同时启动的 launch 写法realsense-ros 官方提供了一个 rs_launch.py但默认它只处理一台相机。要同时跑两台常见做法是写一个自定义 launch 文件在同一个 launch 文件里启动两个 realsense2_camera_node 实例分别传入不同的参数。参考配置直接写成这样from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( packagerealsense2_camera, executablerealsense2_camera_node, namet265_camera, namespacet265, parameters[{ serial_no: 8371, fisheye_width: 848, fisheye_height: 800, fisheye_fps: 30.0, enable_depth: False, enable_color: False, enable_infra: False, enable_fisheye: True, enable_gyro: True, enable_accel: True, enable_pose: True, unite_imu_method: linear_interpolation, tf_prefix: t265, }], ), Node( packagerealsense2_camera, executablerealsense2_camera_node, named435_camera, namespaced435, parameters[{ serial_no: 6192, rgb_camera.enable: True, depth_module.enable: True, depth_module.depth_profile: 640x480x30, rgb_camera.profile: 640x480x30, pointcloud.enable: True, tf_prefix: d435, }], ), ])几个参数的意图我解释一下。T265 这颗相机本身不产出深度图所以 enable_depth 和 enable_color 都关掉只开 fisheye、gyro、accel、poseD435 默认只开彩色和深度pointcloud.enable 开启后会自动发布点云如果不需要点云可以关掉省一点 CPU 开销。tf_prefix 各自加上前缀后TF 树的坐标系就不会打架。注意 unite_imu_method 这个参数。它决定 IMU 的数据以什么形式输出T265 的 IMU 在驱动里如果直接发布三份独立消息后面做时间对齐很痛苦改成 linear_interpolation 合并成一份带插值对齐的 IMU 消息这个对做融合非常友好。3.3 启动后确认话题与 TF 树launch 文件写好后启动看输出ros2 launch realsense2_camera multi_camera.launch.py正常的话话题列表会同时存在两个命名空间ros2 topic list | grep -E (t265|d435)D435 那边至少要有 /d435/camera/color/image_raw、/d435/camera/depth/image_rect_rawT265 那边要有 /t265/camera/fisheye1/image_raw、/t265/camera/odom/sample 以及 /t265/camera/imu。还有一点要重点检查TF 树坐标帧是否统一。用命令生成 TF 图ros2 run tf2_tools view_frames正常时 T265 会发布 camera_odom_frame 到 camera_pose_frame 之间的关系D435 会发布 camera_link 到 camera_color_frame、camera_depth_frame 这些关系。两个设备的 frame 名称会因为 tf_prefix 区分开但如果后续要让两者数据做融合一般需要额外发布一个外参 TF让 T265 的 pose frame 和 D435 的 depth frame 建立连接。这自然引出下一部分双相机联合标定。4. 双相机联合标定与时间同步4.1 内参标定到底要不要做这个问题不少人在起步阶段搞不清楚我一开始也想当然觉得官方出厂标定就够了。实际情况得分开看。D435 出厂自带工厂标定内参保存在设备 flash 里正常情况下直接使用没问题尤其是做避障这种对精度要求不高的任务完全不需要重新标内参。但假如你的相机经历过磕碰、换过镜头或者深度图人工检查发现有明显畸变那就有必要重新做内参标定。ROS2 里有现成的 camera_calibration 包标准流程是播放棋盘格标定板画面用 rqt 界面采集角点然后计算内参。D435 标定彩色图就用颜色流数据标定深度图就用红外流数据因为深度传感器的原始画面是红外的。T265 的内参老实说不是普通用户能随便动的。它的两个鱼眼镜头和 IMU 之间的外参在出厂时就固化在设备固件里内部 viemo-slam 算法直接使用这套内参做追踪根本不通过 ROS 话题输出图像特征来重新标定。所以你拿着 T265 的图像去跑 kalibr 标内参标完了也不知道该写回哪里意义不大。我个人的结论是T265 不用碰内参标定它被设计成一款即插即用的追踪设备。4.2 相机间外参标定实操思路外参标定是这套组合里比较头疼的一步。T265 和 D435 之间的空间变换关系决定了 D435 的深度点云能否精确地转换到 T265 的里程计坐标系下。常用的做法有几种如果只是做轻量级融合可以手测一个粗略的 TF毕竟两块相机一般固定在一个小支架上相对位置是固定的常量。比如量出 T265 光心在机器人坐标系里相对 D435 光心的 x、y、z 偏移和 roll、pitch、yaw 旋转角度直接发布一个静态变换。这种做法定位精度够不够用完全看你的任务容错度。追求更高精度的话推荐用 kalibr 或者 easy_handeye 做标定。kalibr 做多相机外参标定的逻辑是通过同一次采集中多相机同步观测到一个棋盘格利用棋盘格作为公共参照物接下来解算相机之间的位姿关系。操作流程大致是用两块相机同时录制一段包含标定板运动的 bag 包标定板要保持在整个视野内自由移动然后 kalibr 读取两个相机的观测时间戳分别估计各相机对标定板的位姿最终求得相机间外参。这里我特别提醒一下时间同步的重要性。外参标定要求两路图像在时间上对齐否则标定板运动过程中产生的位置误差会被误读成外参误差。实际操作中我一般会用之前提到的 ApproximateTimeSynchronizer 把两路图像消息先做时间同步再录制 bag 包。这个方法在 ROS2 里同样适用可以极大提升标定结果的可靠性。4.3 用 ApproximateTimeSynchronizer 做时间同步T265 和 D435 是两块独立硬件各自的图像时间戳来自各自的时钟域。虽然都走 USB 总线驱动会尽量维护系统时钟但两路消息到达 ROS 节点时时间戳并不严格对齐。对于避障这种对小延时不敏感的应用直接用 D435 的单路数据没问题但你要是打算把 T265 的位姿和 D435 的深度图一起喂给像 VINS-Fusion 或者 open_vins 这类紧耦合系统时间不齐基本就崩。我的做法是写一个消息同步节点订阅两路消息使用 ApproximateTimeSynchronizer 将时间戳接近的消息绑定成一组再往下发。下面这段代码是我实际项目里用的同步逻辑from message_filters import ApproximateTimeSynchronizer, Subscriber from sensor_msgs.msg import Image import rclpy from rclpy.node import Node class SyncNode(Node): def __init__(self): super().__init__(sync_node) self.sub_t265 Subscriber(self, Image, /t265/camera/fisheye1/image_raw, qos_profile10) self.sub_d435 Subscriber(self, Image, /d435/camera/depth/image_rect_raw, qos_profile10) self.sync ApproximateTimeSynchronizer([self.sub_t265, self.sub_d435], queue_size20, slop0.05) self.sync.registerCallback(self.callback) def callback(self, t265_img, d435_img): self.get_logger().info(fgot synced pair, t265 ts{t265_img.header.stamp}, d435 ts{d435_img.header.stamp}) def main(): rclpy.init() node SyncNode() rclpy.spin(node) rclpy.shutdown()slop 参数设成 0.05 秒也就是时间戳差距在 50ms 以内的两帧会凑成一对这个值对 30fps 的图像流来说是合理区间。再强调一次这个同步节点不是必须的如果你做的只是独立读取 T265 里程计和 D435 深度图那就没必要加这一层省一点中间链路复杂度。5. 实测运行与数据可视化5.1 Rviz2 里同时看图像和点云启动 launch 之后打开 Rviz2这是最直观确认系统是否正常工作的一步。我通常先把全局坐标系设定成 odom然后在 Display 面板添加 Image 视图分别选择 T265 的鱼眼图像和 D435 的彩色、深度图像。D435 的点云显示稍微特殊一点。由于 T265 和 D435 之间的 TF 还没接上Rviz2 里如果直接把点云话题加进去会出现点云和图像对视到不同坐标系里的情况。这时候可以临时在 Rviz2 里添加一个静态 TF 发布器或者先手动发布一条静态变换把两个相机的坐标系大致对齐仅用于可视化验证。后面外参标定完正式发布外参 TF 之后就不需要这个临时步骤了。用命令行发布静态变换的示例ros2 run tf2_ros static_transform_publisher 0.12 0 0.02 0 0 0 map d435_camera_link这里的数值只是示例实际请你根据自己设备的安装位置填写。注意静态变换的父子坐标关系不要搞反否则所有后续数据都会镜像到错误位置。5.2 用 ros2 bag 录制并验证轨迹启动正常、可视化正常还不够最好录一段真实数据验证整体系统的数据完整性。ROS2 的 bag 录制命令使用简单ros2 bag record -o nav_test /t265/camera/odom/sample /t265/camera/imu /d435/camera/depth/image_rect_raw /d435/camera/color/image_raw录一小段机器人移动的数据然后回放确认 T265 的里程计话题有没有连续输出、D435 的深度图有没有断帧。我通常还会用下面这个命令看话题频率ros2 topic hz /t265/camera/odom/sample ros2 topic hz /d435/camera/depth/image_rect_rawT265 的 odom 输出频率理论上是 200Hz但实际上受驱动影响一般在 60Hz 到 200Hz 之间波动都正常D435 的深度流按配置的 30fps 输出稳定在 30Hz 附近就是健康状态。5.3 对接导航栈和 VIO 系统的落地方案数据验证完接上层应用就比较顺了。如果目标是用这套数据做 nav2 导航核心是把 T265 的里程计数据转成 nav2 能识别的 odom 话题。T265 输出的 /t265/camera/odom/sample 是 nav_msgs/Odometry 类型一般都够用但它的 frame_id 命名比较特殊大多数团队会做一个中继节点把它重新发布成 /odom 和 /odom_tf同时把机器人底盘的 base_link 和 D435 的坐标关系标定好。如果目标是跑 VINS-Fusion 做紧耦合 VIO那就直接用 T265 的鱼眼图像和 IMU 作为输入D435 的深度图可以作为稠密深度约束加入。这条路我之前试过效果还可以但需要注意 VINS-Fusion 默认会启动它自己的特征点处理流程对计算资源的占用比单纯跑 T265 自带轨迹要高不少工控机上跑的时候得做好降帧策略。5.4 性能与稳定性笔记说到资源占用我顺手分享一下实测的体验。在一台 i5-12450H 的迷你主机上T265 和 D435 全部 30fps 出流 D435 点云开启CPU 占用大概在 25% 到 35% 之间内存占用约 2GB。如果把 D435 的分辨率从 640x480 提到 1280x720CPU 占用会明显上升点云数据更是占带宽建议一般场景就用 640x480。稳定性方面这套组合连续跑 24 小时没有掉线但前提是 USB 接口供电没问题、系统没有触发 USB 自动挂起。Linux 下要检查两个设置一个是 BIOS 里的 USB 电源管理另一个是下面的 udev 规则防止系统把相机休眠掉。可以在 /etc/udev/rules.d/ 下加一条echo ACTIONadd, SUBSYSTEMusb, ATTR{power/control}on | sudo tee /etc/udev/rules.d/99-usb-power.rules sudo udevadm control --reload-rules sudo udevadm trigger这个操作主要是关掉 USB 设备的自动电源管理防止长时间运行中相机被系统挂起。6. 常见问题与排查技巧实录6.1 驱动崩溃、设备无法识别这块遇到得最多。现象是启动 launch 时报 device not found或者启动到一半节点崩溃、日志无输出。排查思路从硬件往软件走。先用 realsense-viewer 看宿主系统能否直接识别这两颗相机。如果 SDK 都认不到问题多半在硬件换一个 USB 口、换一根短一点的 USB3.0 线、确认主板供电稳定。如果 SDK 能认到但 ROS 驱动跑不起来大概率是 serial_no 对不上或者之前有残留进程占用了设备。解决办法是把相机拔掉重插然后在启动前执行pkill -f realsense2_camera_node sudo udevadm control --reload-rulesT265 比较特殊的一点是它需要一定时间初始化内部 SLAM 模块插上后立刻启动驱动偶尔会报设备忙。这种情况等两三秒再启动就正常了。6.2 图像卡顿、深度图断流前面提过 USB 带宽是首因。除此之外还有一个常见坑是 D435 的彩色流和深度流帧率不匹配。比如深度流设 30fps彩色流也被驱动强制设成 30fps但 RealSense 相机模组在某些分辨率下无法同时满足这两个帧率会出现深度图周期性抽风。解决办法是统一把彩色流和深度流的分辨率与帧率都设置一致或者在 launch 里把彩色流的自动曝光关掉调成手动减少带宽波动。如果是高负载导致丢帧还可以打开驱动里的 infra 流注意通过红外流排查一下是不是投射器失效因为 D435 的深度是主动光投射器坏掉以后纯靠环境光也能出深度但室内暗光下会频繁断帧。6.3 IMU 话题不出数据T265 和 D435i 都有这种情况T265 还好D435 不带 IMU 就别指望了。出现这个话题为空先检查 launch 参数里 enable_gyro 和 enable_accel 是否都设成了 true。如果你的设备是 D435i 而不是 D435还需要确认 unite_imu_method 有没有设置某些版本驱动默认不发布合并后的 IMU 话题需要这个参数才能出完整数据。再一个容易被忽略的是消息类型grep 话题列表的时候别只看 /imu可能会被发布成 /imu/data_raw 或者 /t265/camera/imu。用 ros2 topic list | grep imu 看一眼全部结果再说。6.4 时间戳乱跳、定位不稳定这个现象出现时首先要怀疑 USB 总线时序受到干扰。T265 对时序非常敏感前面提到过的 USB 延长线、劣质 HUB都会导致它内部传感器时间不同步最终表现出来的就是 odom 输出跳变、数据不流畅。其次检查系统是否开启了 CPU 频率调节。T265 的 VIO 算法是计算密集型任务如果 CPU 频率被省电策略限制到很低会导致算法跟不上传感器帧率时间戳就会用旧的。解决方法是装 cpufrequtils 把 CPU 调成 performance 模式sudo apt install cpufrequtils echo GOVERNORperformance | sudo tee /etc/default/cpufrequtils sudo systemctl restart cpufrequtils别忘了 T265 的停产事实。设备固件停在某个版本后官方不再提供新版本适配如果你拿到的是二手设备我建议先升级一次固件到 Intel 官方最后提供的稳定版本再跑 ROS 驱动。不升级的话个别情况下会存在 IMU 帧率异常、追踪精度下降的已知问题。如果以上步骤都做对了T265 D435 在 ROS2 Humble 下就能长期稳定运行了。这套东西说穿了并不是什么高深技术难就难在把 SDK、驱动、标定、同步这些环节串起来每一步多花几分钟做对后面实现整套导航方案时就能省下大量的调试时间。
返回列表