YOLO Vision 2026:

Guía de inicio rápido de ROS (Robot Operating System)#

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

Salta a la configuración de YOLO con ROS y, a continuación, trabaja con imágenes RGB, imágenes de profundidad o nubes de puntos.

ROS Introduction (captioned) from Open Robotics on Vimeo.

¿Qué es ROS?#

El Robot Operating System (ROS) es un marco de código abierto ampliamente utilizado en la investigación y la industria de la robótica. ROS ofrece una colección de bibliotecas y herramientas para ayudar a los desarrolladores a crear aplicaciones para robots. ROS está diseñado para funcionar con varias plataformas robóticas, lo que lo convierte en una herramienta flexible y potente para los roboticistas.

Características clave de ROS#

  1. Arquitectura modular: ROS cuenta con una arquitectura modular que permite a los desarrolladores construir 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 robusta que admite la comunicación entre procesos y la computación distribuida. Esto se logra a través de un modelo de publicación-suscripción para flujos de datos (temas) y un modelo de solicitud-respuesta para llamadas de servicio.

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

  4. Herramientas y utilidades: ROS viene con 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 del estado del robot, mientras que Gazebo proporciona un potente entorno de simulación para probar algoritmos y diseños de robots.

  5. Ecosistema extenso: El ecosistema de ROS es vasto y sigue creciendo, con numerosos paquetes disponibles para diferentes aplicaciones robóticas, incluyendo navegación, manipulación, percepción y más. 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 múltiples versiones, divididas en ROS 1 y ROS 2. Los ejemplos existentes a continuación utilizan ROS1 Noetic; los adaptadores compactos en Uso de ROS2 muestran las interfaces correspondientes de rclpy para 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 soluciona sus deficiencias ofreciendo:

  • Rendimiento en tiempo real: Soporte mejorado para sistemas en tiempo real y comportamiento determinista.
  • Seguridad: Características de seguridad mejoradas para un funcionamiento seguro y fiable en diversos entornos.
  • Escalabilidad: Mejor soporte para sistemas multi-robot y despliegues a gran escala.
  • Soporte multiplataforma: Compatibilidad ampliada con varios sistemas operativos más allá de Linux, incluyendo 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 facilita a través de mensajes y tópicos. Un mensaje es una estructura de datos que define la información intercambiada entre nodos, mientras que un tópico es un canal con nombre a través del cual se envían y reciben mensajes. Los nodos pueden publicar mensajes en un tópico o suscribirse a los mensajes de un tópico, lo que les permite comunicarse entre sí. Este modelo de publicación-suscripción permite una comunicación asíncrona y el desacoplamiento entre nodos. Cada sensor o actuador de un sistema robótico suele publicar datos en un tópico, que luego pueden ser consumidos por otros nodos para su procesamiento o control. Para los fines de esta guía, nos centraremos en los mensajes de imagen, profundidad y nube de puntos, así como en los tópicos de cámara.

Configuración de Ultralytics YOLO con ROS#

Los ejemplos de ROS1 se probaron utilizando este entorno ROS, una bifurcación del repositorio ROSbot ROS. El mismo procesamiento de YOLO y NumPy se aplica en ROS2; solo difieren 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 ROS, necesitarás instalar las siguientes dependencias:

  • Paquete NumPy para ROS: Esto es necesario para una conversión rápida entre mensajes de imagen de ROS y matrices de NumPy.

    pip install ros_numpy
  • Paquete Ultralytics:

    pip install ultralytics

Usando ROS2#

ROS2 reemplaza 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 a continuación; instancia los modelos una vez y recúbrelos en las retrollamadas.

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 imágenes de profundidad, reutiliza el código de procesamiento de profundidad a continuación y reemplaza solo 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")
    # Apply the NumPy mask and distance calculation from the depth example below.

Para las nubes de puntos, ROS2 proporciona sensor_msgs_py.point_cloud2; convierte la nube organizada una vez y luego reutiliza la segmentación de NumPy y el mapeo 3D a continuación:

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)

