Ultralytics YOLO27 :

Guide de démarrage rapide de ROS (système d’exploitation robotique)#

Ce guide t montre comment intégrer Ultralytics YOLO à ROS1 (rospy) ou ROS2 (rclpy) pour effectuer en temps réel la détection d’objets et la segmentation sur des images RGB, des images de profondeur et des nuages de points.

Va directement à la configuration de YOLO avec ROS, puis travaille avec des images RGB, des images de profondeur ou des nuages de points.

Qu’est-ce que ROS ?#

Le système d’exploitation robotique (ROS) est un framework open source largement utilisé dans la recherche et l’industrie de la robotique. ROS fournit un ensemble de bibliothèques et d’outils pour aider les développeurs à créer des applications robotiques. ROS est conçu pour fonctionner avec différentes plateformes robotiques, ce qui en fait un outil flexible et puissant pour les roboticiens. Pour une brève introduction, regarde la vidéo de trois minutes Introduction à ROS d’Open Robotics.

Fonctionnalités principales de ROS#

  1. Architecture modulaire : ROS possède une architecture modulaire qui permet aux développeurs de créer des systèmes complexes en combinant de petits composants réutilisables appelés nœuds. Chaque nœud exécute généralement une fonction précise, et les nœuds communiquent entre eux au moyen de messages sur des topics ou des services.

  2. Intergiciel de communication : ROS fournit une infrastructure de communication robuste prenant en charge la communication interprocessus et l’informatique distribuée. Cela repose sur un modèle publication-abonnement pour les flux de données (topics) et sur un modèle requête-réponse pour les appels de services.

  3. Abstraction matérielle : ROS fournit une couche d’abstraction au-dessus du matériel, permettant aux développeurs d’écrire du code indépendant des appareils. Le même code peut ainsi être utilisé avec différentes configurations matérielles, ce qui facilite l’intégration et l’expérimentation.

  4. Outils et utilitaires : ROS est fourni avec un riche ensemble d’outils et d’utilitaires pour la visualisation, le débogage et la simulation. Par exemple, RViz sert à visualiser les données des capteurs et les informations sur l’état du robot, tandis que Gazebo fournit un puissant environnement de simulation pour tester les algorithmes et les conceptions de robots.

  5. Écosystème étendu : l’écosystème ROS est vaste et en constante évolution, avec de nombreux packages disponibles pour différentes applications robotiques, notamment la navigation, la manipulation, la perception et bien plus encore. La communauté contribue activement au développement et à la maintenance de ces packages.

Évolution des versions de ROS

Depuis son développement en 2007, ROS a évolué au fil de plusieurs versions, réparties entre ROS 1 et ROS 2. Les exemples existants ci-dessous utilisent ROS1 Noetic ; les adaptateurs compacts de Utilisation de ROS2 présentent les interfaces rclpy correspondantes pour les versions actuelles de ROS2.

ROS 1 contre ROS 2#

ROS 1 fournissait une base solide pour le développement robotique, mais ROS 2 remédie à ses limites en offrant :

  • Performances en temps réel : prise en charge améliorée des systèmes temps réel et comportement déterministe.
  • Sécurité : fonctionnalités de sécurité renforcées pour un fonctionnement sûr et fiable dans divers environnements.
  • Évolutivité : meilleure prise en charge des systèmes multi-robots et des déploiements à grande échelle.
  • Prise en charge multiplateforme : compatibilité étendue avec divers systèmes d’exploitation au-delà de Linux, notamment Windows et macOS.
  • Communication flexible : utilisation de DDS pour une communication interprocessus plus flexible et plus efficace.

Messages et topics ROS#

Dans ROS, la communication entre les nœuds s’effectue au moyen de messages et de topics. Un message est une structure de données qui définit les informations échangées entre les nœuds, tandis qu’un topic est un canal nommé par lequel les messages sont envoyés et reçus. Les nœuds peuvent publier des messages sur un topic ou s’abonner aux messages d’un topic, ce qui leur permet de communiquer entre eux. Ce modèle publication-abonnement permet une communication asynchrone et le découplage des nœuds. Chaque capteur ou actionneur d’un système robotique publie généralement ses données sur un topic, que d’autres nœuds peuvent ensuite exploiter pour le traitement ou le contrôle. Dans ce guide, nous nous concentrerons sur les messages Image, Depth et PointCloud, ainsi que sur les topics de caméra.

