Ultralytics YOLO27:
Get Started

Guía de inicio rápido de ROS (sistema operativo para robots)#

Esta guía te muestra cómo integrar Ultralytics YOLO con ROS1 (rospy) o ROS2 (rclpy) para realizar detección de objetos y segmentación en tiempo real en imágenes RGB, imágenes de profundidad y nubes de puntos.

Ve directamente a configurar YOLO con ROS y, después, trabaja con imágenes RGB, imágenes de profundidad o nubes de puntos.

¿Qué es ROS?#

El sistema operativo para robots (ROS) es un framework de código abierto muy utilizado en la investigación y la industria de la robótica. ROS proporciona un conjunto de bibliotecas y herramientas para ayudar a los desarrolladores a crear aplicaciones robóticas. ROS está diseñado para funcionar con distintas plataformas robóticas, lo que lo convierte en una herramienta flexible y potente para los especialistas en robótica. Para una breve introducción, mira el vídeo de tres minutos Introducción a ROS de Open Robotics.

Características principales de ROS#

  1. Arquitectura modular: ROS tiene una arquitectura modular que permite a los desarrolladores crear sistemas complejos combinando componentes más pequeños y reutilizables llamados nodos. Cada nodo suele realizar una función específica, y los nodos se comunican entre sí mediante mensajes a través de tópicos o servicios.

  2. Middleware de comunicación: ROS ofrece una infraestructura de comunicación sólida que admite la comunicación entre procesos y la computación distribuida. Esto se consigue mediante un modelo de publicación-suscripción para los flujos de datos (tópicos) y un modelo de solicitud-respuesta para las llamadas a servicios.

  3. Abstracción del hardware: ROS proporciona una capa de abstracción sobre el hardware que permite a los desarrolladores escribir código independiente del dispositivo. Así se puede utilizar el mismo código con distintas configuraciones de hardware, lo que facilita la integración y la experimentación.

  4. Herramientas y utilidades: ROS incluye un amplio conjunto de herramientas y utilidades para la visualización, la depuración y la simulación. Por ejemplo, RViz se utiliza para visualizar datos de sensores e información sobre el estado del robot, mientras que Gazebo ofrece un potente entorno de simulación para probar algoritmos y diseños de robots.

  5. Ecosistema amplio: El ecosistema de ROS es extenso y está en continuo crecimiento, con numerosos paquetes disponibles para distintas aplicaciones robóticas, como la navegación, la manipulación, la percepción y otras. La comunidad contribuye activamente al desarrollo y mantenimiento de estos paquetes.

Evolución de las versiones de ROS

Desde su desarrollo en 2007, ROS ha evolucionado a través de varias versiones, divididas en ROS 1 y ROS 2. Los ejemplos siguientes utilizan ROS1 Noetic; los adaptadores compactos de Uso de ROS2 muestran las interfaces rclpy correspondientes a las versiones actuales de ROS2.

ROS 1 frente a ROS 2#

Aunque ROS 1 proporcionó una base sólida para el desarrollo robótico, ROS 2 aborda sus limitaciones ofreciendo:

  • Rendimiento en tiempo real: Mejor compatibilidad con los sistemas en tiempo real y el comportamiento determinista.
  • Seguridad: Funciones de seguridad mejoradas para un funcionamiento seguro y fiable en diversos entornos.
  • Escalabilidad: Mejor compatibilidad con sistemas multirrobot y despliegues a gran escala.
  • Compatibilidad multiplataforma: Mayor compatibilidad con distintos sistemas operativos, además de Linux, incluidos Windows y macOS.
  • Comunicación flexible: Uso de DDS para una comunicación entre procesos más flexible y eficiente.

Mensajes y temas de ROS#

En ROS, la comunicación entre nodos se realiza mediante mensajes y temas. Un mensaje es una estructura de datos que define la información intercambiada entre nodos, mientras que un tema es un canal con nombre por el que se envían y reciben mensajes. Los nodos pueden publicar mensajes en un tema o suscribirse a los mensajes de un tema, lo que les permite comunicarse entre sí. Este modelo de publicación-suscripción permite la comunicación asíncrona y el desacoplamiento entre nodos. Normalmente, cada sensor o actuador de un sistema robótico publica datos en un tema, que otros nodos pueden consumir para procesarlos o controlarlos. Para esta guía, nos centraremos en los mensajes Image, Depth y PointCloud, y en los temas de cámara.