Usa Ultralytics con ROS sensor_msgs/Image#

El tipo de mensaje sensor_msgs/Image se utiliza comúnmente en ROS para representar datos de imagen. Contiene campos para la codificación, altura, anchura y datos de píxeles, lo que lo hace adecuado para transmitir imágenes capturadas por cámaras u otros sensores. Los mensajes de imagen 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 demuestra cómo usar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tópico de cámara, procesamos la imagen entrante usando YOLO y publicamos los objetos detectados en nuevos tópicos para detección y segmentación.

Primero, importa las bibliotecas necesarias e instancia dos modelos: uno para segmentación y otro para detección. Inicializa un nodo ROS (con el nombre ultralytics) para permitir la comunicación con el maestro de ROS. Para garantizar una conexión estable, incluimos una breve pausa, dando al nodo el 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 tópicos de ROS: uno para detección y otro para segmentación. Estos tópicos se utilizarán para publicar las imágenes anotadas, haciéndolas accesibles para un procesamiento posterior. La comunicación entre nodos se facilita utilizando mensajes de 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)

Finalmente, crea un suscriptor que escuche los mensajes en el tópico /camera/color/image_raw y llame a una función de retrollamada para cada nuevo mensaje. Esta función de retrollamada recibe mensajes de tipo sensor_msgs/Image, los convierte en una matriz de NumPy usando ros_numpy, procesa las imágenes con los modelos de YOLO instanciados previamente, anota las imágenes y luego las publica de nuevo en los tópicos respectivos: /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

La depuración de nodos en ROS (Robot Operating System) puede ser un desafío debido a la naturaleza distribuida del sistema. Varias herramientas pueden ayudar en este proceso:

  1. rostopic echo <TOPIC-NAME> : Este comando te permite ver los mensajes publicados en un tópico específico, ayudándote a inspeccionar el flujo de datos.
  2. rostopic list: Usa este comando para listar todos los tópicos disponibles en el sistema ROS, dándote una visión general de los flujos de datos activos.
  3. rqt_graph: Esta herramienta de visualización muestra el gráfico de comunicación entre nodos, proporcionando información sobre cómo se interconectan los nodos y cómo interactúan.
  4. Para visualizaciones más complejas, como representaciones en 3D, puedes usar RViz. RViz (ROS Visualization) 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 varios otros tipos de información, lo que facilita la depuración y la comprensión del comportamiento de tu sistema robótico.

Publicar 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 republicar toda la imagen anotada; en su lugar, solo se necesitan las clases presentes en la vista del robot. El siguiente ejemplo demuestra cómo utilizar mensajes std_msgs/String para republicar las clases detectadas en el tema /ultralytics/detection/classes. Estos mensajes son más ligeros y proporcionan información esencial, lo que los hace valiosos para diversas aplicaciones.

Caso de uso de ejemplo#

Considera un robot de almacén equipado con una cámara y un modelo de detección de objetos. En lugar de enviar imágenes grandes anotadas a través de la red, el robot puede publicar una lista de clases detectadas como mensajes de std_msgs/String. Por ejemplo, cuando el robot detecta objetos como "caja", "palé" y "carretilla elevadora", publica estas clases en el tópico /ultralytics/detection/classes. Esta información puede ser utilizada por un sistema de monitoreo central para rastrear 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 críticos.

Uso paso a paso de cadenas (String)#

Este ejemplo demuestra cómo usar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tópico de cámara, procesamos la imagen entrante usando YOLO y publicamos los objetos detectados en el nuevo tópico /ultralytics/detection/classes utilizando mensajes de std_msgs/String. El paquete ros_numpy se utiliza para convertir el mensaje de imagen de ROS en una matriz de NumPy para su procesamiento 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()

Usa Ultralytics con imágenes de profundidad en ROS#

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

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

Obtención de imágenes de profundidad

