Ultralytics YOLO27:
Get Started

Guida introduttiva a ROS (Sistema operativo per robot)#

Questa guida ti mostra come integrare Ultralytics YOLO con ROS1 (rospy) o ROS2 (rclpy) per eseguire rilevamento di oggetti e segmentazione in tempo reale su immagini RGB, immagini di profondità e nuvole di punti.

Vai direttamente a configurare 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 usato 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, il che lo rende 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.

Funzionalità principali di ROS#

  1. 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 in genere una funzione specifica e i nodi comunicano tra loro tramite messaggi su topic o servizi.

  2. Middleware di comunicazione: ROS offre una solida infrastruttura di comunicazione 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 richiesta-risposta per le chiamate ai servizi.

  3. Astrazione hardware: ROS fornisce un livello di astrazione sull’hardware, consentendo agli sviluppatori di scrivere codice indipendente dal dispositivo. Lo stesso codice può così essere usato con configurazioni hardware diverse, semplificando l’integrazione e la sperimentazione.

  4. Strumenti e utilità: ROS include un’ampia raccolta di strumenti e utilità per la visualizzazione, il debugging e la simulazione. Ad esempio, RViz serve 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.

  5. 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 diverse versioni, suddivise tra ROS 1 e ROS 2. Gli esempi seguenti usano ROS1 Noetic; gli adattatori compatti in Uso di ROS2 mostrano le interfacce rclpy corrispondenti per le versioni attuali di ROS2.

ROS 1 e ROS 2#

Sebbene ROS 1 abbia fornito solide basi per lo sviluppo robotico, ROS 2 ne risolve 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à estesa con diversi 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 topic ROS#

In ROS, la comunicazione tra i nodi avviene tramite messaggi e topic. Un messaggio è una struttura di dati che definisce le informazioni scambiate tra i nodi, mentre un topic è un canale con nome attraverso il quale i messaggi vengono inviati e ricevuti. I nodi possono pubblicare messaggi su un topic o iscriversi per ricevere messaggi da un topic, così da comunicare tra loro. Questo modello di pubblicazione-sottoscrizione consente la comunicazione asincrona e il disaccoppiamento tra i nodi. In genere, ogni sensore o attuatore di un sistema robotico pubblica dati su un topic, che possono poi essere utilizzati da altri nodi per l'elaborazione o il controllo. In questa guida ci concentreremo sui messaggi Image, Depth e PointCloud e sui topic delle telecamere.

Configurazione di Ultralytics YOLO con ROS#

Gli esempi ROS1 sono stati testati usando questo ambiente ROS, un fork del repository ROS 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.

Husarion ROSbot 2 PRO autonomous robot platform

Installazione delle dipendenze#

Oltre all'ambiente ROS, dovrai installare le seguenti dipendenze:

  • Pacchetto ROS NumPy: è necessario per convertire rapidamente i messaggi ROS Image in array NumPy.

    pip install ros_numpy
  • Pacchetto Ultralytics:

    pip install ultralytics

Uso di ROS2#

ROS2 sostituisce rospy con rclpy e la conversione delle immagini tramite 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")
    # Applica la maschera NumPy e il calcolo della distanza dell'esempio sulla profondità riportato sotto.

Per le nuvole di punti, ROS2 fornisce sensor_msgs_py.point_cloud2; converti una sola volta la nuvola organizzata, poi 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)

Usa Ultralytics con sensor_msgs/Image di ROS#

Il tipo di messaggio sensor_msgs/Image è 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 acquisite da telecamere 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.

Detection and Segmentation in ROS Gazebo

Uso di Image 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 telecamera, elaboriamo l'immagine in ingresso con 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, inseriamo 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("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 usati per pubblicare le immagini annotate, rendendole disponibili per ulteriori elaborazioni. La comunicazione tra i nodi avviene tramite i 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 richiami 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 usando ros_numpy, elabora le immagini con i modelli YOLO istanziati in precedenza, le annota e 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