Configurar Ultralytics YOLO con ROS#

Los ejemplos de ROS1 se probaron con este entorno de ROS, una bifurcación del repositorio de ROS de ROSbot. El mismo procesamiento de YOLO y NumPy se aplica en ROS2; solo cambian el ciclo de vida del nodo y la conversión de mensajes.

Husarion ROSbot 2 PRO autonomous robot platform

Instalación de dependencias#

Además del entorno de ROS, tendrás que instalar las siguientes dependencias:

  • Paquete ROS NumPy: Es necesario para convertir rápidamente mensajes ROS Image en matrices NumPy.

    pip install ros_numpy
  • Paquete Ultralytics:

    pip install ultralytics

Uso de ROS2#

ROS2 sustituye rospy por rclpy y la conversión de imágenes de ros_numpy por cv_bridge. El siguiente nodo es el equivalente completo en ROS2 del flujo de detección RGB que aparece más abajo; instancia los modelos una sola vez y reutilízalos en las distintas devoluciones de llamada.

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()

Para las imágenes de profundidad, reutiliza el código de procesamiento de profundidad que aparece más abajo y sustituye únicamente la adquisición y conversión de mensajes:

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")
    # Aplica la máscara NumPy y el cálculo de distancias del ejemplo de profundidad que aparece más abajo.

Para las nubes de puntos, ROS2 proporciona sensor_msgs_py.point_cloud2; convierte la nube organizada una sola vez y reutiliza después la segmentación NumPy y el mapeo 3D que aparecen más abajo:

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)

Usar Ultralytics con sensor_msgs/Image de ROS#

El tipo de mensaje sensor_msgs/Image se utiliza habitualmente en ROS para representar datos de imagen. Contiene campos de codificación, altura, anchura y datos de píxeles, por lo que resulta adecuado para transmitir imágenes capturadas por cámaras u otros sensores. Los mensajes Image se utilizan ampliamente en aplicaciones robóticas para tareas como la percepción visual, la detección de objetos y la navegación.

Detection and Segmentation in ROS Gazebo

Uso paso a paso de imágenes#

El siguiente fragmento de código muestra cómo usar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tema de cámara, procesamos la imagen recibida con YOLO y publicamos los objetos detectados en nuevos temas de detección y segmentación.

Primero, importa las bibliotecas necesarias e instancia dos modelos: uno para la segmentación y otro para la detección. Inicializa un nodo ROS (con el nombre ultralytics) para habilitar la comunicación con el maestro de ROS. Para garantizar una conexión estable, hacemos una breve pausa y damos al nodo tiempo suficiente para establecer la conexión antes de continuar.

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)

Inicializa dos temas de ROS: uno para la detección y otro para la segmentación. Estos temas se usarán para publicar las imágenes anotadas y permitir su uso en otros procesos. La comunicación entre nodos se realiza mediante mensajes 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)

Por último, crea un suscriptor que escuche los mensajes del tema /camera/color/image_raw y llame a una función de devolución de llamada por cada mensaje nuevo. Esta función recibe mensajes de tipo sensor_msgs/Image, los convierte en una matriz NumPy mediante ros_numpy, procesa las imágenes con los modelos YOLO instanciados previamente, las anota y, a continuación, las publica en los temas correspondientes: /ultralytics/detection/image para la detección y /ultralytics/segmentation/image para la segmentación.

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()
Código completo
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()
Depuración

Depurar nodos de ROS (Robot Operating System) puede resultar complicado debido a la naturaleza distribuida del sistema. Varias herramientas pueden ayudarte en este proceso:

  1. rostopic echo <TOPIC-NAME> : este comando te permite ver los mensajes publicados en un tema específico y te ayuda a inspeccionar el flujo de datos.
  2. rostopic list: usa este comando para mostrar todos los temas disponibles en el sistema ROS y obtener una visión general de los flujos de datos activos.
  3. rqt_graph: esta herramienta de visualización muestra el grafo de comunicación entre nodos y te permite ver cómo están conectados y cómo interactúan.
  4. Para visualizaciones más complejas, como las representaciones 3D, puedes usar RViz. RViz (visualización de ROS) es una potente herramienta de visualización 3D para ROS. Te permite visualizar el estado de tu robot y su entorno en tiempo real. Con RViz, puedes ver datos de sensores (por ejemplo, sensor_msgs/Image), estados del modelo del robot y otros tipos de información, lo que facilita la depuración y la comprensión del comportamiento de tu sistema robótico.

