Ultralytics YOLO27:

Краткое руководство по ROS (операционной системе для роботов)#

Это руководство показывает, как интегрировать Ultralytics YOLO с ROS1 (rospy) или ROS2 (rclpy) для выполнения обнаружения объектов и сегментации в реальном времени на изображениях RGB, изображениях глубины и облаках точек.

Перейди к разделу настройки YOLO с ROS, а затем работай с изображениями RGB, изображениями глубины или облаками точек.

Что такое ROS?#

ROS (операционная система для роботов) — это среда с открытым исходным кодом, широко используемая в робототехнических исследованиях и промышленности. ROS предоставляет набор библиотек и инструментов, помогающих разработчикам создавать приложения для роботов. ROS рассчитана на работу с различными робототехническими платформами, что делает её гибким и мощным инструментом для специалистов по робототехнике. Для краткого знакомства посмотри трёхминутное видео Введение в ROS от Open Robotics.

Ключевые возможности ROS#

  1. Модульная архитектура: ROS имеет модульную архитектуру, позволяющую разработчикам создавать сложные системы, объединяя небольшие повторно используемые компоненты, называемые узлами. Каждый узел обычно выполняет определённую функцию, а узлы обмениваются сообщениями через топики или сервисы.

  2. Коммуникационное промежуточное ПО: ROS предоставляет надёжную инфраструктуру связи, поддерживающую межпроцессное взаимодействие и распределённые вычисления. Это достигается с помощью модели публикации и подписки для потоков данных (топиков) и модели запросов и ответов для вызовов сервисов.

  3. Абстракция аппаратного обеспечения: ROS предоставляет уровень абстракции над аппаратным обеспечением, позволяя разработчикам писать код, не зависящий от устройства. Благодаря этому один и тот же код можно использовать с разными аппаратными конфигурациями, упрощая интеграцию и эксперименты.

  4. Инструменты и утилиты: ROS включает богатый набор инструментов и утилит для визуализации, отладки и моделирования. Например, RViz используется для визуализации данных датчиков и информации о состоянии робота, а Gazebo предоставляет мощную среду моделирования для тестирования алгоритмов и конструкций роботов.

  5. Обширная экосистема: Экосистема ROS велика и постоянно развивается; для различных робототехнических задач доступно множество пакетов, включая навигацию, манипуляции, восприятие и другие. Сообщество активно участвует в разработке и сопровождении этих пакетов.

Эволюция версий ROS

С момента разработки в 2007 году ROS прошла через несколько версий, разделившись на ROS 1 и ROS 2. Приведённые ниже примеры используют ROS1 Noetic; компактные адаптеры в разделе Использование ROS2 показывают соответствующие интерфейсы rclpy для актуальных выпусков ROS2.

ROS 1 и ROS 2#

ROS 1 заложила прочную основу для разработки робототехнических систем, а ROS 2 устраняет её недостатки, предлагая:

  • Производительность в реальном времени: улучшенную поддержку систем реального времени и детерминированного поведения.
  • Безопасность: расширенные функции безопасности для безопасной и надёжной работы в различных средах.
  • Масштабируемость: улучшенную поддержку многороботных систем и крупномасштабных развертываний.
  • Кроссплатформенная поддержка: расширенную совместимость с различными операционными системами помимо Linux, включая Windows и macOS.
  • Гибкая коммуникация: использование DDS для более гибкого и эффективного межпроцессного взаимодействия.

Сообщения и топики ROS#

В ROS взаимодействие между узлами обеспечивается с помощью сообщений и топиков. Сообщение — это структура данных, определяющая информацию, которой обмениваются узлы, а топик — именованный канал, по которому сообщения отправляются и принимаются. Узлы могут публиковать сообщения в топик или подписываться на сообщения из топика, что позволяет им взаимодействовать друг с другом. Эта модель публикации и подписки обеспечивает асинхронное взаимодействие и слабую связанность между узлами. Каждый датчик или исполнительный механизм в робототехнической системе обычно публикует данные в топик, после чего другие узлы могут использовать их для обработки или управления. В этом руководстве мы сосредоточимся на сообщениях Image, Depth и PointCloud, а также на топиках камер.

Настройка Ultralytics YOLO с ROS#

Примеры для ROS1 тестировались с использованием этой среды ROS — форка репозитория ROS для ROSbot. Та же обработка с помощью YOLO и NumPy применяется в ROS2; отличаются только жизненный цикл узла и преобразование сообщений.

