Guía de inicio rápido de ROS (Robot Operating System)#
Esta guía muestra cómo integrar Ultralytics YOLO con ROS1 (rospy) o ROS2 (rclpy) para ejecutar detección de objetos y segmentación en tiempo real sobre 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 Robot Operating System (ROS) es un framework de código abierto muy utilizado en la investigación y la industria de la robótica. ROS proporciona una colección de bibliotecas y herramientas para ayudar a los desarrolladores a crear aplicaciones robóticas. ROS está diseñado para funcionar con varias plataformas robóticas, lo que lo convierte en una herramienta flexible y potente para profesionales de la 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#
-
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.
-
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.
-
Abstracción del hardware: ROS proporciona una capa de abstracción sobre el hardware, lo que permite a los desarrolladores escribir código independiente del dispositivo. Así, se puede utilizar el mismo código con distintas configuraciones de hardware, facilitando la integración y la experimentación.
-
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 proporciona un potente entorno de simulación para probar algoritmos y diseños de robots.
-
Ecosistema amplio: El ecosistema de ROS es extenso y crece continuamente, con numerosos paquetes disponibles para distintas aplicaciones robóticas, como navegación, manipulación, percepción y muchas 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 que aparecen a continuación 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 proporcionaba una base sólida para el desarrollo robótico, ROS 2 aborda sus carencias ofreciendo:
- Rendimiento en tiempo real: Mejor compatibilidad con sistemas en tiempo real y comportamiento determinista.
- Seguridad: Funciones de seguridad mejoradas para un funcionamiento seguro y fiable en distintos entornos.
- Escalabilidad: Mejor compatibilidad con sistemas multirobot y despliegues a gran escala.
- Compatibilidad multiplataforma: Compatibilidad ampliada con distintos sistemas operativos más allá 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 tópicos de ROS#
En ROS, la comunicación entre nodos se facilita mediante 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 por el que 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 la comunicación asíncrona y el desacoplamiento entre nodos. Normalmente, cada sensor o actuador de un sistema robótico publica datos en un tópico, que después pueden consumir otros nodos para procesarlos o controlarlos. En esta guía nos centraremos en los mensajes Image, Depth y PointCloud, 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 de ROS, una bifurcación del repositorio de ROS de ROSbot. El mismo procesamiento con YOLO y NumPy se aplica en ROS2; solo cambian el ciclo de vida del nodo y la conversión de mensajes.
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 Image de ROS en matrices NumPy y viceversa.
pip install ros_numpy -
Paquete de 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 a continuación; 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 a continuación y sustituye únicamente la obtenció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 sola vez y reutiliza después la segmentación de NumPy y el mapeo 3D que aparecen 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 sensor_msgs/Image tipo de mensaje se utiliza habitualmente en ROS para representar datos de imagen. Contiene campos para la codificación, la altura, la anchura y los datos de los 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.
Uso de Image paso a paso#
El siguiente fragmento de código muestra cómo utilizar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tópico de cámara, procesamos la imagen entrante con YOLO y publicamos los objetos detectados en nuevos tópicos para la detección y la 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 de 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 que da 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 tópicos de ROS: uno para la detección y otro para la segmentación. Estos tópicos se utilizarán para publicar las imágenes anotadas y hacerlas accesibles para su posterior procesamiento. La comunicación entre nodos se facilita 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 tópico /camera/color/image_raw y llame a una función de devolución de llamada para 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 anteriormente, anota las imágenes y las vuelve a publicar en los tópicos 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 ser complicado debido a la naturaleza distribuida del sistema. Varias herramientas pueden ayudarte en este proceso:
rostopic echo <TOPIC-NAME>: Este comando permite ver los mensajes publicados en un tópico específico, lo que ayuda a inspeccionar el flujo de datos.rostopic list: Utiliza este comando para mostrar todos los tópicos disponibles en el sistema ROS y obtener una visión general de los flujos de datos activos.rqt_graph: Esta herramienta de visualización muestra el grafo de comunicación entre nodos y proporciona información sobre cómo están interconectados y cómo interactúan.- Para visualizaciones más complejas, como las representaciones 3D, puedes utilizar RViz. RViz (Visualización de ROS) es una potente herramienta de visualización 3D para ROS. Permite visualizar el estado del 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 del sistema robótico.
Publicación de 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 la imagen anotada completa; solo se necesitan las clases presentes en el campo de visión del robot. El siguiente ejemplo muestra cómo utilizar mensajes std_msgs/String para volver a publicar las clases detectadas en el tópico /ultralytics/detection/classes. Estos mensajes son más ligeros y proporcionan la información esencial, por lo que resultan útiles en diversas aplicaciones.
Caso de uso de ejemplo#
Imagina 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 las clases detectadas como mensajes 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. Después, un sistema central de supervisión puede utilizar esta información para controlar el inventario en tiempo real, optimizar la planificación de la ruta 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 de String paso a paso#
Este ejemplo muestra cómo utilizar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tópico de cámara, procesamos la imagen entrante con YOLO y publicamos los objetos detectados en el nuevo tópico /ultralytics/detection/classes mediante mensajes std_msgs/String. El paquete ros_numpy se utiliza para convertir el mensaje Image de ROS en una matriz NumPy que se procesará 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()Uso de 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 fundamentales para 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, lo que permite a los robots percibir la estructura 3D de su entorno.
Las imágenes de profundidad se pueden obtener mediante varios sensores:
- Cámaras estéreo: Utilizan dos cámaras para calcular la profundidad a partir de la disparidad de las imágenes.
- Cámaras de tiempo de vuelo (ToF): Miden el tiempo que tarda la luz en regresar desde un objeto.
- Sensores de luz estructurada: Proyectan un patrón y miden su deformación sobre las superficies.
Uso de YOLO con imágenes de profundidad#
En ROS, las imágenes de profundidad se representan mediante el tipo de mensaje sensor_msgs/Image, que incluye campos para la codificación, la altura, la anchura y los datos de los píxeles. El campo de codificación de 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 habitualmente junto con imágenes RGB para proporcionar 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 utilizar esta detección para localizar las regiones correspondientes en la imagen de profundidad. Esto permite extraer información precisa de profundidad para los objetos detectados y mejora la capacidad del robot para comprender su entorno en tres dimensiones.
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 independientes, es esencial calibrarlas para garantizar una alineación precisa.
Uso de Depth paso a paso#
En este ejemplo, utilizamos YOLO para segmentar una imagen y aplicamos 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 respecto al 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 de la escena. Empieza importando las bibliotecas necesarias, creando un nodo de ROS e instanciando un modelo de segmentación y un tópico 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 entrante. La función espera los mensajes de imagen de profundidad y de imagen 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 utilizando 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 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#
El tipo de mensaje sensor_msgs/PointCloud2 tipo de mensaje es una estructura de datos utilizada en ROS para representar datos de nubes de puntos 3D. Este tipo de mensaje es fundamental para aplicaciones robóticas y 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 representan la superficie externa de un objeto o una escena, capturada mediante tecnologías de escaneado 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 el color y la intensidad.
Al trabajar con sensor_msgs/PointCloud2, es fundamental 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 tópico /tf_static. Sin embargo, según los requisitos específicos de tu aplicación, puede que tengas que convertir la nube de puntos a otro sistema de referencia. Esta transformación se puede realizar mediante el paquete tf2_ros, que proporciona herramientas para gestionar sistemas de coordenadas y transformar datos entre ellos.
Las nubes de puntos se pueden obtener mediante varios sensores:
- LIDAR (detección y medición de distancias mediante luz): Utiliza pulsos láser para medir las distancias a los objetos y crear mapas 3D de alta-precisión.
- Cámaras de profundidad: Capturan información de profundidad para cada píxel, lo que permite reconstruir la escena en 3D.
- Cámaras estéreo: Utilizan dos o más cámaras para obtener información de profundidad mediante triangulación.
- Escáneres de luz estructurada: Proyectan un patrón conocido sobre una superficie y miden su deformación para calcular la profundidad.
Uso de YOLO con nubes de puntos#
Para integrar YOLO con mensajes del 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 de esta imagen con YOLO y aplicar después la máscara resultante a los puntos tridimensionales para aislar el objeto 3D de interés.
Para gestionar nubes de puntos, recomendamos utilizar Open3D (pip install open3d), una biblioteca Python fácil de usar. Open3D proporciona herramientas sólidas para gestionar estructuras de datos de nubes de puntos, visualizarlas y ejecutar operaciones complejas sin problemas. 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 de nubes de puntos paso a paso#
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 n puntos basados en la width y la 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. Se pueden considerar dos canales de información independientes.
La función devuelve las coordenadas xyz y los valores RGB con 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, rgbA continuación, suscríbete al tópico /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 indica 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 dentro de la imagen. Por último, crea un objeto de nube de puntos de Open3D y visualiza el objeto segmentado en el espacio 3D con sus 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)
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])
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 los flujos de datos sin procesar de los sensores en información de percepción útil. A partir de aquí, explora el modo Predict para consultar 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 Robot Operating System (ROS) es un framework de código abierto utilizado habitualmente en robótica para ayudar 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 admite la comunicación entre nodos mediante mensajes a través de tópicos o servicios.
Integrar Ultralytics YOLO con ROS implica configurar un entorno de ROS y utilizar YOLO para procesar datos de sensores. Empieza instalando las dependencias necesarias, como
ros_numpyy Ultralytics YOLO:pip install ros_numpy ultralyticsA continuación, crea un nodo de ROS y suscríbete a un tópico de imágenes para procesar los datos entrantes para la detección de objetos. Este es 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 tópicos de ROS facilitan la comunicación entre nodos de 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 imágenes, procese las imágenes con YOLO para tareas como la detección o la segmentación y publique los resultados en nuevos tópicos.
Por ejemplo, suscríbete a un tópico de cámara y procesa la imagen entrante para realizar la detección:
rospy.Subscriber("/camera/color/image_raw", Image, callback)Las imágenes de profundidad en ROS, representadas mediante
sensor_msgs/Image, proporcionan la distancia de los objetos respecto a la cámara, algo fundamental para tareas como la evitación de obstáculos, el mapeo 3D y la localización. Al utilizar 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 aplicar estas máscaras a imágenes de profundidad para obtener información 3D precisa de los objetos, mejorando la capacidad del robot para desplazarse e interactuar con su entorno.
Para visualizar nubes de puntos 3D en ROS con YOLO:
- Convierte los mensajes
sensor_msgs/PointCloud2en matrices NumPy. - Usa YOLO para segmentar imágenes RGB.
- Aplica la máscara de segmentación a la nube de puntos.
Aquí tienes un ejemplo que utiliza 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 3D de los objetos segmentados, útil para tareas como la navegación y la manipulación en aplicaciones de robótica.
- Convierte los mensajes