Configurer Ultralytics YOLO avec ROS#

Les exemples ROS1 ont été testés avec cet environnement ROS, un fork du dépôt ROS de ROSbot. Le même traitement avec YOLO et NumPy s’applique dans ROS2 ; seuls le cycle de vie du nœud et la conversion des messages diffèrent.

Husarion ROSbot 2 PRO autonomous robot platform

Installation des dépendances#

En plus de l’environnement ROS, tu dois installer les dépendances suivantes :

  • Package ROS NumPy : nécessaire pour convertir rapidement les messages Image de ROS en tableaux NumPy, et inversement.

    pip install ros_numpy
  • Package Ultralytics :

    pip install ultralytics

Utiliser ROS2#

ROS2 remplace rospy par rclpy et la conversion des images ros_numpy par cv_bridge. Le nœud suivant est l’équivalent ROS2 complet du flux de détection RGB présenté plus bas ; instancie les modèles une seule fois et réutilise-les dans les callbacks.

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

Pour les images de profondeur, réutilise le code de traitement de la profondeur ci-dessous et remplace uniquement l’acquisition et la conversion des messages :

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.

Pour les nuages de points, ROS2 fournit sensor_msgs_py.point_cloud2 ; convertis le nuage organisé une seule fois, puis réutilise la segmentation NumPy et la mise en correspondance 3D ci-dessous :

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)

Utiliser Ultralytics avec ROS sensor_msgs/Image#

Le type de message sensor_msgs/Image est couramment utilisé dans ROS pour représenter des données d’image. Il contient des champs pour l’encodage, la hauteur, la largeur et les données des pixels, ce qui le rend adapté à la transmission d’images capturées par des caméras ou d’autres capteurs. Les messages Image sont largement utilisés dans les applications robotiques pour des tâches comme la perception visuelle, la détection d’objets et la navigation.

Detection and Segmentation in ROS Gazebo

Utilisation d’Image étape par étape#

L’extrait de code suivant montre comment utiliser le package Ultralytics YOLO avec ROS. Dans cet exemple, nous nous abonnons à un topic de caméra, traitons l’image reçue avec YOLO et publions les objets détectés sur de nouveaux topics pour la détection et la segmentation.

Commence par importer les bibliothèques nécessaires et instancier deux modèles : un pour la segmentation et un pour la détection. Initialise un nœud ROS (avec le nom ultralytics) pour permettre la communication avec le master ROS. Pour garantir une connexion stable, ajoute une courte pause afin de laisser au nœud le temps d’établir la connexion avant de poursuivre.

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)

Initialise deux topics ROS : un pour la détection et un pour la segmentation. Ces topics serviront à publier les images annotées et à les rendre accessibles pour un traitement ultérieur. La communication entre les nœuds s’effectue au moyen de messages 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)

Enfin, crée un abonné qui écoute les messages du topic /camera/color/image_raw et appelle une fonction de callback pour chaque nouveau message. Cette fonction reçoit des messages de type sensor_msgs/Image, les convertit en tableau NumPy à l’aide de ros_numpy, traite les images avec les modèles YOLO précédemment instanciés, annote les images, puis les republie sur les topics correspondants : /ultralytics/detection/image pour la détection et /ultralytics/segmentation/image pour la segmentation.

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()
Code complet
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()
Débogage

Le débogage des nœuds ROS (système d’exploitation robotique) peut être difficile en raison de la nature distribuée du système. Plusieurs outils peuvent t’aider dans ce processus :

  1. rostopic echo <TOPIC-NAME> : cette commande permet d’afficher les messages publiés sur un topic précis, afin d’inspecter le flux de données.
  2. rostopic list : utilise cette commande pour répertorier tous les topics disponibles dans le système ROS et obtenir une vue d’ensemble des flux de données actifs.
  3. rqt_graph : cet outil de visualisation affiche le graphe de communication entre les nœuds et fournit des informations sur leurs interconnexions et leurs interactions.
  4. Pour des visualisations plus complexes, comme les représentations 3D, tu peux utiliser RViz. RViz (visualisation ROS) est un puissant outil de visualisation 3D pour ROS. Il te permet de visualiser en temps réel l’état de ton robot et de son environnement. Avec RViz, tu peux afficher les données des capteurs (par exemple, sensor_msgs/Image), l’état du modèle du robot et divers autres types d’informations, ce qui facilite le débogage et la compréhension du comportement de ton système robotique.