Husarion ROSbot 2 PRO autonomous robot platform

Установка зависимостей#

Помимо среды ROS, тебе потребуется установить следующие зависимости:

  • Пакет ROS NumPy: требуется для быстрого преобразования сообщений ROS Image в массивы NumPy и обратно.

    pip install ros_numpy
  • Пакет Ultralytics:

    pip install ultralytics

Использование ROS2#

ROS2 заменяет rospy на rclpy, а преобразование изображений ros_numpy — на cv_bridge. Следующий узел является полным эквивалентом приведённого ниже процесса обнаружения 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 и отображение в 3D:

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 для представления данных изображений. Он содержит поля кодировки, высоты, ширины и данных пикселей, что делает его подходящим для передачи изображений, полученных камерами или другими датчиками. Сообщения Image широко используются в робототехнических приложениях для таких задач, как визуальное восприятие, обнаружение объектов и навигация.

Detection and Segmentation in ROS Gazebo

Пошаговое использование Image#

Следующий фрагмент кода демонстрирует использование пакета Ultralytics YOLO с ROS. В этом примере мы подписываемся на топик камеры, обрабатываем входящее изображение с помощью YOLO и публикуем обнаруженные объекты в новые топики для обнаружения и сегментации.

Сначала импортируй необходимые библиотеки и создай две модели: одну для сегментации, другую для обнаружения. Инициализируй узел ROS с именем ultralytics, чтобы включить взаимодействие с мастером ROS. Для стабильного подключения мы добавляем небольшую паузу, чтобы узел успел установить соединение перед продолжением работы.

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, преобразует их в массив NumPy с помощью ros_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. Для более сложных визуализаций, например 3D-представлений, можно использовать RViz. RViz (визуализация ROS) — мощный инструмент 3D-визуализации для ROS. Он позволяет визуализировать состояние робота и его окружения в реальном времени. С помощью RViz можно просматривать данные датчиков (например, sensor_msgs/Image), состояния модели робота и различные другие типы информации, что упрощает отладку и понимание поведения робототехнической системы.

Публикация обнаруженных классов с помощью std_msgs/String#

Стандартные сообщения ROS также включают сообщения std_msgs/String. Во многих приложениях нет необходимости повторно публиковать всё размеченное изображение — достаточно классов, присутствующих в поле зрения робота. Следующий пример демонстрирует использование сообщений std_msgs/String для повторной публикации обнаруженных классов в топик /ultralytics/detection/classes. Эти сообщения имеют меньший объём и содержат основную информацию, что делает их полезными для различных приложений.

Пример использования#

Рассмотрим складского робота, оснащённого камерой и моделью обнаружения объектов. Вместо передачи больших размеченных изображений по сети робот может публиковать список обнаруженных классов в виде сообщений std_msgs/String. Например, обнаружив такие объекты, как «коробка», «палета» и «погрузчик», робот публикует эти классы в топик /ultralytics/detection/classes. Затем центральная система мониторинга может использовать эту информацию для отслеживания запасов в реальном времени, оптимизации маршрута робота для обхода препятствий или запуска определённых действий, например подбора обнаруженной коробки. Такой подход снижает требуемую для связи пропускную способность и позволяет сосредоточиться на передаче критически важных данных.

Пошаговое использование String#

Этот пример демонстрирует использование пакета Ultralytics YOLO с ROS. В нём мы подписываемся на топик камеры, обрабатываем входящее изображение с помощью YOLO и публикуем обнаруженные объекты в новый топик /ultralytics/detection/classes с использованием сообщений std_msgs/String. Пакет ros_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 поддерживает изображения глубины, содержащие информацию о расстоянии от объектов до камеры. Изображения глубины крайне важны для таких робототехнических задач, как обход препятствий, 3D-картографирование и локализация.

Изображение глубины — это изображение, в котором каждый пиксель представляет расстояние от камеры до объекта. В отличие от изображений RGB, фиксирующих цвет, изображения глубины фиксируют пространственную информацию, позволяя роботам воспринимать 3D-структуру окружения.

Получение изображений глубины

Изображения глубины можно получать с помощью различных датчиков:

  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 для представления данных 3D-облака точек. Этот тип сообщения важен для робототехнических приложений, обеспечивая такие задачи, как 3D-картографирование, распознавание объектов и локализация.

