Краткое руководство по ROS (операционной системе для роботов)#
В этом руководстве показано, как интегрировать Ultralytics YOLO с ROS1 (rospy) или ROS2 (rclpy) для обнаружения объектов и сегментации в реальном времени на RGB-изображениях, изображениях глубины и облаках точек.
Перейди к разделу настройки YOLO с ROS, а затем работай с RGB-изображениями, изображениями глубины или облаками точек.
Что такое ROS?#
Операционная система для роботов (ROS) — это фреймворк с открытым исходным кодом, широко используемый в робототехнических исследованиях и промышленности. ROS предоставляет набор библиотек и инструментов, помогающих разработчикам создавать приложения для роботов. ROS разработана для работы с различными робототехническими платформами, что делает её гибким и мощным инструментом для специалистов по робототехнике. Краткое введение смотри в трехминутном видео «Введение в ROS» от Open Robotics.
Основные возможности ROS#
-
Модульная архитектура: ROS имеет модульную архитектуру, позволяющую разработчикам создавать сложные системы из небольших повторно используемых компонентов, называемых узлами. Каждый узел обычно выполняет определенную функцию, а узлы обмениваются сообщениями через топики или сервисы.
-
Коммуникационная инфраструктура: ROS предоставляет надежную инфраструктуру связи, поддерживающую межпроцессное взаимодействие и распределенные вычисления. Для потоков данных (топиков) используется модель публикации-подписки, а для вызова сервисов — модель запрос-ответ.
-
Аппаратная абстракция: ROS предоставляет уровень абстракции над аппаратным обеспечением, позволяя разработчикам писать код, не зависящий от устройства. Благодаря этому один и тот же код можно использовать с различными аппаратными конфигурациями, упрощая интеграцию и эксперименты.
-
Инструменты и утилиты: ROS включает широкий набор инструментов и утилит для визуализации, отладки и моделирования. Например, RViz используют для визуализации данных датчиков и сведений о состоянии робота, а Gazebo предоставляет мощную среду моделирования для тестирования алгоритмов и конструкций роботов.
-
Развитая экосистема: экосистема 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 — форка репозитория ROSbot ROS. Та же обработка с помощью YOLO и NumPy применима и в ROS2; различаются только жизненный цикл узла и преобразование сообщений.
Установка зависимостей#
Помимо окружения ROS, тебе нужно установить следующие зависимости:
-
Пакет ROS NumPy: он необходим для быстрого преобразования сообщений ROS Image в массивы NumPy.
pip install ros_numpy -
Пакет Ultralytics:
pip install ultralytics
Использование ROS2#
В ROS2 вместо rospy используется rclpy, а для преобразования изображений — 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")
# Примени маску NumPy и вычисление расстояния из приведённого ниже примера обработки глубины.Для облаков точек в ROS2 предусмотрен sensor_msgs_py.point_cloud2; преобразуй организованное облако один раз, а затем повторно используй приведённые ниже сегментацию и 3D-сопоставление на 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 для представления данных изображений. Он содержит поля для кодировки, высоты, ширины и данных пикселей, поэтому подходит для передачи изображений, полученных с камер или других датчиков. Сообщения 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 (Robot Operating System) бывает непросто из-за распределённой природы системы. В этом могут помочь несколько инструментов:
rostopic echo <TOPIC-NAME>: эта команда позволяет просматривать сообщения, опубликованные в определённом топике, и анализировать поток данных.rostopic list: используй эту команду, чтобы вывести список всех доступных топиков в системе ROS и получить представление об активных потоках данных.rqt_graph: этот инструмент визуализации показывает граф связей между узлами и помогает понять, как узлы соединены и взаимодействуют.- Для более сложных визуализаций, например трёхмерных, можно использовать RViz. RViz (ROS Visualization) — мощный инструмент трёхмерной визуализации для 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 и публикуем обнаруженные объекты в новый топик /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-изображений, передающих цвет, изображения глубины содержат пространственную информацию, благодаря которой роботы могут воспринимать трёхмерную структуру окружения.
Изображения глубины можно получать с помощью различных датчиков:
- Стереокамеры: используют две камеры для вычисления глубины по разнице изображений.
- Камеры времени пролёта (ToF): измеряют время, за которое свет отражается от объекта и возвращается обратно.
- Датчики структурированного света: проецируют узор и измеряют его деформацию на поверхностях.
Использование YOLO с изображениями глубины#
В ROS изображения глубины представлены сообщениями типа sensor_msgs/Image, которые содержат поля для кодировки, высоты, ширины и данных пикселей. Для изображений глубины часто используется кодировка вида «16UC1»: она обозначает беззнаковое 16-битное целое число на пиксель, где каждое значение соответствует расстоянию до объекта. Изображения глубины обычно используются вместе с RGB-изображениями, чтобы получить более полное представление об окружении.
С помощью YOLO можно извлекать и объединять информацию из RGB-изображений и изображений глубины. Например, YOLO может обнаружить объекты на RGB-изображении, а результаты обнаружения можно использовать, чтобы определить соответствующие области на изображении глубины. Это позволяет получать точные данные о глубине обнаруженных объектов и помогает роботу лучше понимать трёхмерное окружение.
При работе с изображениями глубины важно правильно выровнять 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, retina_masks=True) # masks at the original image resolution
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, retina_masks=True) # masks at the original image resolution
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#
Тип сообщения sensor_msgs/PointCloud2 — это структура данных, используемая в ROS для представления трёхмерных облаков точек. Этот тип сообщений играет важную роль в робототехнических приложениях, обеспечивая такие задачи, как 3D-картографирование, распознавание объектов и локализация.
Облако точек — это набор точек данных в трёхмерной системе координат. Эти точки представляют внешнюю поверхность объекта или сцены, полученную с помощью технологий 3D-сканирования. У каждой точки облака есть координаты X, Y и Z, указывающие её положение в пространстве; также она может содержать дополнительные сведения, например цвет и интенсивность.
При работе с sensor_msgs/PointCloud2 важно учитывать систему координат датчика, с которого были получены данные облака точек. Изначально облако точек фиксируется в системе координат датчика. Определить эту систему координат можно, прослушивая топик /tf_static. Однако в зависимости от требований конкретного приложения может понадобиться преобразовать облако точек в другую систему координат. Это преобразование можно выполнить с помощью пакета tf2_ros, в котором есть инструменты для управления системами координат и преобразования данных между ними.
Облака точек можно получать с помощью различных датчиков:
- ЛИДАР (LIDAR): использует лазерные импульсы для измерения расстояния до объектов и создания трёхмерных карт с высокой точностью.
- Камеры глубины: захватывают информацию о глубине для каждого пикселя, что позволяет реконструировать сцену в 3D.
- Стереокамеры: используют две или более камеры для получения информации о глубине методом триангуляции.
- Сканеры структурированного света: проецируют на поверхность известный узор и измеряют его деформацию для вычисления глубины.
Использование 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, чтобы изолировать объект в трёхмерном пространстве.
Обрабатывать маску просто: она состоит из двоичных значений, где 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, retina_masks=True) # маски в исходном разрешении изображения
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, retina_masks=True) # маски в исходном разрешении изображения
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])
Заключение#
Интегрировав 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-изображениями, роботы могут лучше понимать трёхмерное окружение.С помощью YOLO можно извлекать маски сегментации из RGB-изображений и применять их к изображениям глубины, чтобы получать точную трёхмерную информацию об объектах. Это улучшает способность робота перемещаться и взаимодействовать с окружением.
Чтобы визуализировать трёхмерные облака точек с помощью YOLO в ROS:
- Преобразуй сообщения
sensor_msgs/PointCloud2в массивы NumPy. - Используй YOLO для сегментации RGB-изображений.
- Примени маску сегментации к облаку точек.
Вот пример визуализации с помощью 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, retina_masks=True) # маски в исходном разрешении изображения 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 и полезен для таких задач, как навигация и манипулирование объектами в робототехнических приложениях.
- Преобразуй сообщения