Publier les classes détectées avec std_msgs/String#

Les messages ROS standard incluent également les messages std_msgs/String. Dans de nombreuses applications, il n’est pas nécessaire de republier toute l’image annotée ; seules les classes présentes dans le champ de vision du robot sont nécessaires. L’exemple suivant montre comment utiliser les messages std_msgs/String pour republier les classes détectées sur le topic /ultralytics/detection/classes. Ces messages sont plus légers et fournissent les informations essentielles, ce qui les rend utiles pour diverses applications.

Exemple de cas d’utilisation#

Prenons un robot d’entrepôt équipé d’une caméra et d’un modèle de détection d’objets. Au lieu d’envoyer de grandes images annotées sur le réseau, le robot peut publier une liste de classes détectées sous forme de messages std_msgs/String. Par exemple, lorsque le robot détecte des objets tels qu’une « boîte », une « palette » et un « chariot élévateur », il publie ces classes sur le topic /ultralytics/detection/classes. Un système central de surveillance peut ensuite utiliser ces informations pour suivre l’inventaire en temps réel, optimiser la planification du trajet du robot afin d’éviter les obstacles ou déclencher des actions précises, comme la prise d’une boîte détectée. Cette approche réduit la bande passante nécessaire aux communications et se concentre sur la transmission des données essentielles.

Utilisation de String étape par étape#

Cet exemple montre comment utiliser le package Ultralytics YOLO avec ROS. Dans cet exemple, nous nous abonnons à un topic de caméra, traitons l’image reçue avec YOLO et publions les objets détectés sur le nouveau topic /ultralytics/detection/classes à l’aide de messages std_msgs/String. Le package ros_numpy sert à convertir le message Image de ROS en tableau NumPy pour le traiter avec 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()

Utiliser Ultralytics avec les images de profondeur de ROS#

En plus des images RGB, ROS prend en charge les images de profondeur, qui fournissent des informations sur la distance des objets par rapport à la caméra. Les images de profondeur sont essentielles pour les applications robotiques telles que l’évitement d’obstacles, la cartographie 3D et la localisation.

Une image de profondeur est une image dans laquelle chaque pixel représente la distance entre la caméra et un objet. Contrairement aux images RGB, qui capturent les couleurs, les images de profondeur capturent les informations spatiales, permettant aux robots de percevoir la structure 3D de leur environnement.

Obtenir des images de profondeur

Les images de profondeur peuvent être obtenues à l’aide de différents capteurs :

  1. Caméras stéréo : utilisent deux caméras pour calculer la profondeur à partir de la disparité entre les images.
  2. Caméras à temps de vol (ToF) : mesurent le temps nécessaire à la lumière pour revenir d’un objet.
  3. Capteurs à lumière structurée : projettent un motif et mesurent sa déformation sur les surfaces.

Utiliser YOLO avec des images de profondeur#

Dans ROS, les images de profondeur sont représentées par le type de message sensor_msgs/Image, qui inclut des champs pour l’encodage, la hauteur, la largeur et les données des pixels. Le champ d’encodage des images de profondeur utilise souvent un format comme « 16UC1 », indiquant un entier non signé de 16 bits par pixel, chaque valeur représentant la distance jusqu’à l’objet. Les images de profondeur sont couramment utilisées avec les images RGB pour fournir une vue plus complète de l’environnement.

Avec YOLO, il est possible d’extraire et de combiner les informations des images RGB et de profondeur. Par exemple, YOLO peut détecter des objets dans une image RGB, puis cette détection peut servir à localiser les régions correspondantes dans l’image de profondeur. Cela permet d’extraire des informations de profondeur précises pour les objets détectés et d’améliorer la capacité du robot à comprendre son environnement en trois dimensions.

Caméras RGB-D

Lorsque tu travailles avec des images de profondeur, il est essentiel de vérifier que les images RGB et de profondeur sont correctement alignées. Les caméras RGB-D, comme la série Intel RealSense, fournissent des images RGB et de profondeur synchronisées, ce qui facilite la combinaison des informations provenant des deux sources. Si tu utilises des caméras RGB et de profondeur distinctes, il est essentiel de les calibrer pour garantir un alignement précis.

Utilisation de la profondeur étape par étape#