Облако точек — это набор точек данных, заданных в трёхмерной системе координат. Эти точки представляют внешнюю поверхность объекта или сцены, полученную с помощью технологий 3D-сканирования. Каждая точка облака имеет координаты X, Y и Z, соответствующие её положению в пространстве, а также может содержать дополнительную информацию, например цвет и интенсивность.

Система координат

При работе с sensor_msgs/PointCloud2 важно учитывать систему координат датчика, с которого были получены данные облака точек. Изначально облако точек создаётся в системе координат датчика. Определить эту систему можно, прослушивая топик /tf_static. Однако в зависимости от требований конкретного приложения может потребоваться преобразовать облако точек в другую систему координат. Это преобразование выполняется с помощью пакета tf2_ros, предоставляющего инструменты для управления системами координат и преобразования данных между ними.

Получение облаков точек

Облака точек можно получать с помощью различных датчиков:

  1. LIDAR (обнаружение и определение дальности с помощью света): использует лазерные импульсы для измерения расстояний до объектов и создания 3D-карт высокой точности.
  2. Камеры глубины: захватывают информацию о глубине для каждого пикселя, позволяя выполнять 3D-реконструкцию сцены.
  3. Стереокамеры: используют две или более камеры для получения информации о глубине методом триангуляции.
  4. Сканеры структурированного света: проецируют известный шаблон на поверхность и измеряют его деформацию для расчёта глубины.

Использование YOLO с облаками точек#

Для интеграции YOLO с сообщениями типа sensor_msgs/PointCloud2 можно использовать метод, аналогичный применяемому для карт глубины. Извлекая информацию о цвете, содержащуюся в облаке точек, мы можем получить 2D-изображение, выполнить его сегментацию с помощью YOLO, а затем применить полученную маску к трёхмерным точкам, чтобы выделить интересующий 3D-объект.

Для работы с облаками точек мы рекомендуем использовать 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 содержат точки n, основанные на width и height полученного изображения. Например, изображение 480 x 640 будет содержать 307,200 точек. Каждая точка включает три пространственные координаты (xyz) и соответствующий цвет в формате RGB. Их можно рассматривать как два отдельных канала информации.

Функция возвращает координаты xyz и значения RGB в формате исходного разрешения камеры (width x height). У большинства датчиков есть максимальное расстояние, называемое дистанцией отсечения, за пределами которого значения представлены как 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, чтобы получать сообщения облака точек, и преобразуй сообщение sensor_msgs/PointCloud2 в массивы NumPy, содержащие координаты XYZ и значения RGB (с помощью функции pointcloud2_to_array). Обработай изображение RGB моделью YOLO, чтобы извлечь сегментированные объекты. Для каждого обнаруженного объекта извлеки маску сегментации и примени её к изображению RGB и координатам XYZ, чтобы выделить объект в 3D-пространстве.

Обработка маски проста, поскольку она состоит из бинарных значений: 1 обозначает присутствие объекта, а 0 — его отсутствие. Чтобы применить маску, просто умножь исходные каналы на маску. Эта операция эффективно выделяет интересующий объект на изображении. Наконец, создай объект облака точек Open3D и визуализируй сегментированный объект в 3D-пространстве вместе с соответствующими цветами.

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, изображениях глубины и облаках точек, превращая необработанные потоки данных датчиков в полезную информацию для восприятия. Теперь изучи режим Predict, чтобы узнать о других вариантах инференса, или следуй этапам проекта компьютерного зрения, чтобы перевести своё робототехническое приложение от прототипа к промышленной эксплуатации.

Часто задаваемые вопросы#

  • 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, содержат расстояние от объектов до камеры, что крайне важно для таких задач, как обход препятствий, 3D-картографирование и локализация. Используя информацию о глубине вместе с изображениями RGB, роботы могут лучше понимать своё 3D-окружение.

    С помощью YOLO можно извлекать маски сегментации из изображений RGB и применять их к изображениям глубины, чтобы получать точную 3D-информацию об объектах и улучшать способность робота перемещаться и взаимодействовать с окружением.

  • Чтобы визуализировать 3D-облака точек в ROS с помощью YOLO:

    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-визуализацию сегментированных объектов, что полезно для таких задач, как навигация и манипуляции в робототехнических приложениях.

Комментарии