Guida introduttiva a ROS (sistema operativo per robot)#
Questa guida mostra come integrare Ultralytics YOLO con ROS1 (rospy) o ROS2 (rclpy) per eseguire rilevamento degli oggetti e segmentazione in tempo reale su immagini RGB, immagini di profondità e nuvole di punti.
Vai direttamente alla sezione configurazione di YOLO con ROS, quindi lavora con immagini RGB, immagini di profondità o nuvole di punti.
Che cos'è ROS?#
Il Sistema operativo per robot (ROS) è un framework open source ampiamente utilizzato nella ricerca e nell'industria della robotica. ROS offre una raccolta di librerie e strumenti che aiutano gli sviluppatori a creare applicazioni robotiche. ROS è progettato per funzionare con diverse piattaforme robotiche, rendendolo uno strumento flessibile e potente per chi lavora nel campo della robotica. Per una breve introduzione, guarda il video di tre minuti Introduzione a ROS di Open Robotics.
Caratteristiche principali di ROS#
-
Architettura modulare: ROS ha un'architettura modulare che consente agli sviluppatori di creare sistemi complessi combinando componenti più piccoli e riutilizzabili chiamati nodi. Ogni nodo svolge in genere una funzione specifica e i nodi comunicano tra loro utilizzando messaggi tramite topic o servizi.
-
Middleware di comunicazione: ROS offre un'infrastruttura di comunicazione robusta che supporta la comunicazione tra processi e il calcolo distribuito. Questo avviene tramite un modello publish-subscribe per i flussi di dati (topic) e un modello request-reply per le chiamate ai servizi.
-
Astrazione dell'hardware: ROS fornisce un livello di astrazione sull'hardware, consentendo agli sviluppatori di scrivere codice indipendente dal dispositivo. In questo modo è possibile usare lo stesso codice con diverse configurazioni hardware, facilitando l'integrazione e la sperimentazione.
-
Strumenti e utilità: ROS include un ricco insieme di strumenti e utilità per la visualizzazione, il debug e la simulazione. Ad esempio, RViz viene utilizzato per visualizzare i dati dei sensori e le informazioni sullo stato del robot, mentre Gazebo offre un potente ambiente di simulazione per testare algoritmi e progetti di robot.
-
Ecosistema esteso: l'ecosistema ROS è vasto e in continua crescita, con numerosi pacchetti disponibili per diverse applicazioni robotiche, tra cui navigazione, manipolazione, percezione e altro. La community contribuisce attivamente allo sviluppo e alla manutenzione di questi pacchetti.
Evoluzione delle versioni di ROS
Dalla sua creazione nel 2007, ROS si è evoluto attraverso molteplici versioni, suddivise in ROS 1 e ROS 2. Gli esempi esistenti riportati di seguito utilizzano ROS1 Noetic; gli adattatori compatti nella sezione Utilizzo di ROS2 mostrano le interfacce rclpy corrispondenti per le versioni attuali di ROS2.
ROS 1 e ROS 2 a confronto#
Sebbene ROS 1 fornisse una solida base per lo sviluppo robotico, ROS 2 ne affronta i limiti offrendo:
- Prestazioni in tempo reale: supporto migliorato per i sistemi in tempo reale e comportamento deterministico.
- Sicurezza: funzionalità di sicurezza avanzate per un funzionamento sicuro e affidabile in diversi ambienti.
- Scalabilità: supporto migliore per i sistemi multi-robot e le implementazioni su larga scala.
- Supporto multipiattaforma: compatibilità ampliata con diversi sistemi operativi oltre a Linux, tra cui Windows e macOS.
- Comunicazione flessibile: utilizzo di DDS per una comunicazione tra processi più flessibile ed efficiente.
Messaggi e topic di ROS#
In ROS, la comunicazione tra i nodi avviene tramite messaggi e topic. Un messaggio è una struttura dati che definisce le informazioni scambiate tra i nodi, mentre un topic è un canale denominato attraverso il quale i messaggi vengono inviati e ricevuti. I nodi possono pubblicare messaggi su un topic o sottoscriversi ai messaggi di un topic, consentendo loro di comunicare tra loro. Questo modello publish-subscribe permette la comunicazione asincrona e il disaccoppiamento tra i nodi. In genere, ogni sensore o attuatore di un sistema robotico pubblica i dati su un topic, che può quindi essere utilizzato da altri nodi per l'elaborazione o il controllo. Ai fini di questa guida, ci concentreremo sui messaggi Image, Depth e PointCloud e sui topic delle fotocamere.
Configurazione di Ultralytics YOLO con ROS#
Gli esempi ROS1 sono stati testati utilizzando questo ambiente ROS, un fork del repository ROS di ROSbot. La stessa elaborazione con YOLO e NumPy si applica in ROS2; cambiano solo il ciclo di vita del nodo e la conversione dei messaggi.
Installazione delle dipendenze#
Oltre all'ambiente ROS, dovrai installare le seguenti dipendenze:
-
Pacchetto ROS NumPy: è necessario per convertire rapidamente i messaggi Image di ROS in array NumPy e viceversa.
pip install ros_numpy -
Pacchetto Ultralytics:
pip install ultralytics
Utilizzo di ROS2#
ROS2 sostituisce rospy con rclpy e la conversione delle immagini ros_numpy con cv_bridge. Il nodo seguente è l'equivalente completo in ROS2 del flusso di rilevamento RGB riportato sotto; istanzia i modelli una sola volta e riutilizzali nelle callback.
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()Per le immagini di profondità, riutilizza il codice di elaborazione della profondità riportato sotto e sostituisci solo l'acquisizione e la conversione dei messaggi:
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.Per le nuvole di punti, ROS2 fornisce sensor_msgs_py.point_cloud2; converti una volta la nuvola organizzata, quindi riutilizza la segmentazione NumPy e la mappatura 3D riportate sotto:
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)Usare Ultralytics con ROS sensor_msgs/Image#
Il tipo di messaggio sensor_msgs/Image è comunemente utilizzato in ROS per rappresentare i dati delle immagini. Contiene campi per la codifica, l'altezza, la larghezza e i dati dei pixel, rendendolo adatto alla trasmissione di immagini acquisite da fotocamere o altri sensori. I messaggi Image sono ampiamente utilizzati nelle applicazioni robotiche per attività come la percezione visiva, il rilevamento degli oggetti e la navigazione.
Utilizzo delle immagini passo dopo passo#
Il seguente frammento di codice mostra come usare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci iscriviamo a un topic della fotocamera, elaboriamo l'immagine in ingresso usando YOLO e pubblichiamo gli oggetti rilevati su nuovi topic per il rilevamento e la segmentazione.
Per prima cosa, importa le librerie necessarie e istanzia due modelli: uno per la segmentazione e uno per il rilevamento. Inizializza un nodo ROS (con il nome ultralytics) per abilitare la comunicazione con il master ROS. Per garantire una connessione stabile, includiamo una breve pausa, dando al nodo tempo sufficiente per stabilire la connessione prima di procedere.
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)Inizializza due topic ROS: uno per il rilevamento e uno per la segmentazione. Questi topic verranno utilizzati per pubblicare le immagini annotate, rendendole accessibili per ulteriori elaborazioni. La comunicazione tra i nodi avviene utilizzando messaggi 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)Infine, crea un sottoscrittore che ascolti i messaggi sul topic /camera/color/image_raw e chiami una funzione callback per ogni nuovo messaggio. Questa funzione callback riceve messaggi di tipo sensor_msgs/Image, li converte in un array NumPy usando ros_numpy, elabora le immagini con i modelli YOLO precedentemente istanziati, annota le immagini e quindi le pubblica nuovamente sui rispettivi topic: /ultralytics/detection/image per il rilevamento e /ultralytics/segmentation/image per la segmentazione.
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()Codice 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()Debug
Eseguire il debug dei nodi ROS (sistema operativo per robot) può essere difficile a causa della natura distribuita del sistema. Diversi strumenti possono aiutare in questo processo:
rostopic echo <TOPIC-NAME>: questo comando consente di visualizzare i messaggi pubblicati su un topic specifico, aiutandoti a esaminare il flusso dei dati.rostopic list: usa questo comando per elencare tutti i topic disponibili nel sistema ROS e ottenere una panoramica dei flussi di dati attivi.rqt_graph: questo strumento di visualizzazione mostra il grafo di comunicazione tra i nodi, fornendo informazioni su come sono interconnessi e interagiscono.- Per visualizzazioni più complesse, come le rappresentazioni 3D, puoi usare RViz. RViz (visualizzazione ROS) è un potente strumento di visualizzazione 3D per ROS. Ti consente di visualizzare lo stato del robot e del suo ambiente in tempo reale. Con RViz puoi visualizzare i dati dei sensori (ad esempio,
sensor_msgs/Image), gli stati del modello del robot e vari altri tipi di informazioni, semplificando il debug e la comprensione del comportamento del sistema robotico.
Pubblicazione delle classi rilevate con std_msgs/String#
I messaggi ROS standard includono anche messaggi std_msgs/String. In molte applicazioni non è necessario ripubblicare l'intera immagine annotata; sono invece necessarie solo le classi presenti nel campo visivo del robot. L'esempio seguente mostra come usare i messaggi std_msgs/String per ripubblicare le classi rilevate sul topic /ultralytics/detection/classes. Questi messaggi sono più leggeri e forniscono le informazioni essenziali, risultando utili in diverse applicazioni.
Esempio di caso d'uso#
Considera un robot da magazzino dotato di una fotocamera e di un modello di rilevamento degli oggetti. Invece di inviare immagini annotate di grandi dimensioni attraverso la rete, il robot può pubblicare un elenco delle classi rilevate come messaggi std_msgs/String. Ad esempio, quando il robot rileva oggetti come "scatola", "pallet" e "carrello elevatore", pubblica queste classi sul topic /ultralytics/detection/classes. Un sistema di monitoraggio centrale può quindi usare queste informazioni per monitorare l'inventario in tempo reale, ottimizzare la pianificazione del percorso del robot per evitare ostacoli o attivare azioni specifiche, come raccogliere una scatola rilevata. Questo approccio riduce la larghezza di banda necessaria per la comunicazione e si concentra sulla trasmissione dei dati fondamentali.
Utilizzo delle stringhe passo dopo passo#
Questo esempio mostra come usare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci iscriviamo a un topic della fotocamera, elaboriamo l'immagine in ingresso usando YOLO e pubblichiamo gli oggetti rilevati sul nuovo topic /ultralytics/detection/classes usando messaggi std_msgs/String. Il pacchetto ros_numpy viene utilizzato per convertire il messaggio Image di ROS in un array NumPy da elaborare 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()Usare Ultralytics con le immagini di profondità ROS#
Oltre alle immagini RGB, ROS supporta le immagini di profondità, che forniscono informazioni sulla distanza degli oggetti dalla fotocamera. Le immagini di profondità sono fondamentali per applicazioni robotiche come l'evitamento degli ostacoli, la mappatura 3D e la localizzazione.
Un'immagine di profondità è un'immagine in cui ogni pixel rappresenta la distanza dalla fotocamera a un oggetto. A differenza delle immagini RGB, che acquisiscono il colore, le immagini di profondità acquisiscono informazioni spaziali, consentendo ai robot di percepire la struttura 3D dell'ambiente.
Le immagini di profondità possono essere ottenute utilizzando diversi sensori:
- Fotocamere stereo: usano due fotocamere per calcolare la profondità in base alla disparità dell'immagine.
- Fotocamere time-of-flight (ToF): misurano il tempo impiegato dalla luce per tornare da un oggetto.
- Sensori a luce strutturata: proiettano un pattern e ne misurano la deformazione sulle superfici.
Utilizzo di YOLO con le immagini di profondità#
In ROS, le immagini di profondità sono rappresentate dal tipo di messaggio sensor_msgs/Image, che include campi per la codifica, l'altezza, la larghezza e i dati dei pixel. Il campo di codifica delle immagini di profondità usa spesso un formato come "16UC1", che indica un intero senza segno a 16 bit per pixel, dove ogni valore rappresenta la distanza dall'oggetto. Le immagini di profondità vengono comunemente utilizzate insieme alle immagini RGB per offrire una visione più completa dell'ambiente.
Utilizzando YOLO, è possibile estrarre e combinare le informazioni delle immagini RGB e di profondità. Ad esempio, YOLO può rilevare oggetti all'interno di un'immagine RGB e questo rilevamento può essere usato per individuare le regioni corrispondenti nell'immagine di profondità. Ciò consente di estrarre informazioni precise sulla profondità degli oggetti rilevati, migliorando la capacità del robot di comprendere l'ambiente in tre dimensioni.
Quando lavori con immagini di profondità, è essenziale assicurarti che le immagini RGB e di profondità siano correttamente allineate. Le fotocamere RGB-D, come la serie Intel RealSense, forniscono immagini RGB e di profondità sincronizzate, semplificando la combinazione delle informazioni provenienti da entrambe le fonti. Se usi fotocamere RGB e di profondità separate, è fondamentale calibrarle per garantire un allineamento accurato.
Utilizzo della profondità passo dopo passo#
In questo esempio, usiamo YOLO per segmentare un'immagine e applichiamo la maschera estratta per segmentare l'oggetto nell'immagine di profondità. Ciò consente di determinare la distanza di ogni pixel dell'oggetto di interesse dal centro focale della fotocamera. Ottenendo queste informazioni sulla distanza, possiamo calcolare la distanza tra la fotocamera e l'oggetto specifico nella scena. Inizia importando le librerie necessarie, creando un nodo ROS e istanziando un modello di segmentazione e un topic 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)Successivamente, definisci una funzione callback che elabori il messaggio dell'immagine di profondità in ingresso. La funzione attende i messaggi dell'immagine di profondità e dell'immagine RGB, li converte in array NumPy e applica il modello di segmentazione all'immagine RGB. Estrae quindi la maschera di segmentazione per ogni oggetto rilevato e calcola la distanza media dell'oggetto dalla fotocamera usando l'immagine di profondità. La maggior parte dei sensori ha una distanza massima, nota come distanza di ritaglio, oltre la quale i valori sono rappresentati come inf (np.inf). Prima dell'elaborazione, è importante filtrare questi valori nulli e assegnare loro il valore 0. Infine, pubblica gli oggetti rilevati insieme alle relative distanze medie sul topic /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()Codice 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()Usare Ultralytics con ROS sensor_msgs/PointCloud2#
Il tipo di messaggio sensor_msgs/PointCloud2 è una struttura dati utilizzata in ROS per rappresentare dati di nuvole di punti 3D. Questo tipo di messaggio è fondamentale nelle applicazioni robotiche e abilita attività come la mappatura 3D, il riconoscimento degli oggetti e la localizzazione.
Una nuvola di punti è una raccolta di punti dati definiti all'interno di un sistema di coordinate tridimensionale. Questi punti dati rappresentano la superficie esterna di un oggetto o di una scena, acquisita tramite tecnologie di scansione 3D. Ogni punto della nuvola ha coordinate X, Y e Z, che corrispondono alla sua posizione nello spazio, e può includere anche informazioni aggiuntive come colore e intensità.
Quando lavori con sensor_msgs/PointCloud2, è essenziale considerare il sistema di riferimento del sensore da cui sono stati acquisiti i dati della nuvola di punti. La nuvola di punti viene inizialmente acquisita nel sistema di riferimento del sensore. Puoi determinare questo sistema di riferimento ascoltando il topic /tf_static. Tuttavia, a seconda dei requisiti specifici dell'applicazione, potrebbe essere necessario convertire la nuvola di punti in un altro sistema di riferimento. Questa trasformazione può essere eseguita utilizzando il pacchetto tf2_ros, che fornisce strumenti per gestire i sistemi di coordinate e trasformare i dati tra di essi.
Le nuvole di punti possono essere ottenute utilizzando diversi sensori:
- LIDAR (rilevamento e misurazione della distanza tramite luce): usa impulsi laser per misurare le distanze dagli oggetti e creare mappe 3D ad alta precisione.
- Fotocamere di profondità: acquisiscono le informazioni sulla profondità di ogni pixel, consentendo la ricostruzione 3D della scena.
- Fotocamere stereo: utilizzano due o più fotocamere per ottenere informazioni sulla profondità tramite triangolazione.
- Scanner a luce strutturata: proiettano un pattern noto su una superficie e ne misurano la deformazione per calcolare la profondità.
Utilizzo di YOLO con le nuvole di punti#
Per integrare YOLO con i messaggi di tipo sensor_msgs/PointCloud2, possiamo usare un metodo simile a quello impiegato per le mappe di profondità. Sfruttando le informazioni sul colore incorporate nella nuvola di punti, possiamo estrarre un'immagine 2D, eseguire la segmentazione su questa immagine usando YOLO e quindi applicare la maschera risultante ai punti tridimensionali per isolare l'oggetto 3D di interesse.
Per gestire le nuvole di punti, consigliamo di usare Open3D (pip install open3d), una libreria Python intuitiva. Open3D fornisce strumenti solidi per gestire le strutture dati delle nuvole di punti, visualizzarle ed eseguire operazioni complesse in modo semplice. Questa libreria può semplificare notevolmente il processo e migliorare la nostra capacità di manipolare e analizzare le nuvole di punti insieme alla segmentazione basata su YOLO.
Utilizzo delle nuvole di punti passo dopo passo#
Importa le librerie necessarie e istanzia il modello YOLO per la segmentazione.
import time
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")Crea una funzione pointcloud2_to_array che trasformi un messaggio sensor_msgs/PointCloud2 in due array NumPy. I messaggi sensor_msgs/PointCloud2 contengono punti n basati su width e height dell'immagine acquisita. Ad esempio, un'immagine 480 x 640 avrà punti 307,200. Ogni punto include tre coordinate spaziali (xyz) e il colore corrispondente nel formato RGB. Questi possono essere considerati due canali di informazioni separati.
La funzione restituisce le coordinate xyz e i valori RGB nel formato della risoluzione originale della fotocamera (width x height). La maggior parte dei sensori ha una distanza massima, nota come distanza di ritaglio, oltre la quale i valori sono rappresentati come inf (np.inf). Prima dell'elaborazione, è importante filtrare questi valori nulli e assegnare loro il valore 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, rgbSuccessivamente, sottoscriviti al topic /camera/depth/points per ricevere il messaggio della nuvola di punti e converti il messaggio sensor_msgs/PointCloud2 in array NumPy contenenti le coordinate XYZ e i valori RGB (usando la funzione pointcloud2_to_array). Elabora l'immagine RGB usando il modello YOLO per estrarre gli oggetti segmentati. Per ogni oggetto rilevato, estrai la maschera di segmentazione e applicala sia all'immagine RGB sia alle coordinate XYZ per isolare l'oggetto nello spazio 3D.
L'elaborazione della maschera è semplice poiché consiste di valori binari, con 1 che indica la presenza dell'oggetto e 0 che ne indica l'assenza. Per applicare la maschera, moltiplica semplicemente i canali originali per la maschera. Questa operazione isola efficacemente l'oggetto di interesse nell'immagine. Infine, crea un oggetto nuvola di punti Open3D e visualizza l'oggetto segmentato nello spazio 3D con i colori associati.
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])Codice 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])
Conclusioni#
Con Ultralytics YOLO integrato in ROS, il tuo robot può eseguire il rilevamento degli oggetti e la segmentazione su immagini RGB, immagini di profondità e nuvole di punti, trasformando i flussi grezzi dei sensori in informazioni percettive utilizzabili. Da qui, esplora la modalità Predict per ulteriori opzioni di inferenza oppure segui i passaggi di un progetto di computer vision per portare la tua applicazione robotica dal prototipo alla produzione.
FAQ#
Il Sistema operativo per robot (ROS) è un framework open source comunemente utilizzato nella robotica per aiutare gli sviluppatori a creare applicazioni robotiche robuste. Offre una raccolta di librerie e strumenti per creare sistemi robotici e interfacciarsi con essi, semplificando lo sviluppo di applicazioni complesse. ROS supporta la comunicazione tra i nodi tramite messaggi su topic o servizi.
L'integrazione di Ultralytics YOLO con ROS richiede la configurazione di un ambiente ROS e l'uso di YOLO per elaborare i dati dei sensori. Inizia installando le dipendenze necessarie, come
ros_numpye Ultralytics YOLO:pip install ros_numpy ultralyticsSuccessivamente, crea un nodo ROS e sottoscriviti a un topic delle immagini per elaborare i dati in ingresso per il rilevamento degli oggetti. Ecco un esempio minimo:
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()I topic ROS facilitano la comunicazione tra i nodi in una rete ROS utilizzando un modello publish-subscribe. Un topic è un canale denominato che i nodi usano per inviare e ricevere messaggi in modo asincrono. Nel contesto di Ultralytics YOLO, puoi fare in modo che un nodo si sottoscriva a un topic delle immagini, elabori le immagini usando YOLO per attività come il rilevamento o la segmentazione e pubblichi i risultati su nuovi topic.
Ad esempio, sottoscriviti a un topic della fotocamera ed elabora l'immagine in ingresso per il rilevamento:
rospy.Subscriber("/camera/color/image_raw", Image, callback)Le immagini di profondità in ROS, rappresentate da
sensor_msgs/Image, forniscono la distanza degli oggetti dalla fotocamera, fondamentale per attività come l'evitamento degli ostacoli, la mappatura 3D e la localizzazione. Usando le informazioni sulla profondità insieme alle immagini RGB, i robot possono comprendere meglio il proprio ambiente 3D.Con YOLO puoi estrarre maschere di segmentazione dalle immagini RGB e applicarle alle immagini di profondità per ottenere informazioni 3D precise sugli oggetti, migliorando la capacità del robot di navigare e interagire con l'ambiente circostante.
Per visualizzare nuvole di punti 3D in ROS con YOLO:
- Converti i messaggi
sensor_msgs/PointCloud2in array NumPy. - Usa YOLO per segmentare le immagini RGB.
- Applica la maschera di segmentazione alla nuvola di punti.
Ecco un esempio che usa Open3D per la visualizzazione:
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])Questo approccio fornisce una visualizzazione 3D degli oggetti segmentati, utile per attività come la navigazione e la manipolazione nelle applicazioni di robotica.
- Converti i messaggi