Dans cet exemple, nous utilisons YOLO pour segmenter une image et appliquons le masque obtenu afin de segmenter l’objet dans l’image de profondeur. Cela nous permet de déterminer la distance entre chaque pixel de l’objet ciblé et le centre focal de la caméra. Grâce à ces informations de distance, nous pouvons calculer la distance entre la caméra et l’objet précis dans la scène. Commence par importer les bibliothèques nécessaires, créer un nœud ROS et instancier un modèle de segmentation ainsi qu’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)

Ensuite, définis une fonction de callback qui traite le message d’image de profondeur reçu. La fonction attend les messages d’image de profondeur et d’image RGB, les convertit en tableaux NumPy et applique le modèle de segmentation à l’image RGB. Elle extrait ensuite le masque de segmentation de chaque objet détecté et calcule la distance moyenne de l’objet par rapport à la caméra à l’aide de l’image de profondeur. La plupart des capteurs ont une distance maximale, appelée distance de coupure, au-delà de laquelle les valeurs sont représentées par inf (np.inf). Avant le traitement, il est important de filtrer ces valeurs nulles et de leur attribuer la valeur 0. Enfin, elle publie les objets détectés ainsi que leurs distances moyennes sur le 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()
Code complet
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()

Utiliser Ultralytics avec ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

Le type de message sensor_msgs/PointCloud2 est une structure de données utilisée dans ROS pour représenter des données de nuage de points 3D. Ce type de message est essentiel aux applications robotiques et permet des tâches telles que la cartographie 3D, la reconnaissance d’objets et la localisation.

Un nuage de points est un ensemble de points de données définis dans un système de coordonnées tridimensionnel. Ces points représentent la surface externe d’un objet ou d’une scène, capturée au moyen de technologies de numérisation 3D. Chaque point du nuage possède les coordonnées X, Y et Z, qui correspondent à sa position dans l’espace, et peut également inclure des informations supplémentaires comme la couleur et l’intensité.

Repère de référence

Lorsque tu travailles avec sensor_msgs/PointCloud2, il est essentiel de tenir compte du repère de référence du capteur à partir duquel les données du nuage de points ont été acquises. Le nuage de points est initialement capturé dans le repère de référence du capteur. Tu peux déterminer ce repère en écoutant le topic /tf_static. Toutefois, selon les exigences de ton application, tu devras peut-être convertir le nuage de points dans un autre repère de référence. Cette transformation peut être réalisée avec le package tf2_ros, qui fournit des outils pour gérer les repères de coordonnées et transformer les données entre eux.

Obtenir des nuages de points

Les nuages de points peuvent être obtenus à l’aide de différents capteurs :

  1. LIDAR (détection et télémétrie par ondes lumineuses) : utilise des impulsions laser pour mesurer les distances jusqu’aux objets et créer des cartes 3D haute-précision.
  2. Caméras de profondeur : capturent les informations de profondeur de chaque pixel, ce qui permet de reconstruire la scène en 3D.
  3. Caméras stéréo : utilisent deux caméras ou plus pour obtenir des informations de profondeur par triangulation.
  4. Scanners à lumière structurée : projettent un motif connu sur une surface et mesurent sa déformation pour calculer la profondeur.

Utiliser YOLO avec des nuages de points#

Pour intégrer YOLO à des messages de type sensor_msgs/PointCloud2, nous pouvons utiliser une méthode similaire à celle employée pour les cartes de profondeur. En exploitant les informations de couleur intégrées au nuage de points, nous pouvons extraire une image 2D, effectuer sa segmentation avec YOLO, puis appliquer le masque obtenu aux points tridimensionnels afin d’isoler l’objet 3D ciblé.

Pour traiter les nuages de points, nous recommandons d’utiliser Open3D (pip install open3d), une bibliothèque Python facile à utiliser. Open3D fournit des outils robustes pour gérer les structures de données de nuages de points, les visualiser et exécuter facilement des opérations complexes. Cette bibliothèque peut considérablement simplifier le processus et améliorer notre capacité à manipuler et analyser les nuages de points avec une segmentation fondée sur YOLO.

Utilisation des nuages de points étape par étape#

Importe les bibliothèques nécessaires et instancie le modèle YOLO pour la segmentation.

import time

import rospy

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

Crée une fonction pointcloud2_to_array qui transforme un message sensor_msgs/PointCloud2 en deux tableaux NumPy. Les messages sensor_msgs/PointCloud2 contiennent des points n fondés sur les width et height de l’image acquise. Par exemple, une image 480 x 640 possède 307,200 points. Chaque point inclut trois coordonnées spatiales (xyz) et la couleur correspondante au format RGB. Elles peuvent être considérées comme deux canaux d’information distincts.