Il debug dei nodi ROS (Sistema operativo per robot) può essere complesso a causa della natura distribuita del sistema. Diversi strumenti possono aiutarti in questo processo:

  1. rostopic echo <TOPIC-NAME> : questo comando ti consente di visualizzare i messaggi pubblicati su un topic specifico, aiutandoti a esaminare il flusso di dati.
  2. rostopic list: usa questo comando per elencare tutti i topic disponibili nel sistema ROS e avere una panoramica dei flussi di dati attivi.
  3. rqt_graph: questo strumento di visualizzazione mostra il grafo di comunicazione tra i nodi, offrendo informazioni sulle interconnessioni tra i nodi e sulle loro interazioni.
  4. 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 in tempo reale lo stato del robot e del suo ambiente. Con RViz puoi visualizzare i dati dei sensori (ad es. 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.

Pubblica le classi rilevate con std_msgs/String#

I messaggi ROS standard includono anche i messaggi std_msgs/String. In molte applicazioni non è necessario ripubblicare l'intera immagine annotata; servono invece 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#

Immagina un robot da magazzino dotato di una telecamera e di un modello di rilevamento degli oggetti. Invece di inviare in rete immagini annotate di grandi dimensioni, il robot può pubblicare un elenco di classi rilevate come messaggi std_msgs/String. Ad esempio, quando il robot rileva oggetti come "box", "pallet" e "forklift", pubblica queste classi sul topic /ultralytics/detection/classes. Un sistema di monitoraggio centrale può quindi usare queste informazioni per tenere traccia dell'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 essenziali.

Uso di String passo dopo passo#

Questo esempio mostra come usare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci iscriviamo a un topic della telecamera, elaboriamo l'immagine in ingresso con YOLO e pubblichiamo gli oggetti rilevati sul nuovo topic /ultralytics/detection/classes usando i messaggi std_msgs/String. Il pacchetto ros_numpy viene usato 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("yolo26m.pt")
rospy.init_node("ultralytics")
time.sleep(1)
classes_pub = rospy.Publisher("/ultralytics/detection/classes", String, queue_size=5)

def callback(data):
    """Callback function to process image and publish detected classes."""
    array = ros_numpy.numpify(data)
    if classes_pub.get_num_connections():
        det_result = detection_model(array)
        classes = det_result[0].boxes.cls.cpu().numpy().astype(int)
        names = [det_result[0].names[i] for i in classes]
        classes_pub.publish(String(data=str(names)))

rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
    rospy.spin()

Usa Ultralytics con le immagini di profondità 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 acquisiscono il colore, le immagini di profondità acquisiscono informazioni spaziali, consentendo ai robot di percepire la struttura 3D dell'ambiente.

Acquisizione delle immagini di profondità

Le immagini di profondità possono essere acquisite usando diversi sensori:

  1. Telecamere stereo: usa due telecamere per calcolare la profondità in base alla disparità tra le immagini.
  2. Telecamere a tempo di volo (ToF): misurano il tempo impiegato dalla luce per tornare da un oggetto.
  3. Sensori a luce strutturata: proiettano un pattern e ne misurano la deformazione sulle superfici.

Uso 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. Per le immagini di profondità, il campo di codifica 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 usate insieme alle immagini RGB per fornire una visione più completa dell'ambiente.

Con YOLO è possibile estrarre e combinare informazioni sia dalle immagini RGB sia da quelle di profondità. Ad esempio, YOLO può rilevare oggetti in un'immagine RGB, e questo rilevamento può essere usato per individuare le regioni corrispondenti nell'immagine di profondità. In questo modo è possibile estrarre informazioni precise sulla profondità degli oggetti rilevati, migliorando la capacità del robot di comprendere l'ambiente in tre dimensioni.

Telecamere RGB-D

Quando lavori con immagini di profondità, è fondamentale assicurarti che le immagini RGB e quelle di profondità siano correttamente allineate. Le telecamere 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 telecamere RGB e di profondità separate, è essenziale calibrarle per garantire un allineamento accurato.

Uso delle immagini di 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à. In questo modo possiamo determinare la distanza di ogni 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. 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)

Poi, definisci una funzione di callback che elabora 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 telecamera usando l'immagine di profondità. La maggior parte dei sensori ha una distanza massima, detta distanza di clipping, 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 loro 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, retina_masks=True)  # masks at the original image resolution

    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, retina_masks=True)  # masks at the original image resolution

    all_objects = []
    for index, cls in enumerate(result[0].boxes.cls):
        class_index = int(cls.cpu().numpy())
        name = result[0].names[class_index]
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        obj = depth[mask == 1]
        obj = obj[~np.isnan(obj)]
        avg_distance = np.mean(obj) if len(obj) else np.inf
        all_objects.append(f"{name}: {avg_distance:.2f}m")

    classes_pub.publish(String(data=str(all_objects)))

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

while True:
    rospy.spin()

Usa Ultralytics con sensor_msgs/PointCloud2 di ROS#

Detection and Segmentation in ROS Gazebo

Il tipo di messaggio sensor_msgs/PointCloud2 è una struttura di dati usata in ROS per rappresentare i dati delle nuvole di punti 3D. Questo tipo di messaggio è fondamentale nelle applicazioni robotiche e consente attività come la mappatura 3D, il riconoscimento degli oggetti e la localizzazione.

Una nuvola di punti è un insieme di punti dati definiti in 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 coordinate X, Y e Z, che corrispondono alla sua posizione nello spazio, e può includere anche informazioni aggiuntive come il colore e l'intensità.

