YOLO Vision 2026:

Guida rapida a ROS (Robot Operating System)#

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.

Passa alla configurazione di YOLO con ROS, quindi lavora con immagini RGB, immagini di profondità o nuvole di punti.

ROS Introduction (captioned) from Open Robotics on Vimeo.

Che cos'è ROS?#

Il Robot Operating System (ROS) è un framework open-source ampiamente utilizzato nella ricerca e nell'industria della robotica. ROS fornisce una raccolta di librerie e strumenti per aiutare gli sviluppatori a creare applicazioni robotiche. ROS è progettato per funzionare con varie piattaforme robotiche, rendendolo uno strumento flessibile e potente per i robotici.

Caratteristiche 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. Ciascun nodo in genere svolge una funzione specifica e i nodi comunicano tra loro utilizzando messaggi tramite argomenti (topics) o servizi.

  2. Middleware di comunicazione: ROS offre un'infrastruttura di comunicazione robusta che supporta la comunicazione inter-processo e il calcolo distribuito. Questo si ottiene tramite un modello publish-subscribe per i flussi di dati (argomenti) e un modello request-reply per le chiamate di servizio.

  3. Astrazione hardware: ROS fornisce un livello di astrazione sull'hardware, consentendo agli sviluppatori di scrivere codice agnostico rispetto al dispositivo. Ciò permette di utilizzare lo stesso codice con diverse configurazioni hardware, facilitando l'integrazione e la sperimentazione.

  4. Strumenti e utility: ROS viene fornito con una ricca serie di strumenti e utility 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 fornisce un potente ambiente di simulazione per testare algoritmi e design 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 ancora. La comunità contribuisce attivamente allo sviluppo e alla manutenzione di questi pacchetti.

Evoluzione delle versioni di ROS

Sin dal suo sviluppo nel 2007, ROS si è evoluto attraverso versioni multiple, suddivise in ROS 1 e ROS 2. Gli esempi esistenti di seguito utilizzano ROS1 Noetic; gli adattatori compatti in Utilizzo di ROS2 mostrano le corrispondenti interfacce rclpy per le attuali release di ROS2.

ROS 1 vs. ROS 2#

Mentre ROS 1 ha fornito una solida base per lo sviluppo robotico, ROS 2 affronta le sue carenze 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 vari ambienti.
  • Scalabilità: Miglior supporto per sistemi multi-robot e implementazioni su larga scala.
  • Supporto multipiattaforma: Compatibilità estesa con vari sistemi operativi oltre a Linux, inclusi Windows e macOS.
  • Comunicazione flessibile: Utilizzo di DDS per una comunicazione inter-processo più flessibile ed efficiente.

Messaggi e argomenti ROS#

In ROS, la comunicazione tra i nodi è facilitata tramite messaggi e argomenti (topics). Un messaggio è una struttura di dati che definisce le informazioni scambiate tra i nodi, mentre un topic è un canale con un nome attraverso il quale i messaggi vengono inviati e ricevuti. I nodi possono pubblicare messaggi su un topic o iscriversi a messaggi da un topic, consentendo loro di comunicare tra loro. Questo modello di pubblicazione-sottoscrizione consente una comunicazione asincrona e il disaccoppiamento tra i nodi. Ciascun sensore o attuatore in un sistema robotico pubblica tipicamente dati su un topic, che possono poi essere consumati 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 telecamere.

Configurazione di Ultralytics YOLO con ROS#

Gli esempi ROS1 sono stati testati utilizzando questo ambiente ROS, un fork del repository ROSbot ROS. La stessa elaborazione YOLO e NumPy si applica in ROS2; solo il ciclo di vita del nodo e la conversione dei messaggi differiscono.

Husarion ROSbot 2 PRO autonomous robot platform

Installazione delle dipendenze#

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

  • Pacchetto ROS NumPy: Questo è necessario per una conversione rapida tra messaggi immagine ROS e array NumPy.

    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 seguente nodo è il completo equivalente ROS2 del flusso di rilevamento RGB sottostante; istanzia i modelli una volta e riutilizzali tra i 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à qui 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 la nuvola organizzata una volta, quindi riutilizza la segmentazione NumPy e la mappatura 3D sottostanti:

from sensor_msgs_py import point_cloud2

points = point_cloud2.read_points_numpy(message, field_names=("x", "y", "z", "rgb"))
points = points.reshape(message.height, message.width, 4)

Usa Ultralytics con ROS sensor_msgs/Image#

Il tipo di messaggio sensor_msgs/Image è comunemente usato in ROS per rappresentare i dati delle immagini. Contiene campi per codifica, altezza, larghezza e dati dei pixel, risultando adatto per la trasmissione di immagini catturate da telecamere o altri sensori. I messaggi di immagine sono ampiamente utilizzati nelle applicazioni robotiche per attività come la percezione visiva, il rilevamento di oggetti e la navigazione.

Detection and Segmentation in ROS Gazebo

Utilizzo passo dopo passo delle immagini#

Il seguente snippet di codice dimostra come utilizzare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci iscriviamo a un topic della telecamera, elaboriamo l'immagine in arrivo utilizzando YOLO e pubblichiamo gli oggetti rilevati su nuovi topic per il rilevamento e la segmentazione.

Innanzitutto, 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 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 utilizzati per pubblicare le immagini annotate, rendendole accessibili per un'ulteriore elaborazione. La comunicazione tra i nodi è facilitata utilizzando 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 si mette in ascolto dei messaggi sul topic /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 usando ros_numpy, elabora le immagini con i modelli YOLO precedentemente istanziati, annota le immagini 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 (Robot Operating System) può essere impegnativo a causa della natura distribuita del sistema. Diversi strumenti possono assistere in questo processo:

  1. rostopic echo <TOPIC-NAME> : Questo comando ti consente di visualizzare i messaggi pubblicati su un topic specifico, aiutandoti a ispezionare il flusso di dati.
  2. rostopic list: Usa questo comando per elencare tutti i topic disponibili nel sistema ROS, offrendoti una panoramica dei flussi di dati attivi.
  3. rqt_graph: Questo strumento di visualizzazione mostra il grafico di comunicazione tra i nodi, fornendo approfondimenti su come i nodi sono interconnessi e su come interagiscono.
  4. Per visualizzazioni più complesse, come rappresentazioni 3D, puoi usare RViz. RViz (ROS Visualization) è un potente strumento di visualizzazione 3D per ROS. Ti consente di visualizzare lo stato del tuo robot e del suo ambiente in tempo reale. 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 tuo 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; vengono invece richieste solo le classi presenti nella vista del robot. Il seguente esempio dimostra come utilizzare i messaggi std_msgs/String per ripubblicare le classi rilevate sul topic /ultralytics/detection/classes. Questi messaggi sono più leggeri e forniscono informazioni essenziali, risultando preziosi per varie applicazioni.

Caso d'uso di esempio#

Considera un robot da magazzino dotato di una telecamera e di un modello di rilevamento di oggetti. Invece di inviare grandi immagini annotate sulla rete, 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. Queste informazioni possono quindi essere utilizzate da un sistema di monitoraggio centrale per tracciare l'inventario in tempo reale, ottimizzare la pianificazione del percorso del robot per evitare ostacoli o attivare azioni specifiche come il sollevamento di una scatola rilevata. Questo approccio riduce la larghezza di banda richiesta per la comunicazione e si concentra sulla trasmissione di dati critici.

Utilizzo passo dopo passo delle stringhe#

Questo esempio dimostra come utilizzare il pacchetto Ultralytics YOLO con ROS. In questo esempio, ci iscriviamo a un topic della telecamera, elaboriamo l'immagine in arrivo utilizzando YOLO e pubblichiamo gli oggetti rilevati sul nuovo topic /ultralytics/detection/classes utilizzando i messaggi std_msgs/String. Il pacchetto ros_numpy viene utilizzato per convertire il messaggio immagine ROS in un array NumPy per l'elaborazione 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()

Utilizza Ultralytics con 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 cruciali 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 telecamera a un oggetto. A differenza delle immagini RGB che catturano il colore, le immagini di profondità catturano informazioni spaziali, consentendo ai robot di percepire la struttura 3D del loro ambiente.

Ottenere immagini di profondità