La fonction renvoie les coordonnées xyz et les valeurs RGB au format de la résolution d’origine de la caméra (width x height). La plupart des capteurs ont une distance maximale, appelée distance de coupure, au-delà de laquelle les valeurs sont représentées par inf (np.inf). Avant le traitement, il est important de filtrer ces valeurs nulles et de leur attribuer la valeur 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

Ensuite, abonne-toi au topic /camera/depth/points pour recevoir le message du nuage de points et convertis le message sensor_msgs/PointCloud2 en tableaux NumPy contenant les coordonnées XYZ et les valeurs RGB (à l’aide de la fonction pointcloud2_to_array). Traite l’image RGB avec le modèle YOLO pour extraire les objets segmentés. Pour chaque objet détecté, extrais le masque de segmentation et applique-le à l’image RGB ainsi qu’aux coordonnées XYZ afin d’isoler l’objet dans l’espace 3D.

Le traitement du masque est simple puisqu’il se compose de valeurs binaires, 1 indiquant la présence de l’objet et 0 son absence. Pour appliquer le masque, multiplie simplement les canaux d’origine par celui-ci. Cette opération isole efficacement l’objet ciblé dans l’image. Enfin, crée un objet nuage de points Open3D et visualise l’objet segmenté dans l’espace 3D avec les couleurs associées.

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])
Code complet
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

Conclusion#

Avec Ultralytics YOLO intégré à ROS, ton robot peut effectuer la détection d’objets et la segmentation sur des images RGB, des images de profondeur et des nuages de points, transformant les flux bruts des capteurs en informations de perception exploitables. À partir de là, explore le mode Predict pour découvrir d’autres options d’inférence, ou suis les étapes d’un projet de vision par ordinateur pour faire passer ton application robotique du prototype à la production.

FAQ#

  • Le système d’exploitation robotique (ROS) est un framework open source couramment utilisé en robotique pour aider les développeurs à créer des applications robotiques robustes. Il fournit un ensemble de bibliothèques et d’outils pour concevoir des systèmes robotiques et communiquer avec eux, ce qui facilite le développement d’applications complexes. ROS prend en charge la communication entre les nœuds au moyen de messages sur des topics ou des services.

  • L’intégration d’Ultralytics YOLO à ROS consiste à configurer un environnement ROS et à utiliser YOLO pour traiter les données des capteurs. Commence par installer les dépendances requises comme ros_numpy et Ultralytics YOLO :

    pip install ros_numpy ultralytics

    Ensuite, crée un nœud ROS et abonne-toi à un topic d’image pour traiter les données reçues lors de la détection d’objets. Voici un exemple minimal :

    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()
  • Les topics ROS facilitent la communication entre les nœuds d’un réseau ROS grâce à un modèle publication-abonnement. Un topic est un canal nommé que les nœuds utilisent pour envoyer et recevoir des messages de manière asynchrone. Avec Ultralytics YOLO, tu peux abonner un nœud à un topic d’image, traiter les images avec YOLO pour des tâches comme la détection ou la segmentation, puis publier les résultats sur de nouveaux topics.

    Par exemple, abonne-toi à un topic de caméra et traite l’image reçue pour effectuer une détection :

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • Les images de profondeur dans ROS, représentées par sensor_msgs/Image, fournissent la distance des objets par rapport à la caméra, ce qui est essentiel pour des tâches comme l’évitement d’obstacles, la cartographie 3D et la localisation. En utilisant les informations de profondeur avec les images RGB, les robots peuvent mieux comprendre leur environnement 3D.

    Avec YOLO, tu peux extraire des masques de segmentation des images RGB et appliquer ces masques aux images de profondeur afin d’obtenir des informations 3D précises sur les objets, ce qui améliore la capacité du robot à se déplacer et à interagir avec son environnement.

  • Pour visualiser des nuages de points 3D dans ROS avec YOLO :

    1. Convertis les messages sensor_msgs/PointCloud2 en tableaux NumPy.
    2. Utilise YOLO pour segmenter les images RGB.
    3. Applique le masque de segmentation au nuage de points.

    Voici un exemple utilisant Open3D pour la visualisation :

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

    Cette approche fournit une visualisation 3D des objets segmentés, utile pour des tâches telles que la navigation et la manipulation dans les applications robotiques.

Commentaires