Publicar las clases detectadas con std_msgs/String#

Los mensajes estándar de ROS también incluyen mensajes std_msgs/String. En muchas aplicaciones, no es necesario volver a publicar toda la imagen anotada; basta con las clases presentes en el campo de visión del robot. El siguiente ejemplo muestra cómo usar mensajes std_msgs/String para volver a publicar las clases detectadas en el tema /ultralytics/detection/classes. Estos mensajes son más ligeros y proporcionan información esencial, por lo que resultan útiles en diversas aplicaciones.

Ejemplo de uso#

Piensa en un robot de almacén equipado con una cámara y un modelo de detección de objetos. En lugar de enviar imágenes anotadas de gran tamaño por la red, el robot puede publicar una lista de clases detectadas como mensajes std_msgs/String. Por ejemplo, cuando el robot detecta objetos como «caja», «palé» y «carretilla elevadora», publica esas clases en el tema /ultralytics/detection/classes. Un sistema central de supervisión puede usar esta información para controlar el inventario en tiempo real, optimizar la planificación de rutas del robot para evitar obstáculos o activar acciones específicas, como recoger una caja detectada. Este enfoque reduce el ancho de banda necesario para la comunicación y se centra en transmitir datos esenciales.

Uso paso a paso de String#

Este ejemplo muestra cómo usar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tema de cámara, procesamos la imagen recibida con YOLO y publicamos los objetos detectados en el nuevo tema /ultralytics/detection/classes mediante mensajes std_msgs/String. El paquete ros_numpy se usa para convertir el mensaje ROS Image en una matriz NumPy y procesarlo con 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()

Usar Ultralytics con imágenes de profundidad de ROS#

Además de las imágenes RGB, ROS admite imágenes de profundidad, que proporcionan información sobre la distancia de los objetos respecto a la cámara. Las imágenes de profundidad son esenciales en aplicaciones robóticas como la evitación de obstáculos, el mapeo 3D y la localización.

Una imagen de profundidad es una imagen en la que cada píxel representa la distancia entre la cámara y un objeto. A diferencia de las imágenes RGB, que capturan el color, las imágenes de profundidad capturan información espacial y permiten a los robots percibir la estructura 3D de su entorno.

Obtener imágenes de profundidad

Puedes obtener imágenes de profundidad con distintos sensores:

  1. Cámaras estéreo: usa dos cámaras para calcular la profundidad a partir de la disparidad entre imágenes.
  2. Cámaras de tiempo de vuelo (ToF): miden el tiempo que tarda la luz en volver tras reflejarse en un objeto.
  3. Sensores de luz estructurada: proyectan un patrón y miden su deformación sobre las superficies.

Usar YOLO con imágenes de profundidad#

En ROS, las imágenes de profundidad se representan con el tipo de mensaje sensor_msgs/Image, que incluye campos de codificación, altura, anchura y datos de píxeles. El campo de codificación de las imágenes de profundidad suele usar un formato como «16UC1», que indica un entero sin signo de 16 bits por píxel, donde cada valor representa la distancia al objeto. Las imágenes de profundidad suelen combinarse con imágenes RGB para ofrecer una visión más completa del entorno.

Con YOLO, es posible extraer y combinar información de imágenes RGB y de profundidad. Por ejemplo, YOLO puede detectar objetos en una imagen RGB y usar esa detección para localizar las regiones correspondientes en la imagen de profundidad. Esto permite extraer información precisa sobre la profundidad de los objetos detectados y mejora la capacidad del robot para comprender su entorno en tres dimensiones.

Cámaras RGB-D

Al trabajar con imágenes de profundidad, es esencial asegurarse de que las imágenes RGB y de profundidad estén correctamente alineadas. Las cámaras RGB-D, como la serie Intel RealSense, proporcionan imágenes RGB y de profundidad sincronizadas, lo que facilita combinar la información de ambas fuentes. Si usas cámaras RGB y de profundidad independientes, es fundamental calibrarlas para garantizar una alineación precisa.

Uso paso a paso de imágenes de profundidad#

En este ejemplo, usamos YOLO para segmentar una imagen y aplicamos la máscara obtenida para segmentar el objeto en la imagen de profundidad. Así podemos determinar la distancia entre cada píxel del objeto de interés y el centro focal de la cámara. Con esta información sobre la distancia, podemos calcular la distancia entre la cámara y el objeto concreto de la escena. Empieza importando las bibliotecas necesarias, creando un nodo ROS e instanciando un modelo de segmentación y un tema de 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)

