@freeCodeCamp:机器人技术中的实时目标检测不仅仅需要一个好模型。在本教程中,Iyanuoluwa 将向您展示如何…
摘要
本教程介绍如何使用 ROS 2 和 YOLOv11 构建用于机器人技术的实时目标检测与跟踪管道,涵盖线程推理、ByteTrack 集成、置信度验证以及用于边缘部署的 ONNX 导出。
查看缓存全文
缓存时间: 2026/07/30 05:48
在机器人技术中,实时目标检测需要的不仅仅是一个好模型。在本教程中,Iyanuoluwa 将向你展示如何使用 ROS 2 和 YOLOv11 构建一个感知流水线。你将学习如何运行线程化推理、使用 ByteTrack 跟踪目标、验证检测结果,以及将模型导出为 ONNX 以在边缘端部署。https://freecodecamp.org/news/how-to-build-a-real-time-object-detection-and-tracking-pipeline-with-ros-2-and-yolov11/…
如何使用 ROS 2 和 YOLOv11 构建实时目标检测与跟踪流水线
来源:https://www.freecodecamp.org/news/how-to-build-a-real-time-object-detection-and-tracking-pipeline-with-ros-2-and-yolov11/
如果你曾经尝试构建一个能够看见、跟踪并响应周围世界的机器人系统,你就会知道,难点并不在于训练一个检测模型。难点在于让该模型在真实的机器人软件栈中可靠地实时运行,而一旦硬件限制或时序问题出现,系统也不会崩溃。在本教程中,你将使用 ROS 2 和 YOLOv11 构建一个完整的实时目标检测与跟踪流水线。你将学习如何从仿真器发布相机帧到 ROS 2、在专用线程中运行 YOLO 推理、集成 ByteTrack 进行跨帧多目标跟踪,以及将模型导出为 ONNX 以在受限硬件上实现更快的推理。到本文结束时,你不仅会明白如何将这些工具连接起来,还会理解每个架构决策对一个旨在用于生产环境(而非仅仅一个笔记本)的感知系统为何如此重要。
以下是本文涵盖的内容:
目录
- 先决条件
- 我们要构建什么以及为什么
- 项目结构
- 如何设置 ROS 2 工作空间
- 如何安装依赖项
- 如何将相机帧发布到 ROS 2
- 如何构建具有线程化推理的感知节点
- 如何集成 ByteTrack 进行多目标跟踪
- 如何添加置信度验证层
- 如何将模型导出为 ONNX 用于边缘部署
- 如何测试完整流水线
- 结论
先决条件
在继续之前,你应该熟悉以下内容:
- Python 3.10 或更高版本: 本教程中的所有代码均使用 Python 编写。
- 基本的 ROS 2 知识: 你应该理解什么是节点、话题、发布者和订阅者。如果你对 ROS 2 不熟悉,官方 ROS 2 文档 是一个不错的起点。
- 熟悉 PyTorch 和目标检测概念: 你不需要自己训练过 YOLO 模型,但你应该理解推理的含义以及边界框检测输出是什么样的。
- 在 Ubuntu 22.04 上运行正常的 ROS 2 Humble 安装。
- 建议使用 GPU 进行实时推理,不过流水线在 CPU 上也能运行,只是帧率会降低。
- CARLA 仿真器(可选): 相机发布者部分使用了 CARLA。如果你没有安装 CARLA,可以用任何 ROS 2 兼容的相机源替代,例如摄像头节点或包文件回放。
我们要构建什么以及为什么
感知流水线是机器人系统中负责理解机器人周围环境的部分。它接收原始传感器数据(通常是相机帧),并将其转换为结构化信息:物体在哪里、它们是什么以及它们如何移动。本教程构建了一个具有四个不同层次的感知流水线:
- 相机采集 从仿真器捕获原始图像帧,并将其作为 ROS 2 消息发布,以便机器人软件栈的其他部分可以消费它们。
- 目标检测 在每一帧上运行 YOLOv11,以识别物体及其位置和置信度得分。
- 多目标跟踪 使用 ByteTrack 关联跨帧的检测结果,为每个物体提供一个随时间稳定的身份,而不是将每一帧都视为全新场景。
- 验证与优化 添加一个置信度门控层,防止低质量检测结果进入下游导航逻辑,并将模型导出为 ONNX,以便在边缘硬件上实现更快的推理。
我们使用 CARLA 作为仿真器,因为它提供逼真的传感器数据、可控的环境以及 Python 可访问的相机角色,使其成为自动驾驶车辆和移动机器人感知研究的自然选择。如果你使用不同的传感器源,ROS 2 架构完全相同,只需更改相机发布者节点即可。
项目结构
在编写任何代码之前,先看一下完整的项目布局会很有帮助。以下是完成后的工作空间的样子:
ros2_perception_ws/
├── src/
│ └── perception_stack/
│ ├── perception_stack/
│ │ ├── __init__.py
│ │ ├── camera_publisher.py # 将 CARLA 帧发布到 ROS 2
│ │ ├── perception_node.py # 线程化 YOLO 推理节点
│ │ ├── tracker.py # ByteTrack 集成
│ │ ├── validator.py # 置信度门控层
│ │ └── export_onnx.py # ONNX 导出脚本
│ ├── models/
│ │ └── yolov11n.pt # 下载的 YOLO 权重
│ ├── package.xml
│ ├── setup.py
│ └── setup.cfg
├── requirements.txt
└── README.md
每个文件都有单一的职责。这种分离对机器人软件尤其重要,因为感知、跟踪和验证以不同的速度演进,并且需要能够独立测试。
如何设置 ROS 2 工作空间
创建工作空间和包:
mkdir -p ~/ros2_perception_ws/src
cd ~/ros2_perception_ws/src
ros2 pkg create --build-type ament_python perception_stack
cd ~/ros2_perception_ws
colcon build
source install/setup.bash
colcon build 编译工作空间。source install/setup.bash 使 ROS 2 识别您的新包,从而可以使用 ros2 run 运行其节点。
如何安装依赖项
pip install ultralytics opencv-python-headless cv_bridge \
torch torchvision onnx onnxruntime-gpu \
numpy supervision
关于运行时提供者:本教程使用具有 GPU 支持的 ONNX Runtime(通过 onnxruntime-gpu)。如果您在无 CUDA 兼容 GPU 的机器上运行,请将其替换为 onnxruntime 以进行仅 CPU 推理。流水线的其余部分不变,但预期帧率会降低。
如何将相机帧发布到 ROS 2
第一个节点桥接了 CARLA 的 Python API 与 ROS 2 生态系统之间的差距。CARLA 使用事件驱动的回调系统。ROS 2 使用发布者-订阅者模型,并带有类型化的消息格式。该节点将 CARLA 的原始图像格式转换为 sensor_msgs/Image 消息,任何 ROS 2 节点都可以订阅该消息。
创建 camera_publisher.py:
import carla
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import numpy as np
class CARLACameraNode(Node):
def __init__(self):
super().__init__('carla_camera_node')
self.publisher = self.create_publisher(Image, '/carla/camera/rgb', 10)
self.bridge = CvBridge()
self.get_logger().info('CARLA 相机节点已启动。')
def camera_callback(self, image):
# CARLA 原始数据是 BGRA 格式,我们将其转换为 BGR 以便与 OpenCV 兼容
array = np.frombuffer(image.raw_data, dtype=np.uint8)
array = array.reshape((image.height, image.width, 4))
bgr = array[:, :, :3]
msg = self.bridge.cv2_to_imgmsg(bgr, encoding='bgr8')
# 使用当前 ROS 2 时钟时间给消息打上时间戳。
# 这一点至关重要。下游节点(如跟踪器和 SLAM 系统)
# 通过消息之间的时间差计算速度和位移。
# 没有准确的时间戳,这些计算会产生垃圾结果,跟踪器也会变得不稳定。
msg.header.stamp = self.get_clock().now().to_msg()
self.publisher.publish(msg)
def main():
rclpy.init()
node = CARLACameraNode()
client = carla.Client('localhost', 2000)
world = client.get_world()
blueprint_library = world.get_blueprint_library()
camera_bp = blueprint_library.find('sensor.camera.rgb')
camera_bp.set_attribute('image_size_x', '1280')
camera_bp.set_attribute('image_size_y', '720')
camera_bp.set_attribute('fov', '90')
spawn_point = world.get_map().get_spawn_points()[0]
camera = world.spawn_actor(camera_bp, spawn_point)
camera.listen(node.camera_callback)
rclpy.spin(node)
camera.destroy()
node.destroy_node()
rclpy.shutdown()
cv_bridge 库负责处理 OpenCV 数组与 ROS 2 图像消息之间的转换。bgr8 编码告诉下游订阅者期望什么颜色格式。如果没有这个,颜色通道可能会被静默交换,导致您的检测器对完全有效的输入产生错误结果。
如何构建具有线程化推理的感知节点
这是流水线中架构上最重要的节点,在查看代码之前,设计的解释是必要的。
ROS 2 默认在单执行器线程上处理订阅者回调。如果在回调内部调用 YOLO 推理,它会在推理期间阻塞该线程。推理运行时,订阅者无法接收新消息。根据队列大小和帧率,这会导致您在推理完成时开始处理已经过时的帧。然后您的跟踪器看到的是一个不规则、时间不一致的流,而不是平滑的流。
解决办法是将采集与处理解耦。回调只做一件事:将传入的帧放入一个有界队列并立即返回。另一个线程从队列中取出帧并运行推理。队列有最大大小。当推理跟不上且队列已满时,新帧会被丢弃,而不是无限排队。这是一个有意为之的设计选择:在实时系统中,一个延迟处理的旧帧通常比一个丢弃的帧更糟糕,因为它会导致跟踪器看到的是过去的世界,而不是现在的世界。
创建 perception_node.py:
import threading
import queue
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from ultralytics import YOLO
import cv2
CONFIDENCE_THRESHOLD = 0.45
class PerceptionNode(Node):
def __init__(self):
super().__init__('perception_node')
self.bridge = CvBridge()
self.model = YOLO('models/yolov11n.pt')
# 有界队列:maxsize=5 可防止陈旧帧积累。
# 当队列满时,image_callback 会丢弃传入的帧
# 而不是等待,从而使流水线保持最新。
self.frame_queue = queue.Queue(maxsize=5)
self.subscription = self.create_subscription(
Image,
'/carla/camera/rgb',
self.image_callback,
10
)
self.inference_thread = threading.Thread(
target=self.run_inference,
daemon=True
)
self.inference_thread.start()
self.get_logger().info('感知节点已就绪。')
def image_callback(self, msg):
# 如果推理跟不上,则丢弃帧。
# 我们绝不想阻塞回调线程。
if not self.frame_queue.full():
self.frame_queue.put(msg)
def run_inference(self):
while rclpy.ok():
msg = self.frame_queue.get()
frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
results = self.model(frame, conf=CONFIDENCE_THRESHOLD, verbose=False)
detections = results[0].boxes
self.get_logger().info(
f'检测到 {len(detections)} 个物体,置信度超过阈值 {CONFIDENCE_THRESHOLD}'
)
def main():
rclpy.init()
node = PerceptionNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
如何集成 ByteTrack 进行多目标跟踪
仅凭检测只能告诉你单个帧中的内容。跟踪告诉你随时间发生的情况:哪辆车是哪辆,它要去哪里,是否与你两秒前看到的同一辆车。
ByteTrack 通过使用交并比 (IoU) 将新检测与现有轨迹关联起来工作,IoU 是预测轨迹位置与传入检测之间边界框重叠的度量。它使用两阶段匹配过程处理高置信度和低置信度检测,使其在遮挡期间比简单的跟踪器更鲁棒。
你最常调整的三个参数是:
track_thresh:启动或确认轨迹所需的最小检测置信度match_thresh:将检测匹配到现有轨迹所需的最小 IoUtrack_buffer:在没有匹配检测的情况下,轨迹存活多少帧后才被移除
创建 tracker.py:
from supervision import ByteTracker, Detections
import numpy as np
class RoboticsTracker:
def __init__(self):
# track_buffer 控制轨迹在没有匹配检测的情况下存活的时间。
# 较高的值有助于应对短暂遮挡,但可能会为已经离开场景的物体产生鬼影轨迹。
self.tracker = ByteTracker(
track_thresh=0.45,
match_thresh=0.8,
track_buffer=30,
frame_rate=30
)
def update(self, yolo_results, frame_shape):
boxes = yolo_results[0].boxes
if len(boxes) == 0:
return []
xyxy = boxes.xyxy.cpu().numpy()
confidence = boxes.conf.cpu().numpy()
class_ids = boxes.cls.cpu().numpy().astype(int)
detections = Detections(
xyxy=xyxy,
confidence=confidence,
class_id=class_ids
)
tracked = self.tracker.update(
detections=detections,
frame_resolution_wh=(frame_shape[1], frame_shape[0])
)
# tracked.tracker_id 为每个检测提供稳定的整数 ID,
# 只要轨迹存活,该 ID 就会跨帧持续存在。
return tracked
tracker_id 字段使跟踪对导航有用。下游系统不再看到一组匿名边界框,而是知道 ID 7 的物体是一个在过去 12 帧中一直向东北方向移动的行人。
如何添加置信度验证层
单个高置信度检测并不足以成为机器人改变其行为的依据。置信度分数衡量模型对所检测内容的确定程度。它们并不衡量检测在时间上是否稳定、平台本身是否稳定,或者周围环境是否使检测合理。
该验证层需要在将检测标记为有效之前,从多个信号中达成共识。
相似文章
Ultralytics YOLO26:统一的实时端到端视觉模型
Ultralytics YOLO26 引入了一个统一的实时视觉模型家族,具有无需NMS的推理、改进的训练策略以及用于检测、分割和姿态估计的多任务能力,实现了最先进的精度与延迟权衡。
/yolo
关于YOLO这一广泛使用的实时目标检测模型系列的文章。
使用LoRA/DoRA微调NVIDIA Cosmos Predict 2.5以生成机器人操作视频
一份完整指南,介绍如何使用LoRA/DoRA微调NVIDIA的Cosmos Predict 2.5世界模型,以生成机器人操作视频,涵盖数据集准备、训练、推理和评估。
YOLO26 简介
YOLO26 是一个于2026年1月发布的多任务计算机视觉模型系列,具备无需 Non-Maximum Suppression 的端到端检测功能以降低延迟,并针对边缘部署进行了优化,具有改进的CPU推理能力和紧凑设计。
如何获得一个好的目标检测模型?[P]
一位用户希望获得关于改进其YOLO11n目标检测模型的建议,计划将其部署在Raspberry Pi 5上,但困扰于理论mAP50指标与实际检测性能之间的差距。