☰
TX2上集成L515深度相机与YOLO:ROS实时检测与测距实践
2026/10/15 17:49:09 网站建设 项目流程

TX2上第一次把L515的深度点云和YOLO的检测框凑在一个画面里时,我心里其实没什么喜悦,只有一种“这次总该稳了吧”的忐忑。上一篇总结写到了SDK能枚举设备、能出深度图,但要让Realsense L515作为感知前端,在ROS里稳定供图,再让YOLO实时把检测框叠上去,中间还隔着一大堆版本、资源和时序的坑。这一篇干脆写成第二篇,专门记从“驱动能跑”到“ROS能调、YOLO能出框”这一段,给同踩TX2这块板子的朋友做个参照。

先说明一下我这边的目标场景:一个移动机器人项目,需要把L515的彩色图喂给YOLO做人/物检测,同时从对齐后的深度图取回目标中心距离。TX2是整机算力最紧张的一环,不能像PC一样无脑开高分辨率、高帧率、点云全开。所以这篇文章的很多结论其实是围绕“在有限算力下把相机和检测模型串起来”这个诉求展开的。如果你是在X86台式机上玩,部分坑不会遇到,但整体思路仍然通用。

1. 为什么第二篇还要先确认平台状态:TX2上的相机驱动坑跟PC不一样

1.1 别急着装驱动,先把JetPack和内核这层底子弄清楚

在Ubuntu PC上装L515的驱动,基本就是加个仓库、apt安装、插上设备就能跑,最多用一下官方工具升级固件。但在TX2上完全不是这么回事。TX2的Ubuntu系统通常不是自己手动装的,而是刷机时由JetPack带过来的,内核版本、CUDA版本、OpenCV版本、Python版本之间是绑定关系。很多人装驱动失败,不是librealsense编译报错,而是底层环境和库的版本冲突。

以我踩到的第一个问题为例:TX2刷机后默认内核里不一定带了Realsense需要的UVC模块和HID传感器模块,或者带了但版本不匹配。L515作为激光雷达型深度相机,驱动机制和普通RGB摄像头不完全一样,它除了图像传输还依赖一组传感器元数据通道。内核里的相关驱动模块没加载时,rs-enumerate-devices能列出一个设备,但深度流一启动就报错,或者彩色图正常、深度图黑屏。这时候你第一反应会以为是相机坏了,其实是内核模块的问题。

所以,第二篇我第一个建议是:先把平台版本列成一张表,明确自己的起点。我在动手之前用几条命令确认了系统版本、内核版本、CUDA版本和ROS版本,再对照SDK的编译要求。这一步能省掉后面至少半天的排查时间。

1.2 我用三条命令确认环境,避免白折腾

拿到板子先跑下面的命令,把信息记下来:

# 看系统版本和内核 lsb_release -a uname -a # 看CUDA运行时版本(如果已经装好) nvcc -V # 看ROS主版本 rosversion -d

我在旧版JetPack环境上遇到的情况是:系统是Ubuntu 18.04,内核版本偏老,ROS是Melodic,CUDA 10.2。这套组合本身没问题,但它会直接影响后面选择librealsense的哪个版本。这里的经验是:新版本的librealsense往往要求更新一些的内核头文件或依赖库,在TX2这种嵌入式平台上,无脑拉最新版反而容易编译不过。你会看到一堆“找不到linux/xxx.h”或者“usb相关类型未定义”的报错,本质都是内核头文件和SDK版本不匹配。

另外要注意的是,TX2上的ROS发行版基本限定在老版本,别想着装ROS 2的高版本。如果项目里其它模块还在用ROS 1 Melodic,那realsense-ros也优先选和librealsense版本匹配的旧分支,而不是latest。我吃过这个亏:第一次直接clone了realsense-ros的master分支,结果编译时要求更高版本的librealsense,跟TX2上已经装好的库产生了ABI冲突,最后只能全部卸掉重来。

1.3 硬件连接顺序也有讲究:USB3.0不是插上就行

TX2的USB口质量没什么问题,但供电和线材会比PC敏感。L515刚接上时,如果USB枚举走了USB 2.0,相机虽然能被识别,深度流会非常不稳定。怎么判断?插好相机后执行:

lsusb -t