A continuación, define una función de devolución de llamada que procese el mensaje de imagen de profundidad recibido. La función espera los mensajes de imagen de profundidad y RGB, los convierte en matrices NumPy y aplica el modelo de segmentación a la imagen RGB. Después, extrae la máscara de segmentación de cada objeto detectado y calcula la distancia media del objeto a la cámara usando la imagen de profundidad. La mayoría de los sensores tienen una distancia máxima, conocida como distancia de recorte, más allá de la cual los valores se representan como inf (np.inf). Antes del procesamiento, es importante filtrar estos valores nulos y asignarles el valor 0. Por último, publica los objetos detectados junto con sus distancias medias en el tema /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()
Código completo
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()

Usar Ultralytics con sensor_msgs/PointCloud2 de ROS#

Detection and Segmentation in ROS Gazebo

El tipo de mensaje sensor_msgs/PointCloud2 es una estructura de datos que se usa en ROS para representar datos de nubes de puntos 3D. Este tipo de mensaje es fundamental en las aplicaciones robóticas, ya que permite realizar tareas como el mapeo 3D, el reconocimiento de objetos y la localización.

Una nube de puntos es un conjunto de puntos de datos definidos en un sistema de coordenadas tridimensional. Estos puntos representan la superficie externa de un objeto o una escena capturada mediante tecnologías de escaneo 3D. Cada punto de la nube tiene coordenadas X, Y y Z, que corresponden a su posición en el espacio, y puede incluir también información adicional, como el color y la intensidad.

Sistema de referencia

Al trabajar con sensor_msgs/PointCloud2, es esencial tener en cuenta el sistema de referencia del sensor del que se obtuvieron los datos de la nube de puntos. La nube de puntos se captura inicialmente en el sistema de referencia del sensor. Puedes determinar este sistema de referencia escuchando el tema /tf_static. Sin embargo, según los requisitos específicos de tu aplicación, quizá tengas que convertir la nube de puntos a otro sistema de referencia. Puedes realizar esta transformación con el paquete tf2_ros, que proporciona herramientas para gestionar sistemas de coordenadas y transformar datos entre ellos.

Obtener nubes de puntos

Puedes obtener nubes de puntos con distintos sensores:

  1. LIDAR (detección y medición de distancias por luz): utiliza pulsos láser para medir las distancias a los objetos y crear mapas 3D de gran precisión.
  2. Cámaras de profundidad: capturan información de profundidad de cada píxel, lo que permite reconstruir la escena en 3D.
  3. Cámaras estéreo: utilizan dos o más cámaras para obtener información de profundidad mediante triangulación.
  4. Escáneres de luz estructurada: proyectan un patrón conocido sobre una superficie y miden su deformación para calcular la profundidad.

Usar YOLO con nubes de puntos#

Para integrar YOLO con mensajes del tipo sensor_msgs/PointCloud2, podemos emplear un método similar al usado con los mapas de profundidad. Al aprovechar la información de color incluida en la nube de puntos, podemos extraer una imagen 2D, segmentarla con YOLO y aplicar la máscara resultante a los puntos tridimensionales para aislar el objeto 3D de interés.

Para trabajar con nubes de puntos, recomendamos Open3D (pip install open3d), una biblioteca de Python fácil de usar. Open3D ofrece herramientas robustas para gestionar estructuras de datos de nubes de puntos, visualizarlas y realizar operaciones complejas sin complicaciones. Esta biblioteca puede simplificar considerablemente el proceso y mejorar nuestra capacidad para manipular y analizar nubes de puntos junto con la segmentación basada en YOLO.

Uso paso a paso de nubes de puntos#

Importa las bibliotecas necesarias e instancia el modelo YOLO para la segmentación.

import time

import rospy

from ultralytics import YOLO

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

Crea una función pointcloud2_to_array que transforme un mensaje sensor_msgs/PointCloud2 en dos matrices NumPy. Los mensajes sensor_msgs/PointCloud2 contienen puntos n basados en el width y el height de la imagen adquirida. Por ejemplo, una imagen 480 x 640 tendrá 307,200 puntos. Cada punto incluye tres coordenadas espaciales (xyz) y el color correspondiente en formato RGB. Estos pueden considerarse dos canales de información independientes.

