Guía de inicio rápido de ROS (Sistema Operativo Robótico)
Introducción a ROS (con subtítulos) de Open Robotics en Vimeo.
¿Qué es ROS?
El Sistema Operativo Robótico (ROS) es un framework de código abierto ampliamente 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 los roboticistas.
Características principales de ROS
-
Arquitectura Modular: ROS tiene una arquitectura modular, lo que permite a los desarrolladores construir sistemas complejos combinando componentes más pequeños y reutilizables llamados nodos. Cada nodo normalmente realiza una función específica, y los nodos se comunican entre sí mediante mensajes a través de temas o servicios.
-
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.
-
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 que el mismo código se utilice con diferentes configuraciones de hardware, lo que facilita la integración y la experimentación.
-
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 los datos de los sensores y la información del estado del robot, mientras que Gazebo proporciona un entorno de simulación potente para probar algoritmos y diseños de robots.
-
Ecosistema Extenso: El ecosistema ROS es vasto y está en continuo crecimiento, 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, cada una introduciendo nuevas características y mejoras para satisfacer las crecientes necesidades de la comunidad robótica. El desarrollo de ROS se puede categorizar en dos series principales: ROS 1 y ROS 2. Esta guía se centra en la versión de Soporte a Largo Plazo (LTS) de ROS 1, conocida como ROS Noetic Ninjemys; el código también debería funcionar con versiones anteriores.
ROS 1 vs. ROS 2
Si bien ROS 1 proporcionó una base sólida para el desarrollo robótico, ROS 2 aborda sus deficiencias al ofrecer:
- Rendimiento en tiempo real: Soporte mejorado para sistemas en tiempo real y comportamiento determinista.
- Seguridad: Funciones 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, 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 facilita a través de 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 sobre el cual se envían y reciben mensajes. Los nodos pueden publicar mensajes en un tema o suscribirse a 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. Cada sensor o actuador en un sistema robótico normalmente publica datos en un tema, que luego puede ser consumido por otros nodos para su procesamiento o control. Para el propósito de esta guía, nos centraremos en los mensajes de Imagen, Profundidad y Nube de Puntos y en los temas de la cámara.
Configuración de Ultralytics YOLO con ROS
Esta guía se ha probado utilizando este entorno ROS, que es una bifurcación del repositorio ROSbot ROS. Este entorno incluye el paquete YOLO de Ultralytics, un contenedor Docker para una fácil configuración, paquetes ROS completos y mundos Gazebo para pruebas rápidas. Está diseñado para funcionar con el Husarion ROSbot 2 PRO. Los ejemplos de código proporcionados funcionarán en cualquier entorno ROS Noetic/Melodic, incluyendo tanto la simulación como el mundo real.
Instalación de dependencias
Aparte del entorno ROS, deberá instalar las siguientes dependencias:
-
Paquete ROS Numpy: Esto es necesario para la conversión rápida entre mensajes de imagen ROS y matrices numpy.
pip install ros_numpy
-
Paquete Ultralytics:
pip install ultralytics
Usar Ultralytics con ROS sensor_msgs/Image
El sensor_msgs/Image
tipo de mensaje se usa comúnmente en ROS para representar datos de imagen. Contiene campos para codificación, altura, ancho 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, detección de objetos, y navegación.
Uso paso a paso de la imagen
El siguiente fragmento de código demuestra cómo utilizar el paquete Ultralytics YOLO con ROS. En este ejemplo, nos suscribimos a un tema de cámara, procesamos la imagen entrante utilizando YOLO y publicamos los objetos detectados en nuevos temas para detección y segmentación.
Primero, importe las bibliotecas necesarias e instancie dos modelos: uno para segmentación y uno para detección. Inicialice un nodo ROS (con el nombre ultralytics
) para habilitar la comunicación con el maestro ROS. Para asegurar una conexión estable, incluimos una breve pausa, dando al nodo tiempo suficiente para establecer la conexión antes de proceder.
import time
import rospy
from ultralytics import YOLO
detection_model = YOLO("yolo11m.pt")
segmentation_model = YOLO("yolo11m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
Inicializar dos temas ROS: uno para detección y uno para segmentación. Estos temas se utilizarán para publicar las imágenes anotadas, haciéndolas accesibles para su posterior procesamiento. La comunicación entre nodos se facilita mediante sensor_msgs/Image
mensajes.
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, cree un suscriptor que escuche los mensajes en el /camera/color/image_raw
tema y llama a una función de callback para cada nuevo mensaje. Esta función de callback recibe mensajes de tipo sensor_msgs/Image
, los convierte en un array de numpy usando ros_numpy
, procesa las imágenes con los modelos YOLO previamente instanciados, anota las imágenes y luego las vuelve a publicar en los temas respectivos: /ultralytics/detection/image
para la detección y /ultralytics/segmentation/image
para 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("yolo11m.pt")
segmentation_model = YOLO("yolo11m-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 ROS (Robot Operating System) puede ser un desafío debido a la naturaleza distribuida del sistema. Varias herramientas pueden ayudar con este proceso:
rostopic echo <TOPIC-NAME>
: Este comando le permite ver los mensajes publicados en un tema específico, lo que le ayuda a inspeccionar el flujo de datos.rostopic list
: Utiliza este comando para listar todos los temas disponibles en el sistema ROS, lo que te dará una visión general de los flujos de datos activos.rqt_graph
: Esta herramienta de visualización muestra el gráfico de comunicación entre nodos, proporcionando información sobre cómo están interconectados los nodos y cómo interactúan.- Para visualizaciones más complejas, como representaciones 3D, puedes usar RViz. RViz (ROS Visualization) es una potente herramienta de visualización 3D para ROS. Le permite visualizar el estado de su robot y su entorno en tiempo real. Con RViz, puede ver datos de sensores (por ejemplo,
sensors_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 su sistema robótico.
Publicar clases detectadas con std_msgs/String
Los mensajes ROS estándar también incluyen std_msgs/String
mensajes. En muchas aplicaciones, no es necesario volver a publicar la imagen anotada completa; en su lugar, solo se necesitan las clases presentes en la vista del robot. El siguiente ejemplo demuestra cómo utilizar std_msgs/String
mensajes para volver a publicar las clases detectadas en el /ultralytics/detection/classes
tema. Estos mensajes son más ligeros y proporcionan información esencial, lo que los hace valiosos para diversas aplicaciones.
Caso de uso de ejemplo
Considere un robot de almacén equipado con una cámara y un objeto modelo de detección. En lugar de enviar imágenes grandes anotadas a través de la red, el robot puede publicar una lista de clases detectadas como std_msgs/String
mensajes. Por ejemplo, cuando el robot detecta objetos como "caja", "palet" y "carretilla elevadora", publica estas clases en el /ultralytics/detection/classes
tema. Esta información puede ser utilizada por un sistema de monitoreo central para rastrear 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 requerido para la comunicación y se centra en la transmisión de datos críticos.
Uso detallado paso a paso
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 entrante usando YOLO y publicamos los objetos detectados en un nuevo tema. /ultralytics/detection/classes
usando std_msgs/String
mensajes. El ros_numpy
el paquete se utiliza para convertir el mensaje de imagen ROS en un array 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("yolo11m.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 ROS
Además de las imágenes RGB, ROS admite imágenes de profundidad, que proporcionan información sobre la distancia de los objetos desde la cámara. Las imágenes de profundidad son cruciales 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 donde cada píxel representa la distancia desde la cámara hasta 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.
Obtención de imágenes de profundidad
Las imágenes de profundidad se pueden obtener utilizando varios sensores:
- Cámaras estéreo: Utilice dos cámaras para calcular la profundidad basándose en la disparidad de la imagen.
- Cámaras de tiempo de vuelo (ToF): Miden el tiempo que tarda la luz en regresar de un objeto.
- 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 sensor_msgs/Image
tipo de mensaje, que incluye campos para la codificación, la altura, el ancho y los datos de píxeles. El campo de codificación para las imágenes de profundidad a menudo utiliza 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 de imágenes RGB y de profundidad. Por ejemplo, YOLO puede detectar objetos dentro de una imagen RGB, y esta detección se puede utilizar para identificar las regiones correspondientes en la imagen de profundidad. Esto permite la extracción de información de profundidad precisa para los objetos detectados, lo que mejora la capacidad del robot para comprender su entorno en tres dimensiones.
Cámaras RGB-D
Cuando se trabaja 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 la combinación de información de ambas fuentes. Si se utilizan cámaras RGB y de profundidad separadas, es fundamental calibrarlas para garantizar una alineación precisa.
Uso detallado paso a paso de la profundidad
En este ejemplo, utilizamos 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. Comience importando las bibliotecas necesarias, creando un nodo ROS e instanciando un modelo de segmentación y un tema 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("yolo11m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
A continuación, defina una función de callback que procese el mensaje de la imagen de profundidad entrante. La función espera los mensajes de la imagen de profundidad y la imagen RGB, los convierte en arrays 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 media del objeto desde 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 infinito (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 al /ultralytics/detection/distance
tema.
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("yolo11m-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()
Usar Ultralytics con ROS sensor_msgs/PointCloud2
El sensor_msgs/PointCloud2
tipo de mensaje es una estructura de datos utilizada en ROS para representar datos de nube de puntos 3D. Este tipo de mensaje es fundamental para las aplicaciones robóticas, ya que permite 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 a través de tecnologías de escaneo 3D. Cada punto en la nube tiene X
, Y
, y Z
coordenadas, que corresponden a su posición en el espacio, y también pueden incluir información adicional como el color y la intensidad.
Marco de referencia
Cuando se trabaja con sensor_msgs/PointCloud2
, es esencial tener en cuenta el marco de referencia del sensor desde el que se adquirieron los datos de la nube de puntos. La nube de puntos se captura inicialmente en el marco de referencia del sensor. Puede determinar este marco de referencia escuchando el /tf_static
tema. Sin embargo, dependiendo de los requisitos específicos de tu aplicación, es posible que necesites convertir la nube de puntos en otro marco de referencia. Esta transformación se puede lograr utilizando el tf2_ros
paquete, que proporciona herramientas para gestionar los marcos de coordenadas y transformar los datos entre ellos.
Obtención de nubes de puntos
Las nubes de puntos se pueden obtener utilizando varios sensores:
- LIDAR (Light Detection and Ranging): Utiliza pulsos láser para medir distancias a 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 la reconstrucción 3D de la escena.
- Cámaras estéreo: Utilice dos o más cámaras para obtener información de profundidad mediante triangulación.
- Escáneres de luz estructurada: Proyecte un patrón conocido sobre una superficie y mida la deformación para calcular la profundidad.
Uso de YOLO con nubes de puntos
Para integrar YOLO con sensor_msgs/PointCloud2
mensajes de tipo, podemos emplear un método similar al utilizado para los mapas de profundidad. Al aprovechar la información de color incrustada en la nube de puntos, podemos extraer una imagen 2D, realizar la segmentación en esta imagen utilizando 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 en conjunto con la segmentación basada en YOLO.
Uso paso a paso de nubes de puntos
Importe las bibliotecas necesarias e instancie 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("yolo11m-seg.pt")
Crear una función pointcloud2_to_array
, que transforma un sensor_msgs/PointCloud2
mensaje en dos arrays de numpy. El sensor_msgs/PointCloud2
los mensajes contienen n
puntos basados en el width
y height
de la imagen adquirida. Por ejemplo, un 480 x 640
la imagen tendrá 307,200
puntos. Cada punto incluye tres coordenadas espaciales (xyz
) y el color correspondiente en RGB
formato. Estos pueden considerarse como dos canales separados de información.
La función devuelve el xyz
coordenadas y RGB
valores 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 la 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 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 a /camera/depth/points
tema para recibir el mensaje de nube de puntos y convertir el sensor_msgs/PointCloud2
mensaje en arrays de numpy que contienen las coordenadas XYZ y los valores RGB (usando el pointcloud2_to_array
función). Procese la imagen RGB utilizando el modelo YOLO para extraer objetos segmentados. Para cada objeto detectado, extraiga la máscara de segmentación y aplíquela 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, con 1
indicando la presencia del objeto y 0
indicando 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. Finalmente, 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("yolo11m-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])
Preguntas frecuentes
¿Qué es el Sistema Operativo Robótico (ROS)?
El Sistema Operativo Robótico (ROS) es un framework de código abierto comúnmente utilizado en robótica para ayudar a los desarrolladores a crear aplicaciones robóticas robustas. Proporciona una colección de bibliotecas y herramientas para construir e interactuar con sistemas robóticos, lo que facilita el desarrollo de aplicaciones complejas. ROS admite la comunicación entre nodos mediante mensajes a través de temas 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 la configuración de un entorno ROS y el uso de YOLO para el procesamiento de datos de sensores. Comience instalando las dependencias necesarias como ros_numpy
y Ultralytics YOLO:
pip install ros_numpy ultralytics
A continuación, cree un nodo ROS y suscríbase a un tema de imagen para procesar los datos entrantes. Aquí hay un ejemplo mínimo:
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolo11m.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 ROS y cómo se utilizan en Ultralytics YOLO?
Los temas de ROS facilitan la comunicación entre nodos en una red ROS mediante el uso de 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, puede hacer que un nodo se suscriba a un tema de imagen, procese las imágenes utilizando 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 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?
Imágenes de profundidad en ROS, representadas por sensor_msgs/Image
, proporciona la distancia de los objetos desde la cámara, crucial para tareas como la evitación de obstáculos, el mapeo 3D y la localización. Mediante usando información de profundidad junto con las imágenes RGB, los robots pueden comprender mejor su entorno 3D.
Con YOLO, puede 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, lo que mejora 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:
- Convertir
sensor_msgs/PointCloud2
mensajes a arrays de numpy. - Usar YOLO para segmentar imágenes RGB.
- Aplica la máscara de segmentación a la nube de puntos.
Aquí hay un ejemplo usando Open3D para la visualización:
import sys
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("yolo11m-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 objetos segmentados, útil para tareas como la navegación y la manipulación en aplicaciones de robótica.