正常情况L515应该跑在5000M速率那一档,如果只看到480M,就是USB3.0握手失败。大部分时候换一根带屏蔽的短线、直接插主板原生USB口而不是经过Hub,就能解决。另外,TX2有些载板会把USB3.0和网口、串口共用控制器,接多了外设可能会出现枚举失败。我后来给相机单独留了一个USB口,其它外设尽量走扩展口或者网络,问题就少很多。

确认完这些底子,再进入驱动安装才有意义。这个检查过程看起来不酷,但它决定了后面你是在调算法还是在调驱动。

2. librealsense源码编译:L515在TX2上最折腾的一步

2.1 依赖和编译参数:源码安装不是越新越好

在TX2上,我建议直接用源码编译librealsense,原因有两个:一是官方仓库不一定为ARM架构提供了合适的预编译包,二是源码安装可以把编译选项固定成自己需要的组合,方便后面调优。安装依赖的时候,除了常规的cmake、build-essential,还需要libusb-1.0-0-dev、pkg-config、libgtk-3-dev、libglfw3-dev、libssl-dev等。注意,如果板子上之前装过别的OpenGL库,版本不一致可能让编译在GUI示例程序处报错,但库本身是能编出来的。

编译参数上,我的配置是:

mkdir build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_EXAMPLES=true \ -DFORCE_RSUSB_BACKEND=true \ -DCMAKE_INSTALL_PREFIX=/usr/local make -j2 sudo make install

make -j2是TX2上必须注意的:板子内存不大,默认-j4甚至-j6容易把内存吃满然后编译进程被系统杀掉。FORCE_RSUSB_BACKEND这个选项是我在嵌入式上强烈建议开的,它可以绕开内核里某些不稳定的Video4Linux驱动路径,让SDK直接通过libusb操作设备。代价是抓取效率理论上比内核路径低一点,但在L515这种激光雷达相机上感知不明显,换来的是稳定。

这里我要特别强调:不要因为编译过了就以为自己成功了。编译成功只是第一步,接下来必须跑一遍官方自检工具,把深度、彩色、红外流都打开看一遍,确认三种流都能稳定输出。嵌入式平台上最常见的现象是编译时一切正常,一开深度流就报Frame didn't arrive within X ms,这种大概率不是相机坏,而是USB带宽或后台驱动没对。

2.2 固件与驱动版本对不上,枚举设备直接失败

L515的固件升级比普通摄像头严格,因为深度计算有一部分在相机内部的DSP上完成,固件和SDK必须对应得上。接上相机后,先执行:

rs-enumerate-devices

如果输出里能看到相机序列号、USB类型、固件版本,说明基本OK。如果提示固件版本过旧或者需要更新,再用官方固件更新工具配合DFU文件升级。升级固件的坑在于:L515进入DFU模式后,设备名会短暂消失,系统会重新枚举。这时候千万别拔线,等它回来就行。我见过有人以为设备“死”了,直接拔了线,结果把固件刷到一半中断,后来只能进入恢复模式重刷。

固件升级完还要把相机重新插拔一次,让新固件生效。然后再跑一次rs-enumerate-devices,同时确认SDK版本能识别这个固件版本。如果SDK版本太旧,即使固件是新的,它也可能报不支持。我的经验是:不要追求固件绝对最新,而是以“librealsense版本要求的固件版本区间”为准。官方文档里通常会给一张表格,固件版本低于最低要求时,深度流会拒绝启动。

2.3 USB带宽不够时的真相:rs-enumerate-devices说什么

L515在USB3.0下能同时出彩色图、深度图、红外图和点云,但这几种流的带宽需求和算力需求是加在一起的。TX2上如果你把彩色调到1080p、深度也调到720p、帧率还拉满30FPS,再开点云,USB带宽就可能吃紧。表现不是立刻报错,而是过一会儿深度图断流,接着设备重新枚举,反复循环。

这里有个判断方法:通过rs-enumerate-devices看当前链路速度,再用dmesg看内核日志里有没有usb 3-1: Not enough bandwidth之类的字样。如果有,基本就是带宽不足。解决办法不是去调什么USB参数,而是主动降低相机输出规格。我最终在TX2上长期使用的相机预设是:彩色1280x720@15FPS,深度640x480@15FPS,关掉点云实时输出,只在需要时单独请求一帧。

