Ultralytics YOLO27:

ROS(机器人操作系统)快速入门指南#

本指南将向你展示如何将 Ultralytics YOLO 与 ROS1(rospy)或 ROS2(rclpy)集成,以便在 RGB 图像、深度图像和点云上运行实时目标检测分割

跳转到设置 YOLO 与 ROS,然后处理 RGB 图像深度图像点云

什么是 ROS?#

机器人操作系统(ROS)是一个开源框架,广泛用于机器人研究和工业领域。ROS 提供了一系列库和工具,帮助开发者创建机器人应用。ROS 旨在支持各种机器人平台,因此是一个灵活且强大的机器人开发工具。要快速了解 ROS,请观看 Open Robotics 发布的三分钟视频 ROS 简介

ROS 的主要特性#

  1. 模块化架构:ROS 采用模块化架构,开发者可以组合称为节点的小型可复用组件来构建复杂系统。每个节点通常执行特定功能,节点之间通过话题服务上的消息进行通信。

  2. 通信中间件:ROS 提供了健壮的通信基础设施,支持进程间通信和分布式计算。这通过数据流(话题)的发布-订阅模型以及服务调用的请求-响应模型实现。

  3. 硬件抽象:ROS 在硬件之上提供抽象层,使开发者能够编写与设备无关的代码。这样,同一份代码就能用于不同的硬件配置,从而简化集成和实验。

  4. 工具和实用程序:ROS 附带丰富的可视化、调试和仿真工具及实用程序。例如,RViz 用于可视化传感器数据和机器人状态信息,而 Gazebo 提供了功能强大的仿真环境,用于测试算法和机器人设计。

  5. 丰富的生态系统:ROS 生态系统规模庞大且持续发展,提供了适用于导航、操控、感知等各种机器人应用的大量软件包。社区也在积极参与这些软件包的开发和维护。

ROS 版本的演进

ROS 自 2007 年开发以来,经历了多个版本的演进,分为 ROS 1 和 ROS 2。下面的现有示例使用 ROS1 Noetic;使用 ROS2中的精简适配器展示了当前 ROS2 版本对应的 rclpy 接口。

ROS 1 与 ROS 2#

ROS 1 为机器人开发提供了坚实基础,而 ROS 2 通过以下特性解决了 ROS 1 的不足:

  • 实时性能:改进了对实时系统和确定性行为的支持。
  • 安全性:增强了安全特性,可在各种环境中实现安全可靠的运行。
  • 可扩展性:更好地支持多机器人系统和大规模部署。
  • 跨平台支持:扩展了对 Linux 之外各种操作系统的兼容性,包括 Windows 和 macOS。
  • 灵活的通信:使用 DDS 实现更灵活、更高效的进程间通信。

ROS 消息和话题#

在 ROS 中,节点之间通过消息话题进行通信。消息是一种定义节点之间交换信息的数据结构,而话题则是消息发送和接收所使用的命名通道。节点可以向话题发布消息,也可以订阅话题中的消息,从而实现相互通信。这种发布-订阅模型支持节点之间的异步通信和解耦。机器人系统中的每个传感器或执行器通常都会将数据发布到某个话题,其他节点随后可以使用这些数据进行处理或控制。在本指南中,我们将重点介绍 Image、Depth 和 PointCloud 消息以及相机话题。

设置 Ultralytics YOLO 与 ROS#

ROS1 示例使用此 ROS 环境进行了测试,该环境是 ROSbot ROS 仓库的分支。在 ROS2 中,YOLO 和 NumPy 处理流程相同;不同之处仅在于节点生命周期和消息转换。

Husarion ROSbot 2 PRO autonomous robot platform

安装依赖项#

除了 ROS 环境外,你还需要安装以下依赖项:

  • ROS NumPy 软件包:快速在 ROS Image 消息与 NumPy 数组之间进行转换时需要此软件包。

    pip install ros_numpy
  • Ultralytics 软件包

    pip install ultralytics

使用 ROS2#

ROS2 使用 rclpy 替代 rospy,并使用 cv_bridge 替代 ros_numpy 图像转换。下面的节点是下方 RGB 检测流程完整的 ROS2 等效实现;请只实例化一次模型,并在各个回调之间复用它们。

import cv_bridge
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from sensor_msgs.msg import Image