Sistema di riferimento

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 della tua applicazione, potrebbe essere necessario convertire la nuvola di punti in un altro sistema di riferimento. Puoi eseguire questa trasformazione con il pacchetto tf2_ros, che fornisce strumenti per gestire i sistemi di coordinate e trasformare i dati tra di essi.

Acquisizione delle nuvole di punti

Le nuvole di punti possono essere acquisite usando diversi sensori:

  1. LIDAR (rilevamento e misurazione della distanza tramite luce): usa impulsi laser per misurare le distanze dagli oggetti e creare mappe 3D ad alta precisione.
  2. Telecamere di profondità: acquisiscono informazioni sulla profondità per ogni pixel, consentendo la ricostruzione 3D della scena.
  3. Telecamere stereo: usano due o più telecamere per ottenere informazioni sulla profondità tramite triangolazione.
  4. Scanner a luce strutturata: proiettano un pattern noto su una superficie e ne misurano la deformazione per calcolare la profondità.

Uso 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 contenute nella nuvola di punti, possiamo estrarre un'immagine 2D, segmentarla con YOLO e poi applicare la maschera risultante ai punti tridimensionali per isolare l'oggetto 3D di interesse.

Per gestire le nuvole di punti, consigliamo Open3D (pip install open3d), una libreria Python facile da usare. Open3D fornisce strumenti affidabili per gestire le strutture di 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.

Uso 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 distinti.

La funzione restituisce le coordinate xyz e i valori RGB nel formato della risoluzione originale della telecamera (width x height). La maggior parte dei sensori ha una distanza massima, detta distanza di clipping, 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, rgb

Poi, iscriviti 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 con 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.

Elaborare la maschera è semplice perché è composta da valori binari: 1 indica la presenza dell'oggetto e 0 la sua assenza. Per applicare la maschera, basta moltiplicare 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, retina_masks=True)  # maschere alla risoluzione originale dell'immagine

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, retina_masks=True)  # maschere alla risoluzione originale dell'immagine

if not len(result[0].boxes.cls):
    print("No objects detected")
    sys.exit()

classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
    mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
    mask_expanded = np.stack([mask, mask, mask], axis=2)

    obj_rgb = rgb * mask_expanded
    obj_xyz = xyz * mask_expanded

    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
    pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
    o3d.visualization.draw_geometries([pcd])

Point Cloud Segmentation with Ultralytics

Conclusione#

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 dati percettivi utilizzabili. Da qui, esplora la modalità Predict per altre opzioni di inferenza oppure segui i passaggi di un progetto di visione artificiale per portare la tua applicazione robotica dal prototipo alla produzione.

Domande frequenti#

  • Il Sistema operativo per robot (ROS) è un framework open source comunemente usato in robotica per aiutare gli sviluppatori a creare applicazioni robotiche affidabili. Offre una raccolta di librerie e strumenti per creare interfacce con i sistemi robotici, semplificando lo sviluppo di applicazioni complesse. ROS supporta la comunicazione tra nodi tramite messaggi inviati su topic o servizi.

  • Integrare Ultralytics YOLO con ROS significa configurare un ambiente ROS e usare YOLO per elaborare i dati dei sensori. Per prima cosa, installa le dipendenze necessarie, come ros_numpy, e Ultralytics YOLO:

    pip install ros_numpy ultralytics

    Poi, crea un nodo ROS e iscriviti a un topic di immagini per elaborare i dati in ingresso ed eseguire il rilevamento degli oggetti. Ecco un esempio essenziale:

    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 consentono la comunicazione tra i nodi di una rete ROS tramite un modello di pubblicazione-sottoscrizione. Un topic è un canale con nome che i nodi usano per inviare e ricevere messaggi in modo asincrono. Nel contesto di Ultralytics YOLO, puoi configurare un nodo per iscriversi a un topic di immagini, elaborare le immagini con YOLO per attività come il rilevamento o la segmentazione e pubblicare i risultati su nuovi topic.

    Ad esempio, iscriviti a un topic della telecamera 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 telecamera, un dato 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 l'ambiente 3D.

    Con YOLO puoi 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.

  • Per visualizzare nuvole di punti 3D in ROS con YOLO:

    1. Converti i messaggi sensor_msgs/PointCloud2 in array NumPy.
    2. Usa YOLO per segmentare le immagini RGB.
    3. Applica la maschera di segmentazione alla nuvola di punti.

    Ecco un esempio di visualizzazione con Open3D:

    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, retina_masks=True)  # maschere alla risoluzione originale dell'immagine
    
    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 offre una visualizzazione 3D degli oggetti segmentati, utile per attività come la navigazione e la manipolazione nelle applicazioni robotiche.

Commenti