这个步骤做完,librealsense才算真正在TX2上站稳。接下来就是把它搬进ROS。

3. realsense-ros集成和相机话题配置

3.1 版本匹配:ROS仓库、SDK、还有内核三者对齐

realsense-ros不是简单地git clone就能编译,它和librealsense之间有版本对应关系。比如librealsense某个大版本对应realsense-ros的某个分支,混着用会出现接口不一致,最常见的报错是“找不到rs2::context相关方法”或者“无法解析realsense2_camera组件”。在TX2上由于ROS版本旧,还要额外注意dddynamic_reconfigure这个依赖,它往往需要单独从源码编译。

我的做法是先确定librealsense的版本号,然后去realsense-ros仓库找到相同版本号的分支或标签,之后创建catkin工作空间:

mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone <realsense-ros对应分支> cd ~/catkin_ws catkin_make -j2

如果编译过程中报缺少ddynamic_reconfigure,先去它的源码仓库clone到src目录再一起编译。这里有个容易忽略的小问题:catkin_make默认会尝试编译工作空间里所有包,如果内存不够,会让编译变慢甚至崩掉。可以只编译目标包:

catkin_make --pkg realsense2_camera ddynamic_reconfigure

编译成功后,还要记得source ~/catkin_ws/devel/setup.bash,否则launch时系统找不到包。这个坑很基础,但在TX2上因为经常开多个终端,经常有人忘了每回都要source,导致明明编译好了却提示找不到。

3.2 用launch把深度对齐到彩色图,后面YOLO要用

ROS集成不光是让相机出话题,而是把话题配置成你后面真正会用到的形态。YOLO要的是彩色图,深度测距要的是“和彩色图像素一一对应的深度图”。L515的深度传感器和彩色传感器不在同一个物理位置,视角也不一样,所以原生深度话题和彩色话题不能直接按像素坐标对齐。

打开realsense-ros的launch文件时,我加了下面几个参数:

<arg name="depth_width" value="640"/> <arg name="depth_height" value="480"/> <arg name="depth_fps" value="15"/> <arg name="color_width" value="1280"/> <arg name="color_height" value="720"/> <arg name="color_fps" value="15"/> <arg name="align_depth" value="true"/> <arg name="pointcloud" value="false"/>

其中align_depth设置为true后,相机节点会额外发布一个/camera/aligned_depth_to_color/image_raw话题,这个话题的图像尺寸、视野和彩色图一致,每个像素的数值是深度距离。有了它,后面YOLO检测到目标在图像里的坐标后,直接读对应坐标的深度值就行,不需要自己做投影转换。pointcloud默认我关掉了,因为点云话题对CPU占用太高,而且实时检测根本不需要一整片点云,只需要目标中心那一个距离值。

3.3 快速检查话题和图像流

launch起来之后,用以下命令快速确认:

rostopic list rostopic hz /camera/color/image_raw rostopic hz /camera/aligned_depth_to_color/image_raw

如果rostopic hz显示频率稳定在15Hz左右,说明上传稳定。如果频率忽高忽低,或者经常出现[ WARN] ... last message ... took too long,说明系统负载太高或USB传输不稳定,需要继续降分辨率、降帧率。这一步一定要做扎实,因为后面YOLO跑起来之后,CPU和GPU已经满了,如果相机链路本身就不稳定,你根本分不清是检测模型的问题还是数据源的问题。

查看图像内容,可以在RViz里订阅彩色话题和对齐深度话题,也可以直接用rqt_image_view。我习惯先用rqt_image_view快速看一眼,确认深度图没有大范围黑块。如果对齐深度图里出现大面积零值,往往是相机离物体太近或者太远,L515的有效测量范围大概在0.25米到9米之间,超出范围就会变成无效深度。

4. 在TX2上跑YOLO:darknet_ros和移植到ROS的思路

4.1 检测模型选择:全尺寸YOLO在TX2上不现实

TX2的算力放在今天看已经不算强了,完整版YOLOv3在8位整型模型下也跑不满实时,更别提浮点精度。我第一版直接用YOLOv4全尺寸模型,检测一张640x480的图,推理时间超过1秒,基本没法用。后来换成YOLOv4-tiny,输入分辨率降到320x320,推理时间才压到100毫秒以内。