from ultralytics import YOLO

class UltralyticsNode(Node):
    """Run YOLO detection on ROS2 image messages."""

    def __init__(self):
        """Initialize the ROS2 node, model, and image interfaces."""
        super().__init__("ultralytics")
        self.bridge = cv_bridge.CvBridge()
        self.model = YOLO("yolo26m.pt")
        self.publisher = self.create_publisher(Image, "/ultralytics/detection/image", 5)
        self.create_subscription(Image, "/camera/color/image_raw", self.callback, qos_profile_sensor_data)

    def callback(self, message):
        """Publish the annotated camera frame."""
        image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")
        annotated = self.model(image)[0].plot(show=False)
        self.publisher.publish(self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8"))

def main(args=None):
    """Start the ROS2 node."""
    rclpy.init(args=args)
    node = UltralyticsNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()

对于深度图像,请复用下面的深度处理代码,仅替换消息获取和转换部分:

self.create_subscription(Image, "/camera/color/image_raw", self.rgb_callback, qos_profile_sensor_data)
self.create_subscription(Image, "/camera/depth/image_raw", self.depth_callback, qos_profile_sensor_data)

def rgb_callback(self, message):
    self.rgb_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")

def depth_callback(self, message):
    depth_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="passthrough")
    # Apply the NumPy mask and distance calculation from the depth example below.

对于点云,ROS2 提供了 sensor_msgs_py.point_cloud2;请先转换一次有组织点云,然后复用下面的 NumPy 分割和三维映射流程:

from sensor_msgs_py import point_cloud2

points = point_cloud2.read_points_numpy(message, field_names=("x", "y", "z", "rgb"))
points = points.reshape(message.height, message.width, 4)

将 Ultralytics 与 ROS sensor_msgs/Image 一起使用#

sensor_msgs/Image 消息类型通常用于 ROS 中表示图像数据。它包含编码方式、高度、宽度和像素数据等字段,适合传输相机或其他传感器采集的图像。图像消息广泛用于机器人应用中的视觉感知、目标检测和导航等任务。

Detection and Segmentation in ROS Gazebo

图像分步使用方法#

以下代码片段演示了如何将 Ultralytics YOLO 软件包与 ROS 结合使用。在此示例中,我们订阅相机话题,使用 YOLO 处理传入图像,并将检测到的对象发布到用于检测分割的新话题。

首先,导入所需库并实例化两个模型:一个用于分割,另一个用于检测。初始化一个 ROS 节点(名称为 ultralytics),以启用与 ROS master 的通信。为确保连接稳定,我们加入短暂暂停,为节点建立连接留出足够时间,然后再继续。

import time

import rospy

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
segmentation_model = YOLO("yolo26m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)

初始化两个 ROS 话题:一个用于检测,另一个用于分割。这些话题用于发布标注后的图像,使其可供进一步处理。节点之间通过 sensor_msgs/Image 消息进行通信。

from sensor_msgs.msg import Image

det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)

最后,创建一个订阅器,监听 /camera/color/image_raw 话题上的消息,并针对每条新消息调用回调函数。此回调函数接收 sensor_msgs/Image 类型的消息,使用 ros_numpy 将其转换为 NumPy 数组,使用之前实例化的 YOLO 模型处理图像,为图像添加标注,然后将其分别发布回用于检测的 /ultralytics/detection/image 话题和用于分割的 /ultralytics/segmentation/image 话题。

import ros_numpy

def callback(data):
    """Callback function to process image and publish annotated images."""
    array = ros_numpy.numpify(data)
    if det_image_pub.get_num_connections():
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))

    if seg_image_pub.get_num_connections():
        seg_result = segmentation_model(array)
        seg_annotated = seg_result[0].plot(show=False)
        seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))

rospy.Subscriber("/camera/color/image_raw", Image, callback)

while True:
    rospy.spin()
完整代码
import time

import ros_numpy
import rospy
from sensor_msgs.msg import Image

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
segmentation_model = YOLO("yolo26m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)

det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)

def callback(data):
    """Callback function to process image and publish annotated images."""
    array = ros_numpy.numpify(data)
    if det_image_pub.get_num_connections():
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))

    if seg_image_pub.get_num_connections():
        seg_result = segmentation_model(array)
        seg_annotated = seg_result[0].plot(show=False)
        seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))