Las imágenes de profundidad pueden obtenerse utilizando varios sensores:

  1. Cámaras estéreo: Utilizan dos cámaras para calcular la profundidad basándose en la disparidad de la imagen.
  2. Cámaras de tiempo de vuelo (ToF): Miden el tiempo que tarda la luz en regresar de un objeto.
  3. Sensores de luz estructurada: Proyectan un patrón y miden su deformación en las superficies.

Uso de YOLO con imágenes de profundidad#

En ROS, las imágenes de profundidad están representadas por el tipo de mensaje sensor_msgs/Image, que incluye campos para la codificación, la altura, la anchura y los datos de píxeles. El campo de codificación para las imágenes de profundidad suele utilizar 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 se utilizan comúnmente junto con las imágenes RGB para proporcionar una visión más completa del entorno.

Usando YOLO, es posible extraer y combinar información tanto de imágenes RGB como de profundidad. Por ejemplo, YOLO puede detectar objetos dentro de una imagen RGB, y esta detección puede utilizarse para señalar regiones correspondientes en la imagen de profundidad. Esto permite la extracción de información de profundidad precisa para los objetos detectados, mejorando la capacidad del robot para comprender su entorno en tres dimensiones.

Cámaras RGB-D

Al trabajar con imágenes de profundidad, es fundamental 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 la combinación de información de ambas fuentes. Si utilizas cámaras RGB y de profundidad separadas, es fundamental calibrarlas para garantizar una alineación precisa.

Uso paso a paso de profundidad#

En este ejemplo, usamos YOLO para segmentar una imagen y aplicar la máscara extraída para segmentar el objeto en la imagen de profundidad. Esto nos permite determinar la distancia de cada píxel del objeto de interés desde el centro focal de la cámara. Al obtener esta información de distancia, podemos calcular la distancia entre la cámara y el objeto específico en la escena. Comienza importando las bibliotecas necesarias, creando un nodo de 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 retrollamada que procese el mensaje de imagen de profundidad entrante. La función espera los mensajes de imagen de profundidad e imagen RGB, los convierte en matrices de NumPy y aplica el modelo de segmentación a la imagen RGB. A continuación, extrae la máscara de segmentación para cada objeto detectado y calcula la distancia promedio del objeto a la cámara utilizando la imagen de profundidad. La mayoría de los sensores tienen una distancia máxima, conocida como distancia de corte, 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 un valor de 0. Finalmente, publica los objetos detectados junto con sus distancias promedio en el tópico /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()
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)

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

Usa Ultralytics con ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

El tipo de mensaje sensor_msgs/PointCloud2 es una estructura de datos utilizada en ROS para representar datos de nubes de puntos 3D. Este tipo de mensaje es fundamental para 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 una colección de puntos de datos definidos dentro de un sistema de coordenadas tridimensional. Estos puntos de datos representan la superficie externa de un objeto o una escena, capturados 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 también puede incluir información adicional como color e intensidad.

Marco de referencia

Al trabajar con sensor_msgs/PointCloud2, es esencial tener en cuenta el marco de referencia del sensor a partir del cual se adquirieron los datos de la nube de puntos. La nube de puntos se captura inicialmente en el marco de referencia del sensor. Puedes determinar este marco de referencia escuchando el tópico /tf_static. Sin embargo, dependiendo de los requisitos específicos de tu aplicación, es posible que necesites convertir la nube de puntos a otro marco de referencia. Esta transformación se puede lograr utilizando el paquete tf2_ros, que proporciona herramientas para gestionar marcos de coordenadas y transformar datos entre ellos.

Obtención de nubes de puntos

Las nubes de puntos pueden obtenerse utilizando varios sensores:

  1. LIDAR (Detección y Medición de Luz por Láser): Utiliza pulsos láser para medir distancias a objetos y crear mapas 3D de alta precisión.
  2. Cámaras de profundidad: Capturan información de profundidad para cada píxel, permitiendo la reconstrucción 3D de la escena.
  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 la deformación para calcular la profundidad.