这不是模型好坏的问题,而是嵌入式平台的资源边界问题。移动机器人场景对检测精度没有那么极端,YOLOv4-tiny对人和常见障碍物的检测能力已经够用。如果你有精力和工具链,还可以把模型转成TensorRT的INT8格式,能再快一截。TX2上我记得最流畅的一次配置是YOLOv4-tiny、输入320x320、TensorRT INT8,推理时间大概能到40到60毫秒一帧,配合相机15FPS,已经能连续出框了。

4.2 用darknet_ros订阅相机话题的配置要点

把YOLO接进ROS,最省事的方式是直接用darknet_ros。它内部封装了检测流程,只需要给它指定模型路径和订阅的图像话题。我这边编译好darknet_ros之后,主要改了三个地方:

  • 模型配置文件里的width、height、batch和subdivisions。推理时把batch设成1,subdivisions设成1,不然内存不够。
  • config/ros.yaml里的订阅话题,改成/camera/color/image_raw。
  • launch文件里还要确保darknet_ros和相机节点在同一台机器上跑,不要在多个终端里分别source不同版本的ROS环境,否则话题会发现不了。

启动命令大致是:

roslaunch darknet_ros darknet_ros.launch

但要注意,darknet_ros默认会加载自己目录下的yolo配置和权重,你需要把权重文件放到它指定的model路径下,并且配置文件里的names文件路径要是绝对路径,或者在你launch的工作目录下能找到。否则你会看到它启动正常,但没有检测结果输出,因为识别类别名加载失败。

4.3 跑不起来时,先分清是驱动问题还是模型问题

我在TX2上遇到过一个很迷惑的现象:darknet_ros启动后,日志里显示一直在等待图像,但相机话题明明有数据。后来发现是我在另一个终端里又launch了一次realsense-ros,导致有两个相机节点在抢同一个USB设备,第二个节点抢不到设备,话题也没有正确发布。这种问题光看darknet_ros日志是看不出来的,必须先rostopic list确认话题存在,再rostopic echo确认有消息。

另一个常见问题是CPU占用。darknet_ros使用OpenCV读取图像,如果图像传输用的编码不是rgb8,中间会多一次格式转换。我在TX2上遇到过彩色话题是YUYV格式的情况,darknet_ros那边读取后还要转换,占用很高。后来在realsense-ros的launch里强制彩色输出为rgb8,CPU负载立刻降下来。

跑通之后,你会在/darknet_ros/detection_image话题上看到画了框的图像,在/darknet_ros/bounding_boxes话题上看到检测框的坐标、类别和置信度。到这里,ROS下的YOLO链路就算打通了。

5. 把检测框和深度值串起来:从彩色图到三维坐标

5.1 对齐深度话题的意义

只出检测框,对机器人来说不够,还得知道目标离自己多远。L515的优势是深度精度高,问题是怎么把检测框的位置对应到深度图的像素位置。如果没有打开align_depth,深度图和彩色图的像素坐标是错位的,直接取深度值会取到背景或者错位的物体上。所以我在前面专门强调要对齐深度话题。

完成对齐后,二路话题的分辨率、视野一致。检测框在彩色图上的中心点坐标,可以直接用在深度图上。我用到的关键话题就是/camera/aligned_depth_to_color/image_raw。

5.2 取深度值时的几个坑

取深度值没有那么简单。图像里的深度值默认单位是毫米,存储在uint16格式中,但无效像素的数值是0。如果目标反光、太近、太远,取到的0会让测距直接失败。所以我处理的时候会取检测框中心周围一个3x3区域的中位数,而不是单点,这样既能避免单个像素的噪声,也能在中心点刚好落在物体边界时稍微稳一点。

代码逻辑大概是这样:

import cv2 import numpy as np def distance_from_bbox(depth_image, bbox): cx = int((bbox.xmin + bbox.xmax) / 2) cy = int((bbox.ymin + bbox.ymax) / 2) region = depth_image[cy-1:cy+2, cx-1:cx+2] values = region[region > 0] if len(values) == 0: return None return float(np.median(values)) / 1000.0 # 毫米转米

注意,这里处理的是已经转成numpy数组的深度图,不是ROS消息本体。用cv_bridge转换时,如果直接指定desired_encoding="32FC1",深度会变成浮点米单位,但很多深度相机话题默认是16UC1毫米单位。我建议先用passthrough读取原始编码,再自己判断单位,避免不同相机节点配置导致单位不一致的问题。