rospy.Subscriber("/camera/color/image_raw", Image, callback)

while True:
    rospy.spin()
调试

由于 ROS(机器人操作系统)节点具有分布式特性,调试这些节点可能颇具挑战。以下工具可以协助完成调试:

  1. rostopic echo <TOPIC-NAME>:此命令可以查看特定话题上发布的消息,帮助你检查数据流。
  2. rostopic list:使用此命令列出 ROS 系统中的所有可用话题,了解当前活动的数据流。
  3. rqt_graph:此可视化工具显示节点之间的通信图,帮助你了解节点如何互联以及如何交互。
  4. 对于更复杂的可视化(例如三维表示),你可以使用 RViz。RViz(ROS 可视化)是 ROS 的强大三维可视化工具。它可以实时显示机器人及其环境的状态。借助 RViz,你可以查看传感器数据(例如 sensor_msgs/Image)、机器人模型状态以及各种其他类型的信息,从而更轻松地调试和理解机器人系统的行为。

使用 std_msgs/String 发布检测到的类别#

标准 ROS 消息还包括 std_msgs/String 消息。在许多应用中,无需重新发布完整的标注图像;只需发布机器人视野中存在的类别即可。以下示例演示了如何使用 std_msgs/String 消息,将检测到的类别重新发布到 /ultralytics/detection/classes 话题。这些消息更加轻量,并能提供关键信息,因此适用于各种应用。

示例用例#

设想一台配备相机和目标检测模型的仓库机器人。机器人无需通过网络发送大型标注图像,而是可以将检测到的类别列表作为 std_msgs/String 消息发布。例如,当机器人检测到“箱子”“托盘”和“叉车”等对象时,它会将这些类别发布到 /ultralytics/detection/classes 话题。中央监控系统随后可以使用这些信息实时跟踪库存、优化机器人的路径规划以避开障碍物,或触发拾取检测到的箱子等特定操作。这种方法可以降低通信所需的带宽,并专注于传输关键数据。

字符串分步使用方法#

此示例演示了如何将 Ultralytics YOLO 软件包与 ROS 结合使用。在此示例中,我们订阅相机话题,使用 YOLO 处理传入图像,并使用 std_msgs/String 消息将检测到的对象发布到新话题 /ultralytics/detection/classesros_numpy 软件包用于将 ROS Image 消息转换为 NumPy 数组,以便使用 YOLO 进行处理。

import time

import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
rospy.init_node("ultralytics")
time.sleep(1)
classes_pub = rospy.Publisher("/ultralytics/detection/classes", String, queue_size=5)

def callback(data):
    """Callback function to process image and publish detected classes."""
    array = ros_numpy.numpify(data)
    if classes_pub.get_num_connections():
        det_result = detection_model(array)
        classes = det_result[0].boxes.cls.cpu().numpy().astype(int)
        names = [det_result[0].names[i] for i in classes]
        classes_pub.publish(String(data=str(names)))

rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
    rospy.spin()

将 Ultralytics 与 ROS 深度图像结合使用#

除了 RGB 图像外,ROS 还支持深度图像,可提供对象与相机之间的距离信息。深度图像对于避障、三维映射和定位等机器人应用至关重要。

深度图像中的每个像素都表示相机到某个对象的距离。RGB 图像用于捕获颜色,而深度图像用于捕获空间信息,使机器人能够感知环境的三维结构。

获取深度图像

可以使用各种传感器获取深度图像:

  1. 双目相机:使用两个相机,根据图像视差计算深度。
  2. 飞行时间(ToF)相机:测量光线从对象返回所需的时间。
  3. 结构光传感器:投射图案并测量其在表面上的形变。

使用 YOLO 处理深度图像#

在 ROS 中,深度图像由 sensor_msgs/Image 消息类型表示,其中包含编码方式、高度、宽度和像素数据等字段。深度图像的编码字段通常使用类似“16UC1”的格式,表示每个像素使用 16 位无符号整数,其中每个值代表到对象的距离。深度图像通常与 RGB 图像结合使用,以提供更全面的环境视图。