Uso de YOLO con nubes de puntos#

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

Para el manejo de nubes de puntos, recomendamos usar Open3D (pip install open3d), una biblioteca de Python fácil de usar. Open3D proporciona herramientas robustas para gestionar estructuras de datos de nubes de puntos, visualizarlas y ejecutar operaciones complejas sin problemas. Esta biblioteca puede simplificar significativamente 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 de sensor_msgs/PointCloud2 en dos matrices de NumPy. Los mensajes de sensor_msgs/PointCloud2 contienen puntos de n basados en los width y height de la imagen adquirida. Por ejemplo, una imagen de 480 x 640 tendrá puntos de 307,200. Cada punto incluye tres coordenadas espaciales (xyz) y el color correspondiente en formato RGB. Estos se pueden considerar como dos canales de información separados.

La función devuelve las coordenadas de xyz y los valores de RGB en el formato de la resolución de la cámara original (width x height). La mayoría de los sensores tienen una distancia máxima, conocida como distancia de corte, 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 un valor de 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 tópico /camera/depth/points para recibir el mensaje de la nube de puntos y convierte el mensaje de sensor_msgs/PointCloud2 en matrices de NumPy que contengan las coordenadas XYZ y los valores RGB (utilizando la función pointcloud2_to_array). Procesa la imagen RGB utilizando 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.

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

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

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, convirtiendo flujos de sensores sin procesar en una percepción procesable. A partir de aquí, explora el modo Predict para obtener más opciones de inferencia, o sigue los pasos de un proyecto de visión por computadora para llevar tu aplicación de robótica del prototipo a la producción.

FAQ#

¿Qué es el Robot Operating System (ROS)?#

El Robot Operating System (ROS) es un marco de código abierto comúnmente utilizado en robótica para ayudar a los desarrolladores a crear aplicaciones de robots robustas. Ofrece una colección de bibliotecas y herramientas para construir y conectar sistemas robóticos, lo que facilita el desarrollo de aplicaciones complejas. ROS admite la comunicación entre nodos mediante mensajes a través de tópicos o servicios.

¿Cómo integro Ultralytics YOLO con ROS para la detección de objetos en tiempo real?#

La integración de Ultralytics YOLO con ROS implica configurar un entorno ROS y usar YOLO para procesar los datos de los sensores. Comienza instalando las dependencias requeridas como ros_numpy y Ultralytics YOLO:

pip install ros_numpy ultralytics

A continuación, crea un nodo ROS y suscríbete a un tópico de imagen para procesar los datos entrantes para 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()

¿Qué son los temas de ROS y cómo se utilizan en Ultralytics YOLO?#

Los tópicos de ROS facilitan la comunicación entre nodos en una red ROS mediante un modelo de publicación-suscripción. Un tópico 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 tópico de imagen, procese las imágenes usando YOLO para tareas como detección o segmentación, y publique los resultados en nuevos tópicos.

Por ejemplo, suscríbete a un tema de cámara y procesa la imagen entrante para la detección:

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

¿Por qué usar imágenes de profundidad con Ultralytics YOLO en ROS?#

Las imágenes de profundidad en ROS, representadas por sensor_msgs/Image, proporcionan la distancia de los objetos a la cámara, lo que es crucial para tareas como la evasión de obstáculos, el mapeo 3D y la localización. Al utilizar información de profundidad junto con las 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 aplicar estas máscaras a imágenes de profundidad para obtener información precisa de objetos 3D, mejorando la capacidad del robot para navegar e interactuar con su entorno.

¿Cómo puedo visualizar nubes de puntos 3D con YOLO en ROS?#

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

  1. Convierte los mensajes de sensor_msgs/PointCloud2 en matrices de 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 utilizando 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)

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 proporciona una visualización en 3D de los objetos segmentados, útil para tareas como la navegación y la manipulación en aplicaciones de robótica.

Comentarios