Le immagini di profondità possono essere ottenute utilizzando vari sensori:

  1. Telecamere stereo: Utilizzano due telecamere per calcolare la profondità in base alla disparità dell'immagine.
  2. Telecamere Time-of-Flight (ToF): Misurano il tempo impiegato dalla luce per tornare da un oggetto.
  3. Sensori a luce strutturata: Proiettano un pattern e misurano la sua deformazione sulle superfici.

Utilizzare YOLO con 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 per le immagini di profondità utilizza spesso un formato come "16UC1", che indica un intero senza segno a 16 bit per pixel, in cui ciascun valore rappresenta la distanza dall'oggetto. Le immagini di profondità sono comunemente usate in combinazione con le immagini RGB per fornire una visione più completa dell'ambiente.

Utilizzando YOLO, è possibile estrarre e combinare informazioni sia dalle immagini RGB che da quelle di profondità. Ad esempio, YOLO può rilevare oggetti all'interno di un'immagine RGB e questo rilevamento può essere utilizzato per individuare le regioni corrispondenti nell'immagine di profondità. Ciò consente l'estrazione di informazioni precise sulla profondità per gli oggetti rilevati, migliorando la capacità del robot di comprendere il suo ambiente in tre dimensioni.

Telecamere RGB-D

Quando si lavora con le immagini di profondità, è essenziale assicurarsi che le immagini RGB e di profondità siano correttamente allineate. Le telecamere RGB-D, come la serie Intel RealSense, forniscono immagini RGB e di profondità sincronizzate, semplificando la combinazione di informazioni da entrambe le fonti. Se si utilizzano telecamere RGB e di profondità separate, è fondamentale calibrarle per garantire un allineamento accurato.

Utilizzo passo dopo passo della profondità#

In questo esempio, utilizziamo YOLO per segmentare un'immagine e applichiamo la maschera estratta per segmentare l'oggetto nell'immagine di profondità. Questo ci consente di 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 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("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

Successivamente, definisci una funzione di callback che elabora il messaggio dell'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. Estrae quindi la maschera di segmentazione per ciascun 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 (clip distance), oltre la quale i valori sono rappresentati come inf (np.inf). Prima dell'elaborazione, è importante filtrare questi valori nulli e assegnare loro un valore pari a 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)

    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()

Usa Ultralytics con ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

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 è integrante per le applicazioni robotiche, consentendo 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, catturati tramite tecnologie di scansione 3D. Ciascun punto nella nuvola ha coordinate X, Y e Z, che corrispondono alla sua posizione nello spazio, e può anche includere informazioni aggiuntive come colore e intensità.

Sistema di riferimento

Quando si lavora 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 catturata nel sistema di riferimento del sensore. Puoi determinare questo sistema di riferimento mettendoti in ascolto del topic /tf_static. Tuttavia, a seconda dei requisiti specifici della tua applicazione, potresti aver bisogno di convertire la nuvola di punti in un altro sistema di riferimento. Questa trasformazione può essere ottenuta utilizzando il pacchetto tf2_ros, che fornisce strumenti per la gestione dei frame di coordinate e la trasformazione dei dati tra di essi.

Ottenere nuvole di punti

Le nuvole di punti possono essere ottenute utilizzando vari sensori:

  1. LIDAR (Light Detection and Ranging): Utilizza impulsi laser per misurare le distanze dagli oggetti e creare mappe 3D ad alta precisione.
  2. Telecamere di profondità: Catturano le informazioni di profondità per ogni pixel, consentendo la ricostruzione 3D della scena.
  3. Telecamere stereo: Utilizzano due o più telecamere per ottenere informazioni di profondità tramite triangolazione.
  4. Scanner a luce strutturata: Proiettano un motivo noto su una superficie e ne misurano la deformazione per calcolare la profondità.

Utilizzare YOLO con nuvole di punti#