使用 YOLO 可以提取并融合 RGB 图像和深度图像中的信息。例如,YOLO 可以在 RGB 图像中检测对象,然后利用检测结果在深度图像中定位对应区域。这样可以提取检测对象的精确深度信息,增强机器人对环境三维结构的理解能力。

RGB-D 相机

处理深度图像时,必须确保 RGB 图像和深度图像正确对齐。RGB-D 相机(例如 Intel RealSense 系列)可以提供同步的 RGB 图像和深度图像,从而更容易融合两种来源的信息。如果使用独立的 RGB 相机和深度相机,则必须对它们进行校准,以确保准确对齐。

深度图像分步使用方法#

在此示例中,我们使用 YOLO 分割图像,并将提取的掩码应用于深度图像中的对象。这样可以确定目标对象每个像素相对于相机焦点中心的距离。获得这些距离信息后,我们就能计算相机与场景中特定对象之间的距离。首先导入所需库,创建 ROS 节点,并实例化一个分割模型和一个 ROS 话题。

import time

import rospy
from std_msgs.msg import String

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)

segmentation_model = YOLO("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

接下来,定义一个用于处理传入深度图像消息的回调函数。该函数等待深度图像和 RGB 图像消息,将它们转换为 NumPy 数组,并对 RGB 图像应用分割模型。随后,它提取每个检测对象的分割掩码,并使用深度图像计算对象到相机的平均距离。大多数传感器都有一个最大距离,称为裁剪距离;超过该距离的值会表示为 inf(np.inf)。处理前,必须过滤掉这些空值,并为其赋值 0。最后,将检测到的对象及其平均距离发布到 /ultralytics/detection/distance 话题。

import numpy as np
import ros_numpy
from sensor_msgs.msg import Image

def callback(data):
    """Callback function to process depth image and RGB image."""
    image = rospy.wait_for_message("/camera/color/image_raw", Image)
    image = ros_numpy.numpify(image)
    depth = ros_numpy.numpify(data)
    result = segmentation_model(image)

    all_objects = []
    for index, cls in enumerate(result[0].boxes.cls):
        class_index = int(cls.cpu().numpy())
        name = result[0].names[class_index]
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        obj = depth[mask == 1]
        obj = obj[~np.isnan(obj)]
        avg_distance = np.mean(obj) if len(obj) else np.inf
        all_objects.append(f"{name}: {avg_distance:.2f}m")

    classes_pub.publish(String(data=str(all_objects)))

rospy.Subscriber("/camera/depth/image_raw", Image, callback)

while True:
    rospy.spin()
完整代码
import time

import numpy as np
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)