La función devuelve las coordenadas xyz y los valores RGB en el formato de la resolución original de la cámara (width x height). La mayoría de los sensores tienen una distancia máxima, conocida como distancia de recorte, más allá de la cual los valores se representan como inf (np.inf). Antes del procesamiento, es importante filtrar estos valores nulos y asignarles el valor 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

A continuación, suscríbete al tema /camera/depth/points para recibir el mensaje de la nube de puntos y convierte el mensaje sensor_msgs/PointCloud2 en matrices NumPy que contengan las coordenadas XYZ y los valores RGB (mediante la función pointcloud2_to_array). Procesa la imagen RGB con el modelo YOLO para extraer los objetos segmentados. Para cada objeto detectado, extrae la máscara de segmentación y aplícala tanto a la imagen RGB como a las coordenadas XYZ para aislar el objeto en el espacio 3D.

Procesar la máscara es sencillo, ya que consta de valores binarios: 1 indica la presencia del objeto y 0, su ausencia. Para aplicar la máscara, basta con multiplicar los canales originales por la máscara. Esta operación aísla eficazmente el objeto de interés en la imagen. Por último, crea un objeto de nube de puntos de Open3D y visualiza el objeto segmentado en el espacio 3D con los colores correspondientes.

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)  # máscaras a la resolución original de la imagen

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])
Código completo
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)  # máscaras a la resolución original de la imagen

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

Conclusión#

Con Ultralytics YOLO integrado en ROS, tu robot puede ejecutar detección de objetos y segmentación en imágenes RGB, imágenes de profundidad y nubes de puntos, y convertir flujos de datos de sensores sin procesar en información de percepción útil. A continuación, explora el modo Predict para conocer más opciones de inferencia o sigue los pasos de un proyecto de visión artificial para llevar tu aplicación robótica del prototipo a producción.

Preguntas frecuentes#

  • El Sistema Operativo para Robots (ROS) es un framework de código abierto muy utilizado en robótica que ayuda a los desarrolladores a crear aplicaciones robóticas robustas. Proporciona una colección de bibliotecas y herramientas para crear sistemas robóticos e interactuar con ellos, lo que facilita el desarrollo de aplicaciones complejas. ROS permite la comunicación entre nodos mediante mensajes a través de temas o servicios.

  • Para integrar Ultralytics YOLO con ROS, tienes que configurar un entorno ROS y usar YOLO para procesar datos de sensores. Empieza instalando las dependencias necesarias, como ros_numpy, y Ultralytics YOLO:

    pip install ros_numpy ultralytics

    A continuación, crea un nodo ROS y suscríbete a un tema de imagen para procesar los datos recibidos y realizar la detección de objetos. Aquí tienes un ejemplo mínimo:

    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()
  • Los temas de ROS permiten la comunicación entre nodos de una red ROS mediante un modelo de publicación-suscripción. Un tema es un canal con nombre que los nodos utilizan para enviar y recibir mensajes de forma asíncrona. En el contexto de Ultralytics YOLO, puedes hacer que un nodo se suscriba a un tema de imagen, procese las imágenes con YOLO para tareas como la detección o la segmentación y publique los resultados en nuevos temas.

    Por ejemplo, suscríbete a un tema de cámara y procesa la imagen recibida para detectar objetos:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • Las imágenes de profundidad en ROS, representadas mediante sensor_msgs/Image, indican la distancia de los objetos a la cámara, un dato esencial para tareas como la evitación de obstáculos, el mapeo 3D y la localización. Al usar información de profundidad junto con imágenes RGB, los robots pueden comprender mejor su entorno 3D.

    Con YOLO, puedes extraer máscaras de segmentación de imágenes RGB y aplicarlas a imágenes de profundidad para obtener información 3D precisa sobre los objetos, lo que mejora la capacidad del robot para desplazarse e interactuar con su entorno.

  • Para visualizar nubes de puntos 3D en ROS con YOLO:

    1. Convierte los mensajes sensor_msgs/PointCloud2 en matrices NumPy.
    2. Usa YOLO para segmentar imágenes RGB.
    3. Aplica la máscara de segmentación a la nube de puntos.

    Aquí tienes un ejemplo que usa Open3D para la visualización:

    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)  # máscaras a la resolución original de la imagen
    
    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])

    Este enfoque ofrece una visualización 3D de los objetos segmentados, útil para tareas como la navegación y la manipulación en aplicaciones robóticas.

Comentarios