简介:这份资源面向计算机、电子信息工程、数学等专业的大学生,适用于课程设计、期末大作业或毕业设计场景,核心内容是基于YOLOv5的ROS部署版实现行人与红绿灯识别。压缩包共123个文件,约83.87MB,其中38个py源码文件承载模型推理与节点逻辑,48个yaml负责参数与数据集配置,另有3个pt权重文件、5个sh启动脚本、5份md说明文档及Dockerfile等部署文件,覆盖从环境搭建到模型加载的完整链路。资源已积累1936人学习下载,说明其在同类课程设计参考中具备一定认可度。读者可据此获得一套可直接运行的ROS视觉感知工程:借助预训练权重快速验证行人与红绿灯检测效果,通过yaml与py文件理解话题订阅、图像推理与结果发布的组织方式,并参考说明文档与注意力机制报告完成功能修改与二次开发,适合具备一定基础、能自行调试代码的学习者作为参考资料使用。
1. 从一份 .rar 说起:YOLOv5 在 ROS 里跑行人和红绿灯,到底难在哪
很多人第一次拿到「基于 YOLOv5 ROS 部署版实现行人和红绿灯识别」这类压缩包,第一反应是解压、catkin_make、roslaunch,然后盯着终端等结果。现实往往是:编译报错、摄像头打不开、检测框延迟半秒、红绿灯颜色认反。问题不在 YOLOv5 本身,也不在 ROS 本身,而在两者中间的「胶水层」——图像怎么从 ROS 话题喂进 PyTorch、推理结果怎么带时间戳发回话题、GPU 和 CPU 之间怎么不互相拖死。
这个方案要解决的核心场景很明确:让一台带摄像头的机器人或边缘计算盒子,在 ROS 体系内实时输出「画面里有没有人、人在哪、红绿灯是什么颜色」。适合三类人:做 ROS 小车自主导航、需要感知层输入的学生和工程师;想把 YOLOv5 从脚本推理搬到机器人系统的算法同学;以及手上有 RK3568、树莓派 4B 这类边缘板子,想验证部署可行性的开发者。它不是一个「跑通就完事」的 demo,而是一套需要你理解话题、帧率、坐标系和推理后端的最小工程。
2. 拆开这个部署版:YOLOv5 与 ROS 的接口设计
2.1 为什么不是「写个 Python 脚本读摄像头」就完事
单机脚本推理和 ROS 部署的本质区别在于数据流的所有权。脚本里cv2.VideoCapture(0)直接抓帧,model(frame)出结果,cv2.imshow显示,整条链路你一个人说了算。到了 ROS 里,摄像头可能由usb_cam、cv_camera或厂商驱动节点发布,图像是sensor_msgs/Image消息,编码格式可能是bgr8、rgb8甚至yuv422;你的检测节点只是订阅者之一,还要考虑别的节点(比如建图、导航)也在消费同一路图像。
常见做法是检测节点订阅/camera/image_raw,用cv_bridge转成 OpenCV 的numpy.ndarray,送进 YOLOv5,再把结果发布成自定义消息或vision_msgs/Detection2DArray。这里第一个选型点就来了:用cv_bridge还是手动解析Image.data。cv_bridge省事但有拷贝开销,手动np.frombuffer加reshape更快但要对齐step和encoding。我一般先用cv_bridge跑通,确认帧率和精度没问题,再考虑优化。
第二个选型点是推理后端。YOLOv5 官方仓库支持 PyTorch 原生、TorchScript、ONNX Runtime、TensorRT。ROS 节点里如果直接用torch.hub.load加载,启动慢、显存占用高,但改起来最方便;导出成 TorchScript 或 ONNX 后,节点启动快、依赖少,适合边缘设备。标题里提到「源码+权重」,通常权重是.pt格式,部署版会附带导出脚本或已经转好的.torchscript。
2.2 一个最小可跑的检测节点长什么样
下面这段代码是一个 ROS 节点的骨架,订阅图像话题,用 YOLOv5 推理,发布带类别和框的结果。它不是完整工程,但把关键接口都摆出来了。
#!/usr/bin/env python3 # detector_node.py import rospy import cv2 import numpy as np import torch from cv_bridge import CvBridge from sensor_msgs.msg import Image from vision_msgs.msg import Detection2DArray, Detection2D, BoundingBox2D, ObjectHypothesisWithPose class YoloRosDetector: def __init__(self): # 加载 TorchScript 模型,比原生 .pt 启动快,适合节点常驻 self.model = torch.jit.load(rospy.get_param("~model_path", "yolov5s.torchscript")) self.model.eval() if torch.cuda.is_available(): self.model.to("cuda") self.bridge = CvBridge() self.conf_thres = rospy.get_param("~conf_thres", 0.35) self.iou_thres = rospy.get_param("~iou_thres", 0.45) # 行人和红绿灯在 COCO 里的类别 id:person=0, traffic light=9 self.keep_classes = [0, 9] self.pub = rospy.Publisher("/detections", Detection2DArray, queue_size=10) self.sub = rospy.Subscriber("/camera/image_raw", Image, self.callback, queue_size=1, buff_size=2**24) def callback(self, msg): try: frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8") except Exception as e: rospy.logerr("cv_bridge failed: %s", e) return # YOLOv5 输入要求 RGB、尺寸 640、归一化到 0-1 img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img = img.transpose(2, 0, 1) # HWC -> CHW tensor = torch.from_numpy(img).float().div(255.0).unsqueeze(0) if torch.cuda.is_available(): tensor = tensor.to("cuda") with torch.no_grad(): pred = self.model(tensor)[0] # pred: [N, 5+nc],前 4 是 xywh,第 5 是 obj conf,后面是类别分数 pred = self.non_max_suppression(pred, self.conf_thres, self.iou_thres) self.publish_detections(pred, msg.header, frame.shape) def non_max_suppression(self, pred, conf_thres, iou_thres): # 简化版 NMS,实际工程建议直接用 yolov5/utils/general.py 里的实现 pred = pred[pred[:, 4] > conf_thres] if pred.shape[0] == 0: return pred # 只保留行人和红绿灯 class_scores = pred[:, 5:] class_ids = class_scores.argmax(dim=1) mask = torch.zeros_like(class_ids, dtype=torch.bool) for c in self.keep_classes: mask |= (class_ids == c) return pred[mask] def publish_detections(self, pred, header, shape): arr = Detection2DArray() arr.header = header h, w = shape[:2] for det in pred: x, y, bw, bh = det[:4].tolist() conf = float(det[4]) cls = int(det[5:].argmax()) d = Detection2D() d.header = header d.bbox = BoundingBox2D() d.bbox.center.x = x / 640.0 * w d.bbox.center.y = y / 640.0 * h d.bbox.size_x = bw / 640.0 * w d.bbox.size_y = bh / 640.0 * h hyp = ObjectHypothesisWithPose() hyp.id = cls hyp.score = conf d.results.append(hyp) arr.detections.append(d) self.pub.publish(arr) if __name__ == "__main__": rospy.init_node("yolo_ros_detector") YoloRosDetector() rospy.spin()逻辑说明:节点初始化时加载 TorchScript 模型,订阅/camera/image_raw,回调里做 BGR→RGB、resize、归一化、CHW 转换,推理后过滤出 person 和 traffic light 两类,最后把框从 640 尺度映射回原图尺寸发布。参数说明:~model_path是模型路径,~conf_thres默认 0.35,行人检测可以降到 0.25 提高召回,红绿灯建议保持 0.4 以上避免误检;~iou_thres控制 NMS 重叠阈值,密集行人场景可以调到 0.5。queue_size=1和buff_size=2**24是为了避免图像消息积压导致延迟累积,这是 ROS 图像订阅的常见坑。
2.3 红绿灯识别为什么不能只靠 COCO 预训练权重
COCO 数据集里traffic light是一个大类,但它不区分红、黄、绿。如果你直接拿 YOLOv5s 的 COCO 权重跑,模型只会告诉你「这里有个红绿灯」,不会告诉你「现在是红灯」。标题里说「红绿灯识别」,实际落地通常有两种做法:
第一种是检测框 + 颜色分类。YOLOv5 只负责定位红绿灯,裁出框内区域后用 HSV 阈值或一个小分类网络判断颜色。这种做法工程上最稳,因为红绿灯的颜色特征非常强,HSV 空间里红绿黄分离度高,不需要重新训练检测模型。
第二种是重新训练检测模型,把类别改成red_light、green_light、yellow_light三类。这需要标注数据,通常从行车记录仪或机器人摄像头采集。训练时注意红绿灯在画面里占比很小,建议用--img 640以上分辨率,并在数据增强里加mosaic和mixup提升小目标召回。
我一般会先跑第一种方案验证链路,因为不需要标注、当天就能出结果;如果误判率高,再考虑第二种。下面是一个 HSV 颜色判断的片段:
def classify_light_color(frame, bbox): x, y, w, h = bbox roi = frame[int(y):int(y+h), int(x):int(x+w)] if roi.size == 0: return "unknown" hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 红色在 HSV 里跨 0 度,需要两段 red1 = cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) red2 = cv2.inRange(hsv, (170, 100, 100), (180, 255, 255)) red = cv2.countNonZero(red1) + cv2.countNonZero(red2) green = cv2.countNonZero(cv2.inRange(hsv, (40, 80, 80), (85, 255, 255))) yellow = cv2.countNonZero(cv2.inRange(hsv, (20, 100, 100), (35, 255, 255))) counts = {"red": red, "green": green, "yellow": yellow} color = max(counts, key=counts.get) return color if counts[color] > 0.1 * roi.shape[0] * roi.shape[1] else "unknown"参数说明:红色阈值分两段是因为 HSV 色相环上红色在 0 和 180 附近;S和V下限设 80~100 是为了排除灰白和暗光干扰;最后用像素占比 10% 作为阈值,避免框内噪声被误判成颜色。这个函数对光照变化比较敏感,夜间场景建议先做白平衡或直方图均衡。
3. 从零跑通:环境、编译、启动的完整路径
3.1 环境选型:Ubuntu 20.04 + ROS Noetic 还是 22.04 + Humble
这是第一个让人纠结的地方。ROS Noetic 对应 Ubuntu 20.04,Python 3.8,cv_bridge和 OpenCV 的兼容性经过多年验证,YOLOv5 官方仓库在 Python 3.8 上跑得最稳。ROS Humble 对应 Ubuntu 22.04,Python 3.10,新特性多但部分老包需要重新编译。
如果你的板子是 Jetson 或 RK3568,厂商 SDK 通常绑定特定 Ubuntu 版本,这时候没得选,跟着 SDK 走。如果是 x86 工控机或笔记本,我建议 Noetic,因为网上能搜到的 YOLOv5 ROS 部署资料大多基于这个组合,踩坑成本低。安装 ROS 可以用常见的一键脚本,但注意脚本执行前确认系统版本和源,装完用rosversion -d验证。
Python 依赖方面,YOLOv5 需要torch、torchvision、opencv-python、numpy、pyyaml、tqdm、matplotlib。ROS 节点还需要rospy、cv_bridge、vision_msgs。vision_msgs如果没装,sudo apt install ros-noetic-vision-msgs。GPU 推理需要 CUDA 和对应版本的 PyTorch,Jetson 上要用 NVIDIA 提供的 wheel,不要直接pip install torch。
3.2 编译工作空间与常见报错处理
假设压缩包解压后是一个 catkin 工作空间,目录结构通常是src/yolov5_ros/下面有scripts/、launch/、weights/、CMakeLists.txt、package.xml。编译流程:
# 进入工作空间根目录 cd ~/yolov5_ros_ws # 先确认 Python 脚本有可执行权限 chmod +x src/yolov5_ros/scripts/*.py # 编译,-DCMAKE_BUILD_TYPE=Release 对 C++ 节点有优化 catkin_make -DCMAKE_BUILD_TYPE=Release # 如果报 cv_bridge 找不到,检查 ROS 环境是否 source source /opt/ros/noetic/setup.bash # 编译通过后 source 工作空间 source devel/setup.bash常见报错一:ModuleNotFoundError: No module named 'cv_bridge'。原因是 ROS 的 Python 环境和系统 Python 不一致,或者没有 source ROS 环境。解决方法是确认python3 -c "import cv_bridge"在 source 后能通过,不行就sudo apt install ros-noetic-cv-bridge。
常见报错二:ImportError: libtorch.so: cannot open shared object file。这是 PyTorch 动态库路径没进LD_LIBRARY_PATH。解决方法是找到 torch 安装路径,在~/.bashrc里加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/torch/lib。
常见报错三:catkin_make卡在cmake阶段报 OpenCV 版本冲突。ROS Noetic 自带 OpenCV 4.2,如果你 pip 装了opencv-python4.5+,C++ 节点可能链接到错误版本。解决方法是 C++ 节点用 ROS 自带的 OpenCV,Python 节点用 pip 的,两者互不干扰;或者在CMakeLists.txt里显式指定find_package(OpenCV 4.2 REQUIRED)。
3.3 启动文件怎么写:摄像头、模型、话题一次配好
一个可用的 launch 文件应该把摄像头驱动、检测节点、可选的可视化节点都串起来。下面是一个模板:
<launch> <!-- 摄像头驱动,usb_cam 是常见选择 --> <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen"> <param name="video_device" value="/dev/video0" /> <param name="image_width" value="640" /> <param name="image_height" value="480" /> <param name="pixel_format" value="yuyv" /> <param name="camera_frame_id" value="camera_link" /> <param name="io_method" value="mmap" /> </node> <!-- YOLOv5 检测节点 --> <node name="yolo_detector" pkg="yolov5_ros" type="detector_node.py" output="screen"> <param name="model_path" value="$(find yolov5_ros)/weights/yolov5s.torchscript" /> <param name="conf_thres" value="0.35" /> <param name="iou_thres" value="0.45" /> <remap from="/camera/image_raw" to="/usb_cam/image_raw" /> </node> <!-- 可选:用 image_view 看检测结果叠加 --> <node name="image_view" pkg="image_view" type="image_view" output="screen"> <remap from="image" to="/detections_image" /> </node> </launch>参数说明:video_device根据实际设备改,笔记本自带摄像头通常是/dev/video0;pixel_format用yuyv还是mjpeg取决于摄像头支持,mjpeg帧率高但解码占 CPU;camera_frame_id要和机器人 URDF 里的坐标系一致,否则 RViz 里显示错位。检测节点的remap把订阅话题指到摄像头实际发布的话题,这是新手最容易漏的一步——话题名对不上,节点启动了但一直没输出。
启动命令:
roslaunch yolov5_ros detect.launch如果只想验证检测节点,可以用rosbag play回放已有图像包,避免摄像头硬件问题干扰。rosbag play --clock your_bag.bag,同时 launch 里加<param name="/use_sim_time" value="true" />。
4. 避坑与排查:那些让检测节点「看起来在跑但没结果」的问题
4.1 话题对不上:节点活着,但一帧都没处理
现象:rosnode list能看到检测节点,rostopic hz /detections没有输出,终端也不报错。原因:订阅的话题名和摄像头发布的不一致,或者remap写错。解决:rostopic list确认图像话题真实名称,rostopic info /camera/image_raw看发布者,然后在 launch 里把remap的from改成检测节点代码里订阅的名字,to改成实际话题名。注意remap的from是节点内部的名字,不是外部话题名。
4.2 图像编码不匹配:cv_bridge 抛异常或颜色错乱
现象:cv_bridge报Unsupported encoding,或者检测框位置对但颜色发蓝。原因:摄像头发布的是yuv422或rgb8,代码里按bgr8转。解决:在imgmsg_to_cv2里指定desired_encoding="bgr8",cv_bridge会自动转换;如果报错,先用msg.encoding打印实际编码,再决定是否手动转换。颜色发蓝通常是 RGB 和 BGR 搞反,YOLOv5 训练时用的是 RGB,所以从 ROS 拿到 BGR 后要cvtColor转一次。
4.3 延迟越跑越大:queue_size 和 buff_size 的锅
现象:刚启动时检测框跟得上画面,跑几分钟后延迟越来越大,最后画面和检测结果差好几秒。原因:订阅者处理速度跟不上发布速度,消息在队列里堆积。queue_size默认是None(无限),图像消息又大,很快占满内存。解决:订阅时设queue_size=1,并且buff_size设大一点(比如2**24)避免单帧被截断。更彻底的做法是在回调里判断msg.header.stamp和当前时间的差,超过阈值就丢弃旧帧。
4.4 GPU 显存泄漏:跑一晚上就 OOM
现象:节点连续运行几小时后报CUDA out of memory。原因:每次推理都创建新 tensor 但没有释放,或者torch.no_grad()没加导致计算图累积。解决:推理代码包在with torch.no_grad():里;如果用了cv_bridge和 numpy 转换,注意大数组及时置None;可以在回调里每隔 N 帧调用一次torch.cuda.empty_cache(),但不要每帧都调,否则性能下降。
4.5 红绿灯误检:广告牌、车尾灯都被认成红绿灯
现象:画面里红色广告牌、前车尾灯被检测成traffic light。原因:COCO 预训练权重对红绿灯的定义比较宽泛,加上红色圆形物体特征相似。解决:提高conf_thres到 0.5 以上;在检测后加规则过滤,比如红绿灯通常出现在画面上半部分、宽高比接近 1:2 到 1:3;如果还不行,用自己标注的数据微调模型,只保留真正的红绿灯样本。
5. 进阶:把检测结果真正用起来,以及怎么验证它靠谱
5.1 从检测框到机器人决策:坐标系转换和话题设计
检测结果发布成Detection2DArray只是第一步。如果机器人要根据行人位置避障,需要把像素坐标转成相机坐标系下的三维点,再转到机器人基坐标系。常见做法是用image_geometry的PinholeCameraModel,配合/camera/camera_info里的内参矩阵,把框中心投影到归一化平面,再乘以一个假设深度(比如行人平均距离 3 米)得到相机坐标系下的点,最后用tf2转到base_link。
话题设计上,我建议检测节点只发布原始检测结果,单独写一个决策节点订阅/detections和/tf,做坐标转换和逻辑判断。这样检测节点可以独立测试,决策逻辑改了也不用重新编译整个包。如果多个节点都要用检测结果,用Detection2DArray比自定义消息更通用,RViz 也能直接可视化。
5.2 验证检测质量的三个实用方法
第一个方法是用 rosbag 录制一段包含行人和红绿灯的场景,反复回放,统计不同conf_thres下的召回率和误检数。具体操作:rosbag record /camera/image_raw录 1 分钟,然后写个脚本遍历 bag 里的帧,跑检测并保存结果,人工抽查 50 帧。这比在实时画面里凭感觉判断靠谱得多。
第二个方法是在 RViz 里叠加显示。把/detections和/camera/image_raw都打开,检测框会画在图像上。重点看边界情况:远处小目标有没有框、遮挡一半的行人有没有框、红绿灯颜色和实际是否一致。RViz 的Camera显示插件支持Detection2DArray,不需要额外写可视化代码。
第三个方法是记录推理耗时。在回调里用time.time()打点,统计预处理、推理、后处理各占多少毫秒。YOLOv5s 在 1080Ti 上单帧推理约 8~12ms,Jetson Xavier NX 约 30~50ms,RK3568 用 NPU 约 50~80ms。如果推理耗时远高于这个范围,检查是不是用了 CPU 推理、输入尺寸是不是被改成了 1280、有没有重复加载模型。
5.3 一个我踩过的坑:模型导出时的动态轴和 batch 维度
YOLOv5 导出 TorchScript 时,默认--batch-size 1,导出的模型输入固定为[1, 3, 640, 640]。如果你在 ROS 节点里想一次处理多帧,或者输入尺寸不是 640,模型会报维度错误。导出时加--dynamic可以让 batch 和尺寸动态化,但 TorchScript 的动态轴在部分后端上性能会下降。我的习惯是导出两个版本:一个固定 640 用于实时推理,一个动态用于离线批量测试。另外,导出后一定要用torch.jit.load加载并跑一次随机输入,确认输出形状和预期一致,别等到 ROS 节点里才发现问题。
红绿灯识别还有一个容易忽略的点:摄像头自动曝光。白天到夜晚切换时,画面亮度突变会导致 HSV 阈值失效。我一般会在颜色分类前加一个简单的亮度判断,如果 ROI 平均亮度低于 40 或高于 220,直接返回unknown,避免在极端光照下给出错误颜色。这个规则看起来粗糙,但在实际路测里能减少很多误报。
希望帮到你。
本文还有配套的精品资源,点击获取