segmentation_model = YOLO("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

def callback(data):
    """Callback function to process depth image and RGB image."""
    image = rospy.wait_for_message("/camera/color/image_raw", Image)
    image = ros_numpy.numpify(image)
    depth = ros_numpy.numpify(data)
    result = segmentation_model(image)

    all_objects = []
    for index, cls in enumerate(result[0].boxes.cls):
        class_index = int(cls.cpu().numpy())
        name = result[0].names[class_index]
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        obj = depth[mask == 1]
        obj = obj[~np.isnan(obj)]
        avg_distance = np.mean(obj) if len(obj) else np.inf
        all_objects.append(f"{name}: {avg_distance:.2f}m")

    classes_pub.publish(String(data=str(all_objects)))

rospy.Subscriber("/camera/depth/image_raw", Image, callback)

while True:
    rospy.spin()

将 Ultralytics 与 ROS sensor_msgs/PointCloud2 一起使用#

Detection and Segmentation in ROS Gazebo

sensor_msgs/PointCloud2 消息类型是 ROS 中用于表示三维点云数据的数据结构。此消息类型是机器人应用的重要组成部分,可支持三维映射、对象识别和定位等任务。

点云是在三维坐标系中定义的数据点集合。这些数据点表示通过三维扫描技术捕获的对象或场景外部表面。点云中的每个点都有 XYZ 坐标,分别对应其空间位置,还可能包含颜色和强度等附加信息。

参考坐标系

使用 sensor_msgs/PointCloud2 时,必须考虑获取点云数据的传感器参考坐标系。点云最初是在传感器的参考坐标系中捕获的。你可以通过监听 /tf_static 话题来确定此参考坐标系。不过,根据具体应用需求,你可能需要将点云转换到另一个参考坐标系。可以使用 tf2_ros 软件包完成这一变换,该软件包提供了管理坐标系以及在不同坐标系之间转换数据的工具。

获取点云

可以使用各种传感器获取点云:

  1. LIDAR(光探测和测距):使用激光脉冲测量与对象之间的距离,并创建高精度三维地图。
  2. 深度相机:捕获每个像素的深度信息,从而实现对场景的三维重建。
  3. 双目相机:使用两个或更多相机,通过三角测量获取深度信息。
  4. 结构光扫描仪:将已知图案投射到表面上,并测量其形变以计算深度。

使用 YOLO 处理点云#

要将 YOLO 与 sensor_msgs/PointCloud2 类型的消息集成,可以采用类似深度图所使用的方法。利用点云中包含的颜色信息,我们可以提取二维图像,使用 YOLO 对该图像执行分割,然后将生成的掩码应用于三维点,以分离出目标三维对象。

对于点云处理,我们建议使用 Open3D(pip install open3d),这是一个易于使用的 Python 库。Open3D 提供了管理点云数据结构、可视化点云以及无缝执行复杂操作的强大工具。该库可以显著简化处理流程,并增强我们结合基于 YOLO 的分割来操作和分析点云的能力。

点云分步使用方法#

导入所需库,并实例化用于分割的 YOLO 模型。

import time

import rospy

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

创建函数 pointcloud2_to_array,将 sensor_msgs/PointCloud2 消息转换为两个 NumPy 数组。sensor_msgs/PointCloud2 消息包含基于所采集图像的 widthheightn 个点。例如,480 x 640 图像将包含 307,200 个点。每个点包含三个空间坐标(xyz)以及 RGB 格式的对应颜色。这些可以视为两个独立的信息通道。

该函数以原始相机分辨率(width x height)的格式返回 xyz 坐标和 RGB 值。大多数传感器都有一个最大距离,称为裁剪距离;超过该距离的值会表示为 inf(np.inf)。处理前,必须过滤掉这些空值,并为其赋值 0

import numpy as np
import ros_numpy

def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
    """Convert a ROS PointCloud2 message to a numpy array.

    Args:
        pointcloud2 (PointCloud2): the PointCloud2 message

    Returns:
        (tuple): tuple containing (xyz, rgb)
    """
    pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
    split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
    rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
    xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
    xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
    nan_rows = np.isnan(xyz).all(axis=2)
    xyz[nan_rows] = [0, 0, 0]
    rgb[nan_rows] = [0, 0, 0]
    return xyz, rgb

接下来,订阅 /camera/depth/points 话题以接收点云消息,并使用 pointcloud2_to_array 函数将 sensor_msgs/PointCloud2 消息转换为包含 XYZ 坐标和 RGB 值的 NumPy 数组。使用 YOLO 模型处理 RGB 图像,以提取分割对象。对于每个检测到的对象,提取分割掩码,并将其应用于 RGB 图像和 XYZ 坐标,以分离出三维空间中的对象。

由于掩码由二进制值组成,因此处理起来很简单:1 表示对象存在,0 表示对象不存在。要应用掩码,只需将原始通道乘以掩码即可。此操作可以有效分离图像中的目标对象。最后,创建 Open3D 点云对象,并使用关联颜色在三维空间中可视化分割后的对象。

import sys

import open3d as o3d

ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)

if not len(result[0].boxes.cls):
    print("No objects detected")
    sys.exit()

classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
    mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
    mask_expanded = np.stack([mask, mask, mask], axis=2)

    obj_rgb = rgb * mask_expanded
    obj_xyz = xyz * mask_expanded

    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
    pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
    o3d.visualization.draw_geometries([pcd])
完整代码
import sys
import time

import numpy as np
import open3d as o3d
import ros_numpy
import rospy
from sensor_msgs.msg import PointCloud2

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
    """Convert a ROS PointCloud2 message to a numpy array.

    Args:
        pointcloud2 (PointCloud2): the PointCloud2 message

    Returns:
        (tuple): tuple containing (xyz, rgb)
    """
    pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
    split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
    rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
    xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
    xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
    nan_rows = np.isnan(xyz).all(axis=2)
    xyz[nan_rows] = [0, 0, 0]
    rgb[nan_rows] = [0, 0, 0]
    return xyz, rgb

ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)