5.3 把结果发布成可视化消息

拿到检测信息和距离后,我习惯把它们重新封装成两个话题:一个直接覆盖在图像上的可视化话题(用cv_bridge把画好框的图转成sensor_msgs/Image发出去),另一个是结构化的检测结果话题(包含类别、置信度、框坐标、距离)。这样后续做导航避障、路径规划时,可以直接订阅结构化结果,不用每次重复跑YOLO和取深度。

发布可视化话题看起来简单,但有个细节:画框和文字时要处理好中文字体,OpenCV默认不支持中文,我直接改成类别英文缩写,省得字体库问题。另外,画框之前要给图像做个拷贝,千万别直接修改从订阅回调里拿到的共享内存图像,否则会导致显示闪烁。

到这一步,L515和YOLO就在ROS里真正形成闭环了。但闭环归闭环,能不能稳定实时运行,还要看TX2这台板子的整体调度。

6. 性能调优记录:从1~2帧提到8帧的狠活

6.1 先打破幻想:TX2不是带不动YOLO,是喂数据的方式不对

刚跑通时,我的端到端链路是:L515输出1080p彩色图,darknet_ros把图像缩放成416x416送进完整版YOLOv4,再开点云,再在RViz里显示多个话题。结果整条链路只有1到2帧,CPU还经常满核。这个表现让我一度怀疑TX2是不是完全跑不了实时检测。

后来我复盘发现,TX2的GPU算力没有PC那么宽裕,但它也不是完全没用。问题在于我把资源浪费在了很多不必要的地方:1080p的图像传输与缩放、全尺寸YOLO的高算力消耗、点云的高内存占用、RViz那一路可视化。把这些降下来之后,同样的硬件,端到端帧率翻了四倍还多。

6.2 三条立竿见影的优化

第一条,也是最容易做的一条,给TX2开启高性能模式。TX2默认不是跑满全速的,执行下面两条命令,能明显感觉到性能变化:

sudo nvpmodel -m 0 sudo jetson_clocks

注意jetson_clocks会把CPU/GPU频率拉满,功耗和发热会上升,如果是电池供电的项目,需要在续航和性能之间做个取舍。但做开发测试时,这一步必须开。

第二条,压相机输出。把彩色分辨率降到640x480,深度保持640x480,帧率都压到15FPS。YOLO输入分辨率用320x320。这四组参数一改,编解码负载、缩放负载、推理负载全部降了一个量级。视觉检测本身不依赖超高分辨率,反而会因为分辨率太高导致小目标检测速度更慢。

第三条,关掉不用的可视化与点云话题。实时联调阶段只需要保留必要话题,等需要排查具体问题时再单独开RViz。我实际测试中,仅仅把RViz和点云关掉,CPU占用就降了30个百分点。

6.3 更进一步的模型优化

如果还想再快,就得从模型侧入手。darknet的YOLOv4-tiny在TX2的GPU上大概耗时80到120毫秒一帧,这个数字还不够好看。把它转换成TensorRT的FP16或INT8模型后,可以进一步压到40到60毫秒。有条件的项目,还可以剪枝和蒸馏,但这个对工具链要求高,不是所有团队都能做。

我的建议是:先别急着上TensorRT,把整条链路的瓶颈用话题频率测一遍。比如用rostopic hz分别看相机话题频率、darknet输入话题频率、检测输出话题频率,哪一段掉得最厉害,瓶颈就在哪。很多时候你会发现瓶颈在图像缩放或者某一次无谓的格式转换,而不是GPU本身。先把这些基础问题解决掉,再考虑模型压缩。

这条优化记录没有太多玄学,就是不断做减法,把TX2上每一份算力都花在刀刃上。

最后再分享一个小技巧:调试这种实时链路时,建议先用rosbag把一小段相机数据录下来,后面所有检测与测距的实验都用rosbag回放,不要每次都用真实相机对着人跑。这样既能让现场环境更稳定,也能节省调试时的电量,还能随时复盘相机配置改动带来的影响。我就是靠着这个习惯,最后才把“TX2安装L515并在ROS下运行YOLO”这件事从一堆零散笔记整理成一篇能复现的总结。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询