@freeCodeCamp:机器人技术中的实时目标检测不仅仅需要一个好模型。在本教程中,Iyanuoluwa 将向您展示如何…

X AI KOLs Timeline 工具

摘要

本教程介绍如何使用 ROS 2 和 YOLOv11 构建用于机器人技术的实时目标检测与跟踪管道,涵盖线程推理、ByteTrack 集成、置信度验证以及用于边缘部署的 ONNX 导出。

机器人技术中的实时目标检测不仅仅需要一个好模型。 在本教程中,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/…
查看原文
查看缓存全文

缓存时间: 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 以在受限硬件上实现更快的推理。到本文结束时,你不仅会明白如何将这些工具连接起来,还会理解每个架构决策对一个旨在用于生产环境(而非仅仅一个笔记本)的感知系统为何如此重要。

以下是本文涵盖的内容:

目录

先决条件

在继续之前,你应该熟悉以下内容:

  • 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 兼容的相机源替代,例如摄像头节点或包文件回放。

我们要构建什么以及为什么

感知流水线是机器人系统中负责理解机器人周围环境的部分。它接收原始传感器数据(通常是相机帧),并将其转换为结构化信息:物体在哪里、它们是什么以及它们如何移动。本教程构建了一个具有四个不同层次的感知流水线:

  1. 相机采集 从仿真器捕获原始图像帧,并将其作为 ROS 2 消息发布,以便机器人软件栈的其他部分可以消费它们。
  2. 目标检测 在每一帧上运行 YOLOv11,以识别物体及其位置和置信度得分。
  3. 多目标跟踪 使用 ByteTrack 关联跨帧的检测结果,为每个物体提供一个随时间稳定的身份,而不是将每一帧都视为全新场景。
  4. 验证与优化 添加一个置信度门控层,防止低质量检测结果进入下游导航逻辑,并将模型导出为 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:将检测匹配到现有轨迹所需的最小 IoU
  • track_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:统一的实时端到端视觉模型

Hugging Face Daily Papers

Ultralytics YOLO26 引入了一个统一的实时视觉模型家族,具有无需NMS的推理、改进的训练策略以及用于检测、分割和姿态估计的多任务能力,实现了最先进的精度与延迟权衡。

/yolo

Reddit r/LocalLLaMA

关于YOLO这一广泛使用的实时目标检测模型系列的文章。

YOLO26 简介

Hacker News Top

YOLO26 是一个于2026年1月发布的多任务计算机视觉模型系列,具备无需 Non-Maximum Suppression 的端到端检测功能以降低延迟,并针对边缘部署进行了优化,具有改进的CPU推理能力和紧凑设计。

如何获得一个好的目标检测模型?[P]

Reddit r/MachineLearning

一位用户希望获得关于改进其YOLO11n目标检测模型的建议,计划将其部署在Raspberry Pi 5上,但困扰于理论mAP50指标与实际检测性能之间的差距。