if not len(result[0].boxes.cls):
    print("No objects detected")
    sys.exit()

classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
    mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
    mask_expanded = np.stack([mask, mask, mask], axis=2)

    obj_rgb = rgb * mask_expanded
    obj_xyz = xyz * mask_expanded

    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
    pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
    o3d.visualization.draw_geometries([pcd])

Point Cloud Segmentation with Ultralytics

结论#

将 Ultralytics YOLO 集成到 ROS 后,你的机器人就能在 RGB 图像、深度图像和点云上运行目标检测分割,将原始传感器流转化为可执行的感知结果。接下来,你可以探索预测模式以了解更多推理选项,或按照计算机视觉项目的步骤,将机器人应用从原型推进到生产环境。

常见问题#

  • 机器人操作系统(ROS)是一个开源框架,广泛用于机器人领域,帮助开发者创建健壮的机器人应用。它提供了一系列用于构建机器人系统并与之交互的库和工具,让复杂应用的开发更加容易。ROS 支持节点之间通过话题服务上的消息进行通信。

  • 将 Ultralytics YOLO 与 ROS 集成,需要设置 ROS 环境并使用 YOLO 处理传感器数据。首先安装所需依赖项,例如 ros_numpy 和 Ultralytics YOLO:

    pip install ros_numpy ultralytics

    接下来,创建一个 ROS 节点并订阅图像话题,以处理传入数据并执行目标检测。下面是一个最小示例:

    import ros_numpy
    import rospy
    from sensor_msgs.msg import Image
    
    from ultralytics import YOLO
    
    detection_model = YOLO("yolo26m.pt")
    rospy.init_node("ultralytics")
    det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
    
    def callback(data):
        array = ros_numpy.numpify(data)
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
    
    rospy.Subscriber("/camera/color/image_raw", Image, callback)
    rospy.spin()
  • ROS 话题通过发布-订阅模型促进 ROS 网络中节点之间的通信。话题是节点用于异步发送和接收消息的命名通道。在 Ultralytics YOLO 中,你可以让一个节点订阅图像话题,使用 YOLO 处理图像以执行检测分割等任务,然后将结果发布到新话题。

    例如,订阅相机话题并处理传入图像以执行检测:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • ROS 中由 sensor_msgs/Image 表示的深度图像可以提供对象与相机之间的距离,这对于避障、三维映射和定位等任务至关重要。通过将深度信息与 RGB 图像结合使用,机器人可以更好地理解三维环境。

    借助 YOLO,你可以从 RGB 图像中提取分割掩码,并将这些掩码应用于深度图像,以获取精确的三维对象信息,从而提升机器人导航和与周围环境交互的能力。

  • 使用 YOLO 在 ROS 中可视化 3D 点云:

    1. sensor_msgs/PointCloud2 消息转换为 NumPy 数组。
    2. 使用 YOLO 对 RGB 图像进行分割。
    3. 将分割掩码应用于点云。

    下面是一个使用 Open3D 进行可视化的示例:

    import sys
    
    import numpy as np
    import open3d as o3d
    import ros_numpy
    import rospy
    from sensor_msgs.msg import PointCloud2
    
    from ultralytics import YOLO
    
    rospy.init_node("ultralytics")
    segmentation_model = YOLO("yolo26m-seg.pt")
    
    def pointcloud2_to_array(pointcloud2):
        pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
        split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
        rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
        xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
        xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
        return xyz, rgb
    
    ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
    xyz, rgb = pointcloud2_to_array(ros_cloud)
    result = segmentation_model(rgb)
    
    if not len(result[0].boxes.cls):
        print("No objects detected")
        sys.exit()
    
    classes = result[0].boxes.cls.cpu().numpy().astype(int)
    for index, class_id in enumerate(classes):
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        mask_expanded = np.stack([mask, mask, mask], axis=2)
    
        obj_rgb = rgb * mask_expanded
        obj_xyz = xyz * mask_expanded
    
        pcd = o3d.geometry.PointCloud()
        pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((-1, 3)))
        pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((-1, 3)) / 255)
        o3d.visualization.draw_geometries([pcd])

    这种方法可以对分割后的对象进行 3D 可视化,适用于机器人应用中的导航和操作等任务。

评论