Guida rapida al ROS (Robot Operating System)
Introduzione a ROS (didascalia) da Open Robotics su Vimeo.
Che cos'è il ROS?
Il Robot Operating System (ROS) è un framework open-source ampiamente utilizzato nella ricerca e nell'industria robotica. ROS fornisce una raccolta di librerie e strumenti per aiutare gli sviluppatori a creare applicazioni robotiche. ROS è progettato per funzionare con diverse piattaforme robotiche, il che lo rende uno strumento flessibile e potente per i robotisti.
Caratteristiche principali di ROS
-
Architettura modulare: ROS ha un'architettura modulare che consente agli sviluppatori di costruire sistemi complessi combinando componenti più piccoli e riutilizzabili chiamati nodi. Ogni nodo svolge una funzione specifica e i nodi comunicano tra loro utilizzando messaggi su argomenti o servizi.
-
Middleware di comunicazione: ROS offre una solida infrastruttura di comunicazione che supporta la comunicazione tra processi e l'elaborazione distribuita. Ciò avviene attraverso un modello publish-subscribe per i flussi di dati (topic) e un modello request-reply per le chiamate di servizio.
-
Astrazione dell'hardware: ROS fornisce un livello di astrazione sull'hardware, consentendo agli sviluppatori di scrivere codice indipendente dal dispositivo. Ciò consente di utilizzare lo stesso codice con diverse configurazioni hardware, facilitando l'integrazione e la sperimentazione.
-
Strumenti e utilità: ROS è dotato di una ricca serie di strumenti e utilità per la visualizzazione, il debug e la simulazione. Ad esempio, RViz è utilizzato per visualizzare i dati dei sensori e le informazioni sullo stato del robot, mentre Gazebo fornisce 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 comunità contribuisce attivamente allo sviluppo e alla manutenzione di questi pacchetti.
Evoluzione delle versioni di ROS
Dal suo sviluppo nel 2007, ROS si è evoluto attraverso diverse versioni, ognuna delle quali ha introdotto nuove funzionalità e miglioramenti per soddisfare le crescenti esigenze della comunità robotica. Lo sviluppo di ROS può essere classificato in due serie principali: ROS 1 e ROS 2. Questa guida si concentra sulla versione Long Term Support (LTS) di ROS 1, nota come ROS Noetic Ninjemys, ma il codice dovrebbe funzionare anche con le versioni precedenti.
ROS 1 vs. ROS 2
Mentre ROS 1 ha fornito una solida base per lo sviluppo robotico, ROS 2 ne colma le lacune offrendo:
- Prestazioni in tempo reale: Supporto migliorato per sistemi in tempo reale e comportamento deterministico.
- Sicurezza: Funzioni di sicurezza avanzate per un funzionamento sicuro e affidabile in vari ambienti.
- Scalabilità: Migliore supporto per i sistemi multi-robot e le implementazioni su larga scala.
- Supporto multipiattaforma: Compatibilità ampliata con vari sistemi operativi oltre a Linux, tra cui Windows e macOS.
- Comunicazione flessibile: Uso di DDS per una comunicazione interprocesso più flessibile ed efficiente.
Messaggi e argomenti ROS
In ROS, la comunicazione tra i nodi è facilitata da messaggi e argomenti. Un messaggio è una struttura di dati che definisce le informazioni scambiate tra i nodi, mentre un argomento è un canale denominato su cui vengono inviati e ricevuti i messaggi. I nodi possono pubblicare messaggi su un argomento o sottoscrivere messaggi da un argomento, consentendo loro di comunicare tra loro. Questo modello publish-subscribe consente la comunicazione asincrona e il disaccoppiamento tra i nodi. Ogni sensore o attuatore di un sistema robotico pubblica tipicamente dati in un argomento, che possono poi essere utilizzati da altri nodi per l'elaborazione o il controllo. Ai fini di questa guida, ci concentreremo sui messaggi Image, Depth e PointCloud e sugli argomenti delle telecamere.
Impostazione di Ultralytics YOLO con ROS
Questa guida è stata testata utilizzando questo ambiente ROS, che è un fork del repository ROSbot ROS. Questo ambiente include il pacchetto Ultralytics YOLO , un contenitore Docker per una facile configurazione, pacchetti ROS completi e mondi Gazebo per un test rapido. È progettato per funzionare con Husarion ROSbot 2 PRO. Gli esempi di codice forniti funzionano in qualsiasi ambiente ROS Noetic/Melodic, sia di simulazione che reale.
Installazione delle dipendenze
Oltre all'ambiente ROS, è necessario installare le seguenti dipendenze:
-
Pacchetto ROS Numpy: È necessario per la conversione rapida tra i messaggi ROS Image e gli array numpy.
-
Ultralytics pacchetto:
Utilizzare Ultralytics con ROS sensor_msgs/Image
Il sensor_msgs/Image
tipo di messaggio è comunemente usato in ROS per rappresentare i dati delle immagini. Contiene campi per la codifica, l'altezza, la larghezza e i dati dei pixel, che lo rendono adatto alla trasmissione di immagini catturate da telecamere o altri sensori. I messaggi immagine sono ampiamente utilizzati nelle applicazioni robotiche per compiti quali la percezione visiva, rilevamento degli oggettie la navigazione.
Immagine Utilizzo passo-passo
Il seguente frammento di codice dimostra come utilizzare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci si iscrive a un topic della telecamera, si elabora l'immagine in arrivo usando YOLO e si pubblicano gli oggetti rilevati in nuovi topic per il rilevamento e la segmentazione.
Per prima cosa, si importano le librerie necessarie e si istanziano due modelli: uno per segmentazione e uno per rilevamento. Inizializzare un nodo ROS (con il nome di ultralytics
) per consentire la comunicazione con il master ROS. Per garantire una connessione stabile, includiamo una breve pausa, dando al nodo il tempo sufficiente per stabilire la connessione prima di procedere.
import time
import rospy
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
segmentation_model = YOLO("yolov8m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
Inizializzare due argomenti ROS: uno per rilevamento e uno per segmentazione. Questi argomenti saranno utilizzati per pubblicare le immagini annotate, rendendole accessibili per ulteriori elaborazioni. La comunicazione tra i nodi è facilitata dall'uso di sensor_msgs/Image
messaggi.
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, creare un sottoscrittore che ascolti i messaggi sulla cartella /camera/color/image_raw
e chiama una funzione di callback per ogni nuovo messaggio. Questa funzione di callback riceve messaggi di tipo sensor_msgs/Image
, li converte in un array numpy utilizzando ros_numpy
, elabora le immagini con i modelli YOLO istanziati in precedenza, annota le immagini e le pubblica di nuovo nei 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("yolov8m.pt")
segmentation_model = YOLO("yolov8m-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
Il debug dei nodi ROS (Robot Operating System) 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 argomento specifico, aiutandovi a ispezionare il flusso di dati.rostopic list
: Utilizzare questo comando per elencare tutti gli argomenti disponibili nel sistema ROS, fornendo una panoramica dei flussi di dati attivi.rqt_graph
: Questo strumento di visualizzazione mostra il grafico delle comunicazioni tra i nodi, fornendo informazioni su come i nodi sono interconnessi e come interagiscono.- Per visualizzazioni più complesse, come le rappresentazioni in 3D, si può usare RViz. RViz (ROS Visualization) è un potente strumento di visualizzazione 3D per ROS. Permette di visualizzare lo stato del robot e del suo ambiente in tempo reale. Con RViz è possibile visualizzare i dati dei sensori (ad es.
sensors_msgs/Image
), gli stati del modello del robot e vari altri tipi di informazioni, rendendo più facile il debug e la comprensione del comportamento del sistema robotico.
Pubblicare le classi rilevate con std_msgs/String
I messaggi standard di ROS includono anche std_msgs/String
messaggi. In molte applicazioni non è necessario ripubblicare l'intera immagine annotata, ma sono necessarie solo le classi presenti nella vista del robot. L'esempio seguente mostra come utilizzare std_msgs/String
messaggi per ripubblicare le classi rilevate sul sito /ultralytics/detection/classes
argomento. Questi messaggi sono più leggeri e forniscono informazioni essenziali, rendendoli preziosi per varie applicazioni.
Caso d'uso esemplificativo
Si consideri un robot da magazzino dotato di telecamera e di un oggetto modello di rilevamento. Invece di inviare in rete immagini annotate di grandi dimensioni, il robot può pubblicare un elenco di classi rilevate come std_msgs/String
messaggi. Ad esempio, quando il robot individua oggetti come "scatola", "pallet" e "carrello elevatore", pubblica queste classi nel sistema di gestione dei dati. /ultralytics/detection/classes
argomento. Queste informazioni possono essere utilizzate da un sistema di monitoraggio centrale per tracciare l'inventario in tempo reale, ottimizzare la pianificazione del percorso del robot per evitare gli ostacoli o attivare azioni specifiche come il prelievo di una scatola rilevata. Questo approccio riduce la larghezza di banda necessaria per la comunicazione e si concentra sulla trasmissione dei dati critici.
Stringa Utilizzo passo-passo
Questo esempio dimostra come utilizzare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci si iscrive all'argomento di una telecamera, si elabora l'immagine in arrivo usando YOLO e si pubblicano gli oggetti rilevati su un nuovo argomento /ultralytics/detection/classes
utilizzando std_msgs/String
messaggi. Il ros_numpy
viene utilizzato per convertire il messaggio ROS Image 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("yolov8m.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()
Utilizzare Ultralytics con le immagini di profondità di ROS
Oltre alle immagini RGB, ROS supporta le immagini di profondità, che forniscono informazioni sulla distanza degli oggetti dalla telecamera. Le immagini di profondità sono fondamentali per le 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 tra la telecamera e un oggetto. A differenza delle immagini RGB che catturano il colore, le immagini di profondità catturano le informazioni spaziali, consentendo ai robot di percepire la struttura 3D del loro ambiente.
Ottenere immagini di profondità
Le immagini di profondità possono essere ottenute con diversi sensori:
- Telecamere stereo: Utilizza due telecamere per calcolare la profondità in base alla disparità delle immagini.
- Telecamere a tempo di volo (ToF): Misurano il tempo di ritorno della luce da un oggetto.
- Sensori a luce strutturata: Proiettano un disegno e ne misurano la deformazione sulle superfici.
Utilizzo di YOLO con le immagini di profondità
In ROS, le immagini di profondità sono rappresentate dall'elemento sensor_msgs/Image
che comprende i campi per la codifica, l'altezza, la larghezza e i dati dei pixel. Il campo di codifica per le immagini di profondità utilizza 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à sono comunemente utilizzate insieme alle immagini RGB per fornire una visione più completa dell'ambiente.
Utilizzando YOLO, è possibile estrarre e combinare informazioni da immagini RGB e di profondità. Ad esempio, YOLO è in grado di rilevare gli oggetti all'interno di un'immagine RGB e questo rilevamento può essere utilizzato 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.
Telecamere RGB-D
Quando si lavora con le immagini di profondità, è essenziale garantire che le immagini RGB e di profondità siano allineate correttamente. Le telecamere RGB-D, come la serie RealSense di Intel , forniscono immagini RGB e di profondità sincronizzate, facilitando la combinazione delle informazioni provenienti da entrambe le fonti. Se si utilizzano telecamere RGB e di profondità separate, è fondamentale calibrarle per garantire un allineamento preciso.
Profondità d'uso passo dopo passo
In questo esempio, utilizziamo YOLO per segmentare un'immagine e applichiamo la maschera estratta per segmentare l'oggetto nell'immagine di profondità. Questo ci permette di determinare la distanza di ciascun pixel dell'oggetto di interesse dal centro focale della telecamera. Ottenendo queste informazioni sulla distanza, possiamo calcolare la distanza tra la telecamera e l'oggetto specifico nella scena. Si inizia importando le librerie necessarie, creando un nodo ROS e istanziando un modello di segmentazione e un argomento 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("yolov8m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
Quindi, definire una funzione di callback che elabori il messaggio di immagine di profondità in arrivo. 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. Quindi estrae la maschera di segmentazione per ogni oggetto rilevato e calcola la distanza media dell'oggetto dalla telecamera utilizzando l'immagine di profondità. La maggior parte dei sensori ha una distanza massima, nota come distanza di clip, oltre la quale i valori sono rappresentati come inf (np.inf
). Prima dell'elaborazione, è importante filtrare questi valori nulli e assegnare loro un valore di 0
. Infine, pubblica gli oggetti rilevati insieme alle loro distanze medie alla /ultralytics/detection/distance
argomento.
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)
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
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("yolov8m-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)
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
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()
Utilizzare Ultralytics con ROS sensor_msgs/PointCloud2
Il sensor_msgs/PointCloud2
tipo di messaggio è una struttura di dati utilizzata in ROS per rappresentare i dati delle nuvole di punti 3D. Questo tipo di messaggio è parte integrante delle applicazioni robotiche e consente di eseguire operazioni come la mappatura 3D, il riconoscimento degli oggetti e la localizzazione.
Una nuvola di punti è una raccolta di punti di dati definiti all'interno di un sistema di coordinate tridimensionale. Questi punti rappresentano la superficie esterna di un oggetto o di una scena, acquisita tramite tecnologie di scansione 3D. Ogni punto della nuvola ha X
, Y
, e Z
coordinate, che corrispondono alla sua posizione nello spazio, e possono anche includere informazioni aggiuntive come il colore e l'intensità.
Quadro di riferimento
Quando si lavora con sensor_msgs/PointCloud2
È essenziale considerare il quadro di riferimento del sensore da cui sono stati acquisiti i dati della nuvola di punti. La nuvola di punti viene inizialmente acquisita nell'inquadratura di riferimento del sensore. È possibile determinare tale inquadratura di riferimento ascoltando l'immagine del sensore /tf_static
argomento. Tuttavia, a seconda dei requisiti specifici dell'applicazione, potrebbe essere necessario convertire la nuvola di punti in un altro quadro di riferimento. Questa trasformazione può essere ottenuta utilizzando l'opzione tf2_ros
che fornisce strumenti per la gestione dei quadri di coordinate e la trasformazione dei dati tra di essi.
Ottenere nuvole di punti
Le nuvole di punti possono essere ottenute utilizzando vari sensori:
- LIDAR (Light Detection and Ranging): Utilizza impulsi laser per misurare le distanze degli oggetti e creare mappe 3D di alta precisione.
- Telecamere di profondità: Acquisiscono informazioni sulla profondità per ogni pixel, consentendo la ricostruzione 3D della scena.
- Telecamere stereo: Utilizzano due o più telecamere per ottenere informazioni sulla profondità attraverso la triangolazione.
- Scanner a luce strutturata: Proiettano un modello noto su una superficie e misurano la deformazione per calcolare la profondità.
Utilizzo di YOLO con le nuvole di punti
Per integrare YOLO con sensor_msgs/PointCloud2
Per i messaggi di questo tipo, possiamo utilizzare un metodo simile a quello usato 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 utilizzando YOLO e quindi applicare la maschera risultante ai punti tridimensionali per isolare l'oggetto 3D di interesse.
Per gestire le nuvole di punti, si consiglia di utilizzare Open3D (pip install open3d
), una libreria Python di facile utilizzo. Open3D fornisce strumenti robusti per gestire le strutture di dati delle nuvole di punti, visualizzarle ed eseguire operazioni complesse senza problemi. Questa libreria può semplificare significativamente il processo e migliorare la nostra capacità di manipolare e analizzare le nuvole di punti insieme alla segmentazione basata su YOLO.
Nuvole di punti Utilizzo passo dopo passo
Importare le librerie necessarie e istanziare il modello YOLO per la segmentazione.
import time
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
Creare una funzione pointcloud2_to_array
, che trasforma un sensor_msgs/PointCloud2
in due array numpy. Il sensor_msgs/PointCloud2
i messaggi contengono n
punti in base alla width
e height
dell'immagine acquisita. Ad esempio, un 480 x 640
l'immagine avrà 307,200
punti. Ogni punto comprende tre coordinate spaziali (xyz
) e il colore corrispondente in RGB
formato. Questi possono essere considerati due canali di informazione separati.
La funzione restituisce il valore xyz
coordinate e RGB
nel formato della risoluzione originale della fotocamera (width x height
). La maggior parte dei sensori ha una distanza massima, nota come distanza di clip, oltre la quale i valori sono rappresentati come inf (np.inf
). Prima dell'elaborazione, è importante filtrare questi valori nulli e assegnare loro un valore di 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
Successivamente, sottoscrivere il file /camera/depth/points
per ricevere il messaggio della nuvola di punti e convertire il file sensor_msgs/PointCloud2
in array numpy contenenti le coordinate XYZ e i valori RGB (utilizzando l'opzione pointcloud2_to_array
funzione). Elaborare l'immagine RGB utilizzando il modello YOLO per estrarre gli oggetti segmentati. Per ogni oggetto rilevato, estrarre la maschera di segmentazione e applicarla sia all'immagine RGB sia alle coordinate XYZ per isolare l'oggetto nello spazio 3D.
L'elaborazione della maschera è semplice, poiché è costituita da valori binari con 1
che indica la presenza dell'oggetto e 0
che indica l'assenza. Per applicare la maschera, è sufficiente moltiplicare i canali originali per la maschera. Questa operazione isola efficacemente l'oggetto di interesse all'interno dell'immagine. Infine, creare un oggetto nuvola di punti Open3D e visualizzare 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 ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-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])
FAQ
Che cos'è il Robot Operating System (ROS)?
Il Robot Operating System (ROS) è un framework open-source comunemente usato nella robotica per aiutare gli sviluppatori a creare applicazioni robotiche robuste. Fornisce una raccolta di librerie e strumenti per la costruzione e l'interfacciamento con i sistemi robotici, consentendo un più facile sviluppo di applicazioni complesse. ROS supporta la comunicazione tra i nodi utilizzando messaggi su argomenti o servizi.
Come si integra Ultralytics YOLO con ROS per il rilevamento di oggetti in tempo reale?
L'integrazione di Ultralytics YOLO con ROS comporta la creazione di un ambiente ROS e l'utilizzo di YOLO per l'elaborazione dei dati dei sensori. Si inizia con l'installazione delle dipendenze necessarie, come ad esempio ros_numpy
e Ultralytics YOLO :
Quindi, creare un nodo ROS e sottoscrivere un argomento immagine per elaborare i dati in arrivo. Ecco un esempio minimo:
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolov8m.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()
Quali sono gli argomenti ROS e come vengono utilizzati in Ultralytics YOLO ?
Gli argomenti di ROS facilitano la comunicazione tra i nodi di una rete ROS utilizzando un modello publish-subscribe. Un argomento è un canale denominato che i nodi usano per inviare e ricevere messaggi in modo asincrono. Nel contesto di Ultralytics YOLO , è possibile far sottoscrivere a un nodo un topic di immagini, elaborare le immagini utilizzando YOLO per attività come il rilevamento o la segmentazione e pubblicare i risultati in nuovi topic.
Ad esempio, sottoscrivere un argomento relativo a una telecamera ed elaborare l'immagine in arrivo per il rilevamento:
Perché utilizzare le immagini di profondità con Ultralytics YOLO in ROS?
Immagini di profondità in ROS, rappresentate da sensor_msgs/Image
forniscono la distanza degli oggetti dalla telecamera, fondamentale per compiti come l'evitamento degli ostacoli, la mappatura 3D e la localizzazione. Da utilizzando le informazioni sulla profondità insieme alle immagini RGB, i robot possono comprendere meglio l'ambiente 3D in cui si trovano.
Con YOLO è possibile estrarre maschere di segmentazione dalle immagini RGB e applicarle alle immagini di profondità per ottenere informazioni precise sugli oggetti 3D, migliorando la capacità del robot di navigare e interagire con l'ambiente circostante.
Come si possono visualizzare le nuvole di punti 3D con YOLO in ROS?
Per visualizzare le nuvole di punti 3D in ROS con YOLO:
- Convertire
sensor_msgs/PointCloud2
in array numpy. - Utilizzare YOLO per segmentare le immagini RGB.
- Applicare la maschera di segmentazione alla nuvola di punti.
Ecco un esempio che utilizza Open3D per la visualizzazione:
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("yolov8m-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.