Per integrare YOLO con i messaggi di tipo sensor_msgs/PointCloud2, possiamo impiegare un metodo simile a quello utilizzato 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 la gestione delle nuvole di punti, consigliamo di utilizzare Open3D (pip install open3d), una libreria Python intuitiva. Open3D fornisce strumenti robusti per la gestione di strutture dati di nuvole di punti, la loro visualizzazione e l'esecuzione fluida di operazioni complesse. Questa libreria può semplificare notevolmente il processo e migliorare la nostra capacità di manipolare e analizzare nuvole di punti in combinazione con la segmentazione basata su YOLO.

Utilizzo passo dopo passo delle nuvole di punti#

Importa le librerie necessarie e crea 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 trasforma 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. Ciascun punto include tre coordinate spaziali (xyz) e il colore corrispondente nel formato RGB. Questi possono essere considerati come due canali di informazioni separati.

La funzione restituisce le coordinate xyz e i valori RGB nel formato della risoluzione della telecamera originale (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 pari a 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, 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 (utilizzando la funzione pointcloud2_to_array). Elabora l'immagine RGB utilizzando il modello YOLO per estrarre gli oggetti segmentati. Per ciascun oggetto rilevato, estrai la maschera di segmentazione e applicala sia all'immagine RGB che 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 ne 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, 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])

Point Cloud Segmentation with Ultralytics

Conclusione#

Con Ultralytics YOLO integrato in ROS, il tuo robot può eseguire rilevamento di oggetti e segmentazione su immagini RGB, immagini di profondità e nuvole di punti, trasformando flussi di sensori grezzi in percezione azionabile. Da qui, esplora la modalità Predict per ulteriori opzioni di inferenza, o segui i passaggi di un progetto di computer vision per portare la tua applicazione robotica dal prototipo alla produzione.

FAQ#

Che cos'è il Robot Operating System (ROS)?#

Il Robot Operating System (ROS) è un framework open-source comunemente utilizzato in robotica per aiutare gli sviluppatori a creare applicazioni robotiche robuste. Fornisce una raccolta di librerie e strumenti per la costruzione e l'interfacciamento con sistemi robotici, consentendo uno sviluppo più semplice di applicazioni complesse. ROS supporta la comunicazione tra i nodi utilizzando messaggi tramite argomenti (topics) o servizi.

Come posso integrare Ultralytics YOLO con ROS per il rilevamento di oggetti in tempo reale?#

L'integrazione di Ultralytics YOLO con ROS comporta la configurazione di un ambiente ROS e l'utilizzo di YOLO per l'elaborazione dei dati dei sensori. Inizia installando le dipendenze richieste come ros_numpy e Ultralytics YOLO:

pip install ros_numpy ultralytics

Successivamente, crea un nodo ROS e iscriviti a un topic di immagini per elaborare i dati in arrivo per il rilevamento di oggetti. Ecco un esempio minimale:

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()

Cosa sono gli argomenti ROS e come vengono utilizzati in Ultralytics YOLO?#

I topic ROS facilitano la comunicazione tra i nodi in una rete ROS utilizzando un modello di pubblicazione-sottoscrizione. Un topic è un canale con un nome che i nodi utilizzano per inviare e ricevere messaggi in modo asincrono. Nel contesto di Ultralytics YOLO, puoi fare in modo che un nodo si iscriva a un topic di immagini, elabori le immagini utilizzando YOLO per attività come il rilevamento o la segmentazione e pubblichi i risultati su nuovi topic.

Ad esempio, iscriviti a un argomento della telecamera ed elabora l'immagine in arrivo per il rilevamento:

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

Perché utilizzare immagini di profondità con Ultralytics YOLO in ROS?#

Le immagini di profondità in ROS, rappresentate da sensor_msgs/Image, forniscono la distanza degli oggetti dalla telecamera, fondamentale per attività come l'evitamento degli ostacoli, la mappatura 3D e la localizzazione. Utilizzando le informazioni di profondità insieme alle immagini RGB, i robot possono comprendere meglio il loro ambiente 3D.

Con YOLO, puoi estrarre maschere di segmentazione dalle immagini RGB e applicare queste maschere alle immagini di profondità per ottenere informazioni precise sugli oggetti 3D, migliorando la capacità del robot di navigare e interagire con ciò che lo circonda.

Come posso visualizzare nuvole di punti 3D con YOLO in ROS?#

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 che utilizza 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 in applicazioni robotiche.

Commenti