ARTICLE DETAIL

资讯详情

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

ROS 2 Humble下T265与D435双相机实战:VIO里程计与深度感知方案

ROS 2 Humble下T265与D435双相机实战:VIO里程计与深度感知方案 1. “T265明明停产了为什么我还在ROS 2 Humble里用它”1.1 旧设备的尴尬处境停产、固件冻结与librealsense的兼容性收缩先说个很多人不愿意面对的事实Intel RealSense T265在2022年就宣布EOL了官方固件停在5.12.3后续不再有任何维护。D435虽然还在产但同一个librealsense版本要同时兼容这两款定位完全不同的设备在ROS 2 Humble时代已经变得相当拧巴。我接触到的不少项目里大家还是咬着牙用T265。原因很实际它是少数具备完整VIO视觉惯性里程计能力的现成模组内部集成了两颗鱼眼跟踪相机和一颗IMU通电就能输出camera/odom/sample主题。对于不想自己调VINS、不想折腾标定的团队T265就是拿来即用的定位源。D435则负责深度感知、RGB语义识别两者一组合视觉导航的基础架构就齐了。问题是librealsense从2.54开始对T265的支持有回退某些版本在T265上会直接崩溃或者根本枚举不到设备。这意味着你不能无脑apt install最新版必须学会“锁定版本”。这篇文章就是把我从Ubuntu 22.04 ROS 2 Humble实战里趟出来的兼容方案、启动参数、TF设计和排坑过程完整记录下来给还在用这对组合的兄弟们省点时间。1.2 D435作为“眼睛”、T265作为“防漂移锚点”的分工逻辑为什么不是只用D435配IMU做视觉里程计坦白说D435的IMU精度和T265不在一个量级而且D435的视觉定位需要依赖AprilTag、ArUco或者外部特征。T265自带VIO融合它的计程计在室内慢速场景下能维持不错的稳定性即便在纯纹理稀疏的走廊它的鱼眼视角也比普通RGB相机更能抓住边缘特征。所以我的分工是这样的T265跑体感定位输出odom和pose作为机器人的位置基准D435输出深度图、点云和RGB给导航、避障和物体识别用。两个相机的数据在TF层面打通就能让move_base、Nav2或者自己的运动规划节点拿到“我在哪、我前面有什么”这两个核心信息。这种方案的另一个好处是解耦如果T265哪天真坏了你可以换一个带IMU的D435i只改launch和标定参数上层规划逻辑完全不用动。2. Ubuntu 22.04 下的驱动安装与版本锁定2.1 第一步永远是检查USB控制器与内核版本很多人在驱动阶段就卡住不是因为librealsense编不过而是因为T265对USB控制器的兼容性很差。我遇到过插在USB 3.0扩展卡上完全枚举不到换到主板原生USB 3.1接口就好了的情况。所以别急着编译先把环境摸清楚lsusb lsusb -t dmesg | grep usb确认系统内核版本uname -a我这边稳定运行的内核是6.2.0-26-generic和5.15.0系列T265和D435都能正常识别。某些6.5以上的新内核我试过D435没问题T265偶尔会出现设备掉线具体原因我后面在排坑部分展开。设备识别正常后最好先跑一次官方端到端自检工具确认固件和传感器状态rs-enumerate-devices如果rs-enumerate-devices能列出T265和D435驱动这关就过了大半。2.2 librealsense 2.55才是T265在Humble下能安心用的大版本这是全文最重要的一个结论librealsense请锁定2.55.1不要装2.56以上也不要装最新的2.5x分支。T265固件冻结后Intel虽然在较新版本里保留了基础支持但部分传感器初始化逻辑变了实测中2.56.0会让T265在启动realsense2_camera节点十几秒后自动断连CPU占用还飙到200%。2.55.1是最后一版我认为对T265足够友好的librealsense。编译安装的过程如下sudo apt-get update sudo apt-get install -y \ git cmake build-essential pkg-config \ libusb-1.0-0-dev libglfw3-dev libgtk-3-dev \ libssl-dev libudev-dev libgl1-mesa-dev libglu1-mesa-dev git clone https://github.com/IntelRealSense/librealsense.git -b v2.55.1 --depth1 cd librealsense # 安装Udev规则并编译内核模块补丁可选 ./scripts/setup_udev_rules.sh mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease \ -DBUILD_EXAMPLEStrue \ -DBUILD_GRAPHICAL_EXAMPLEStrue \ -DFORCE_RSUSB_BACKENDtrue make -j$(nproc) sudo make install这里我特意加上了-DFORCE_RSUSB_BACKENDtrue。对T265来说用RSUSB后端比V4L2后端更稳它在内核模块冲突和USB带宽调度上问题更少。如果你的环境里没有其他独占摄像头的应用这个选项能显著降低掉线概率。如果你用的是树莓派或者ARM平台建议再加一个-DCMAKE_CXX_FLAGS-marcharmv8-acrc之类的编译参数否则某些浮点指令在ARM上会崩具体看你的板子指令集。2.3 realsense-ros的三种安装方式及我的选择realsense-ros在ROS 2下的安装方式有三条路apt直接安装二进制包sudo apt install ros-humble-realsense2-camera。简单但有时apt源里的librealsense版本较新可能又把T265坑了。源码编译realsense-ros并让它链接系统里已有的librealsense 2.55.1。这是我最推荐的方式。直接用Docker镜像隔离环境但摄像头USB设备穿透后实时性多少打点折扣做导航还是算了。源码编译的命令mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src git clone https://github.com/IntelRealSense/realsense-ros.git -b ros2-development cd ~/ros2_ws rosdep install --from-paths src --ignore-src -r -y colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPERelease关键一步在rosdep安装依赖时它可能尝试给你升级librealsense2。如果你确认系统里已经装好了2.55.1可以这样跳过对librealsense2的拉取sudo apt-mark hold librealsense2 librealsense2-dev librealsense2-utils rosdep install --from-paths src --ignore-src -r -yapt-mark hold之后不管rosdep还是apt upgrade都不会动你的librealsense版本了。我这里用的是Humble官方支持的ros2-development分支T265的驱动标识符realsense2_camera它仍然保留。3. 让T265在Humble下稳定输出里程计3.1 realsense2_camera启动参数与话题结构启动T265的launch文件我简化成了这样一个命令ros2 launch realsense2_camera rs_launch.py \ camera_namespace:t265 \ camera_name:t265 \ enable_gyro:true \ enable_accel:true \ enable_fisheye1:true \ enable_fisheye2:true \ enable_color:false \ enable_depth:false \ unite_imu_method:linear_interpolation \ initial_reset:true \ odom_frame_name:odom \ pose_frame_name:pose \ base_frame_name:t265_link注意参数之间的关系。T265没有深度流和RGB流所以enable_color:false和enable_depth:false能减少不必要的资源占用。enable_fisheye1和enable_fisheye2是T265的两颗鱼眼相机必须都开否则VIO数据就不完整。unite_imu_method:linear_interpolation的意义是把IMU的角速度和线性加速度在时间轴上线性插值合成一个更平滑的/t265/camera/imu话题。原生的IMU数据频率高但噪声大融合到视觉里程计里反而更容易漂插值之后整体稳定性好很多。启动之后用ros2 topic list看看/t265/camera/imu /t265/camera/odom/sample /t265/camera/pose/sample /t265/camera/fisheye1/image_raw /t265/camera/fisheye2/image_raw/t265/camera/odom/sample是用四元数表示的T265自身VIO里程计输出/t265/camera/pose/sample是6DOF位姿你可以从后者直接取pose.pose.position和pose.pose.orientation作为机器人的全局位姿。3.2 T265的VIO漂移调整从滤波器到话题频率不要以为T265内置VIO就可以完全不用调。它没有外部视觉回环时长时间行走累计误差依然会“飘”只是飘的速度比纯IMU慢得多。我在使用中总结出三个实用调节手段第一降低IMU数据噪声。虽然unite_imu_method:linear_interpolation已经做了一层插值但如果你觉得里程计抖动大可以在上游加一个imu_filter_madgwick滤波器ros2 run imu_filter_madgwick imu_filter_madgwick_node \ --ros-args \ -p imu_topic:/t265/camera/imu \ -p output_topic:/t265/camera/imu/filtered \ -p use_mag:false当然加了滤波会增加额外延迟实测在室内慢速场景下大约增加10~20ms基本无感。快动态场景下不建议加反而会模糊特征。第二主动重置VIO。T265长时间运行后如果发现里程计开始缓慢旋转或者平移漂移可以让它重新初始化视觉里程计。最简单的办法是重启realsense2_camera节点不想重启节点的话可以在代码里通过服务调用触发重置ros2 service call /t265/camera/reconnect realsense2_camera_msgs/srv/DeviceInfo这个服务不是真正意义上的VIO重置但能恢复部分软异常。真正百分百重置视觉里程计的做法还是重启节点或者用上层逻辑在检测到漂移时把机器人停一下让T265重新稳定。第三控制发布频率。T265的odom发布频率默认在200Hz左右这对导航来说太高了。Nav2或者EKF节点的时序窗口容易错乱。建议在节点内部设置odom_frequency:50.0或者在下游滤波时把频率降到50Hz。实测50Hz对室内轮式机器人的控制环路完全够用EKF协方差的收敛效果也更理想。4. D435深度流的配置与校验4.1 深度对齐、点云和“三流同步”的配置D435这边我在同一个realsense2_camera里启动但namespace分开避免话题冲突。启动命令如下ros2 launch realsense2_camera rs_launch.py \ camera_namespace:d435 \ camera_name:d435 \ enable_color:true \ enable_depth:true \ enable_gyro:false \ enable_accel:false \ depth_width:640 \ depth_height:480 \ depth_fps:30 \ color_width:640 \ color_height:480 \ color_fps:30 \ aligned_depth.enabled:true \ pointcloud.enabled:true \ pointcloud.ordered_pc:true \ initial_reset:true这里aligned_depth.enabled:true的作用是让深度图对齐到RGB坐标系这样深度图像素和RGB像素一一对应后面做YOLO检测加深度测距就不用手动对齐坐标系了。pointcloud.enabled:true会直接发布/d435/camera/depth/color/points点云话题免去自己拼PCL的麻烦。如果你是D435i带IMU版本可以打开enable_gyro:true、enable_accel:true把IMU数据当成VIO的辅助输入。但如果你只是用D435做感知不参与视觉里程计我建议关掉IMU减少USB带宽和CPU占用。4.2 判断D435数据是否正常的三个小技巧很多同学启动之后只看/d435/camera/color/image_raw有没有图像其实不够。我建议用三个手段做基本校验第一个检查点云是否连续。用rviz2订阅/d435/camera/depth/color/points把Fixed Frame设置成d435_link如果点云在地面和墙壁处是连续的面状结构说明深度有效。如果看到大量飞点或者横向条纹可能是摄像头出厂标定偏了重新跑一遍rs-depth-quality工具看下的评分。第二个验证深度对齐精度。在rviz2里同时显示RGB图像和颜色映射过后的深度图像把手掌放到相机前50cm处如果深度图上手掌轮廓和RGB图重合度好没有大范围错位说明对齐和标定都没问题。如果错开一个手掌宽度多半是depth_width和color_width设置了不同分辨率要对齐的话必须保持二者一致或者对齐分辨率选小的那个。第三个检查时间戳同步。ros2 topic echo --once /d435/camera/color/image_raw可以看header.stamp对比/d435/camera/depth/image_rect_meters的时间戳。两者时间差应该在数据帧间隔之内比如30fps就是33ms以内。如果时间差经常达到几百毫秒说明USB带宽争抢严重需要降低某个传感器的帧率常见组合是深度降到30fps同时把RGB降到15fps。5. 双相机协同TF树、外参标定和嵌套启动5.1 手写TF树base_link背后的坐标系设计T265和D435各自会发布自己的坐标系变换但它们的TF树是互相独立的。T265发布odom → t265_linkD435发布d435_link → d435_depth_optical_frame这类变换这台机器人的base_link和这两个相机之间的变换关系必须你自己补。一般来说我习惯的TF树结构是odom - t265_link (由T265里程计提供) t265_link - base_link (静态变换标定T265在机器人本体上的安装位姿) base_link - d435_link (静态变换标定D435在机器人本体上的安装位姿) d435_link - d435_depth_optical_frame / d435_color_optical_frame (来自相机驱动)对应的静态变换发布可以这样写ros2 run tf2_ros static_transform_publisher \ 0.10 0.0 0.05 0.0 0.0 0.0 \ base_link d435_link ros2 run tf2_ros static_transform_publisher \ -0.05 0.0 0.08 0.0 0.0 0.0 \ base_link t265_link这两条命令只是示例实际数值必须根据你的机械结构和安装孔位量出来。别想当然地用默认值TF错了上层的一切感知和定位都是白搭。如果你不知道该给每个相机多少旋转量可以先量出平移量旋转量用ros2 run tf2_ros static_transform_publisher --roll 0 --pitch 0 --yaw 0也可以但大多数机器人的相机都是水平向前安装yaw角需要微调。微调方法下一篇我再细聊这里只提醒一句相机光轴和机器人前进方向之间的偏差最好控制在1度以内。5.2 镜头间外参标定的实操方案kalibr imu_utilsT265和D435之间的外参即t265_link与d435_link之间的旋转和平移如果厂家安装位置精度不高、或者你想做视觉惯性融合就该用标定工具拿到更准的外参。我的标定方案是kalibr在Ubuntu 22.04上自己编译即可并不复杂。流程是这样的把T265和D435固定在同一块刚性板上确保两者相对位置在标定过程中不变化。打印一张AprilGrid标定板尺寸建议A2以上格子数量8x8打印后贴在绝对平整的硬纸板上。同时采集T265的鱼眼图和D435的彩色图。采集脚本可以用ROS 2的ros2 bag record分别记录主题。用kalibr对T265鱼眼相机做内参标定对D435做内参标定。最后跑kalibr_calibrate_cameras用双目联合标定得到T_t265_leftcam_d435_color之类的外参。注意我真实验过的结果是T265的鱼眼相机内参和D435的RGB内参标定误差很容易做到0.1像素以内但如果你直接拿两个相机各自输出图像去标定T265的鱼眼畸变太厉害需要足够多的外圈样本来稳住畸变系数。所以采集时一定要包含大量把标定板放在图像边缘的画面。如果你还打算做视觉惯性融合建议再用imu_utils标定D435i的IMU内参随机游走、噪声密度这一步骤输出的imu.yaml在VINS-Fusion里是必需的。5.3 用launch文件把两个相机一次性拉起来手动在两个终端分别启动两个相机节点调试阶段没问题但做导航任务时最好用一个launch一次性拉起。我自己用的launch文件核心结构如下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[{ enable_fisheye1: True, enable_fisheye2: True, enable_color: False, enable_depth: False, unite_imu_method: linear_interpolation, odom_frame_name: odom, pose_frame_name: pose, base_frame_name: t265_link, initial_reset: True, }], ), Node( packagerealsense2_camera, executablerealsense2_camera_node, named435_camera, namespaced435, parameters[{ enable_color: True, enable_depth: True, depth_width: 640, depth_height: 480, color_width: 640, color_height: 480, aligned_depth.enabled: True, pointcloud.enabled: True, pointcloud.ordered_pc: True, initial_reset: True, }], ), Node( packagetf2_ros, executablestatic_transform_publisher, arguments[0.10, 0.0, 0.05, 0.0, 0.0, 0.0, base_link, d435_link], ), Node( packagetf2_ros, executablestatic_transform_publisher, arguments[-0.05, 0.0, 0.08, 0.0, 0.0, 0.0, base_link, t265_link], ), ])写好后放src/your_bot/launch/realsense_dual.launch.pycolcon build过后就能一键启动。有个细节两个节点都开了initial_reset:true这个参数会在启动时向硬件发送一次热复位避免上一次异常关闭导致的设备锁死。代价是每次启动多了几秒初始化时间能接受。6. 融合落地VINS-Fusion与D435/T265的两种接法6.1 方案A直接用T265自带里程计D435只做感知如果只是想快速跑通一个导航实验不要过度工程化。T265自带VIO已经能输出odom和poseD435只需要出深度和点云。两条数据流各走各的Nav2或move_base订阅T265的odom做定位D435的点云用来做costmap障碍物检测。这个方案下你需要做的是在Nav2参数里把odom_topic设置成/t265/camera/odom/sample。robot_localization的EKF配置里把odom0指向T265的odompose0指向T265的pose。costmap的observation sources里填/d435/camera/depth/color/points。实测中这样的组合在室内走廊场景能稳定运行导航精度受限于T265的VIO漂移速度。大约在100米直线行走后累计误差可能到0.5米左右。如果导航任务范围不超过50米半径这个方案完全够用。6.2 方案BVINS-Fusion融合双目IMU的高级玩法如果对定位精度要求更高或者T265的VIO在动态光照下经常失效那就得把数据交给VINS-Fusion让它自己融合双目、IMU和外部里程计。这里有个关键点VINS-Fusion原本的工作流是“单目IMU”或者“双目IMU”把T265的视觉数据和D435的深度数据塞进去需要做一些话题适配。我采用的是双路IMU 双视觉的变体配置T265的鱼眼图作为VINS-Fusion的cam0和cam1输入双子目对。D435i的IMU作为IMU输入或者T265的IMU也行但D435i的IMU标定数据更完整。如果你不想让VINS-Fusion直接用D435的深度可以在VINS后处理阶段把深度图转成稠密点云做局部地图。配置文件的核心改动大致是这样的imu_topic: /d435/camera/imu image0_topic: /t265/camera/fisheye1/image_raw image1_topic: /t265/camera/fisheye2/image_raw max_imu_accumulate_time: 0.02 max_cnt: 180 min_dist: 25实测这套配置在室内跟踪时VINS-Fusion的定位精度比T265自带VIO提升约20%左右尤其是快速旋转时几乎不丢。代价是CPU占用明显高Jetson Orin NX上还能跑低端工控机就吃力了。6.3 实机测试的一条建议路线不要一上来就在复杂环境里跑融合。我通常按下面这个顺序静止放置5分钟观察IMU零偏是否稳定T265的odom零漂是否小于1cm/s。手动推着机器人做缓慢的8字运动录制一份bag回放时检查VINS-Fusion的轨迹是否平滑、有没有瞬跳。做一次包含原地旋转、前进、后退的标定动作确认T265的pose和VINS-Fusion的pose差在可接受范围内。最后才接入Nav2或move_base跑真实避障和巡航。这个流程很笨但能帮你把定位、标定、TF、导航的问题逐个隔离出来。如果直接全系统联调出了任何问题都不知道该查哪一层。7. 实机排坑我在Humble下遇到的五个典型问题7.1 T265时而识别时而不识别表现每次重启电脑后lsusb能看到T265但realsense2_camera启动失败偶尔要重新插拔才恢复。原因T265对USB控制器信号质量敏感尤其是非原生USB 3.0控制器。我遇到过Intel主板上“ASMedia”扩展卡上的T265几乎每次冷启动都会出问题换到主机背板的原生USB接口后好了。另外一个容易被忽略的坑是USB带宽。如果你同时接了D435和T265两个相机都跑在USB 3.0上带宽不足时T265经常掉。解决方案是D435接USB 3.1 Gen1口T265接USB 3.1 Gen2口或者干脆一个接前面板一个接后面板。7.2 装新版librealsense后T265启动即崩表现升级librealsense到2.56以上后realsense2_camera节点启动时打印FreeContext trigger retry...然后节点退出。原因新版本librealsense对T265的设备初始化流程做了调整T265的固件停更后无法适配新的初始化时序。解决办法就是回到2.55.1并且apt-mark hold。如果你在编译realsense-ros时链接的是系统librealsense记得把平时的CMAKE_PREFIX_PATH指到自定义安装路径或者直接用apt装的开发包版本对齐到2.55.1。7.3 TF坐标系跳变表现在rviz2里打开TF后odom → t265_link的变换每隔一段时间跳一次导致所有感知数据跟着跳。原因多数时候是T265自身VIO的漂移修正引起的。T265内部有个“auto reset”机制当它检测到视觉里程计误差过大时会自己重置轨迹体现在外部就是坐标系的瞬跳。解决思路有两个方向一是接受它的存在在导航逻辑里设置“位移连续性和角度连续性”校验当发现位姿突变超过阈值时暂时冻结cmd_vel输出等T265稳定后再运动二是彻底放弃T265自带VIO改用VINS-Fusion做自采里程计从源头避免这种内部重置。7.4 深度图全黑表现D435的depth图像话题有数据但图像全黑reward和rviz2里什么都看不到。原因多半是enable_device没有打开或深度传感器在自检时失败了。我之前遇到过D435固件版本太老在Ubuntu 22.04上深度流只输出0值。执行一次固件升级rs-fw-update -f /path/to/Signed_Image_5.13.0.50.bin另外如果某次异常断电导致D435内部状态错乱initial_reset:true能解决大多数问题。7.5 里程计与视觉的延迟不同步表现在rviz2里D435点云和T265的odom对应的机器人位姿总是相差几十个厘米但单独看每路数据都正常。原因两个节点各自使用自己的时间参考同一时刻采集到的数据在实际时间轴上对不齐。典型的出错点是realsense2_camera节点使用传感器硬件时间戳而VIO或EKF使用系统时间戳。如果你发现时间差普遍达到几百毫秒先检查是否有多个clock话题或者节点间没设置use_sim_time。解决方法是把统一时间戳的阀门放在数据入口处ros2 run tf2_ros buffer_server python3 your_time_sync_node.py简单来说做一个“时间对齐器”缓存当前时刻前后一个帧周期内的所有传感器数据等齐了再一起发布给下游。不要信那些“30帧就去算30帧同时到达”的假设实际调度时差远比你想象的大。这套组合的调试说难不算难但每一步都藏着“版本”“时间”“坐标系”这三个大坑。希望这次的装机记录和排坑经验能让你少走我走过的弯路。
返回列表