YOLO Vision 2026 :

Guide de démarrage rapide de ROS (Robot Operating System)#

Ce guide te montre comment intégrer Ultralytics YOLO avec ROS1 (rospy) ou ROS2 (rclpy) pour exécuter la détection d'objets et la segmentation en temps réel sur des images RVB, des images de profondeur et des nuages de points.

Passe directement à la configuration de YOLO avec ROS, puis travaille avec les images RVB, les images de profondeur ou les nuages de points.

ROS Introduction (captioned) from Open Robotics on Vimeo.

Qu'est-ce que ROS ?#

Le Robot Operating System (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 diverses plateformes robotiques, ce qui en fait un outil flexible et puissant pour les roboticiens.

Fonctionnalités clés de ROS#

  1. Architecture modulaire : ROS possède une architecture modulaire, permettant aux développeurs de construire des systèmes complexes en combinant des composants plus petits et réutilisables appelés nœuds. Chaque nœud remplit généralement une fonction spécifique, et les nœuds communiquent entre eux à l'aide de messages sur des topics ou des services.

  2. Middleware de communication : ROS offre une infrastructure de communication robuste qui prend en charge la communication inter-processus et le calcul distribué. Cela est réalisé grâce à un modèle de publication-abonnement pour les flux de données (topics) et un modèle de demande-réponse pour les appels de service.

  3. Abstraction matérielle : ROS fournit une couche d'abstraction sur le matériel, permettant aux développeurs d'écrire du code agnostique par rapport au périphérique. Cela permet d'utiliser le même code avec différentes configurations matérielles, facilitant ainsi l'intégration et l'expérimentation.

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

  5. Écosystème étendu : L'écosystème ROS est vaste et en croissance continue, avec de nombreux packages disponibles pour différentes applications robotiques, notamment la navigation, la manipulation, la perception, et 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é à travers plusieurs versions, divisées en ROS 1 et ROS 2. Les exemples existants ci-dessous utilisent ROS1 Noetic ; les adaptateurs compacts de la section Utilisation de ROS2 montrent les interfaces rclpy correspondantes pour les versions actuelles de ROS2.

ROS 1 vs. ROS 2#

Alors que ROS 1 fournissait une base solide pour le développement robotique, ROS 2 résout ses lacunes en offrant :

  • Performance 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é amélioré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.
  • Support multi-plateforme : Compatibilité étendue avec divers systèmes d'exploitation au-delà de Linux, y compris Windows et macOS.
  • Communication flexible : Utilisation de DDS pour une communication inter-processus plus flexible et efficace.

Messages et Topics ROS#

Dans ROS, la communication entre les nœuds est facilitée par des messages et des 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 à des messages provenant d'un topic, ce qui leur permet de communiquer entre eux. Ce modèle publication-abonnement permet une communication asynchrone et un découplage entre les nœuds. Chaque capteur ou actionneur d'un système robotique publie généralement des données sur un topic, qui peuvent ensuite être consommées par d'autres nœuds pour le traitement ou le contrôle. Pour les besoins de ce guide, nous nous concentrerons sur les messages d'image, de profondeur et de nuage de points, ainsi que sur les topics de caméra.

Configuration de Ultralytics YOLO avec ROS#

Les exemples ROS1 ont été testés en utilisant cet environnement ROS, un fork du dépôt ROSbot ROS. Le même traitement 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 dehors de l'environnement ROS, tu devras installer les dépendances suivantes :

  • Package ROS NumPy : Ceci est requis pour une conversion rapide entre les messages d'image ROS et les tableaux NumPy.

    pip install ros_numpy
  • Package Ultralytics :

    pip install ultralytics

Utilisation de ROS2#

ROS2 remplace rospy par rclpy et la conversion d'image ros_numpy par cv_bridge. Le nœud suivant est l'équivalent ROS2 complet du flux de détection RVB ci-dessous ; instancie les modèles une seule fois et réutilise-les dans les rappels.

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 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 structuré une seule fois, puis réutilise la segmentation NumPy et la cartographie 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 de pixels, ce qui le rend adapté à la transmission d'images capturées par des caméras ou d'autres capteurs. Les messages d'image sont largement utilisés dans les applications robotiques pour des tâches telles que la perception visuelle, la détection d'objets et la navigation.

Detection and Segmentation in ROS Gazebo

Utilisation étape par étape des images#

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 entrante à l'aide de YOLO et publions les objets détectés sur de nouveaux topics pour la détection et la segmentation.

Tout d'abord, importe les bibliothèques nécessaires et instancie deux modèles : un pour la segmentation et un pour la détection. Initialise un nœud ROS (portant le nom ultralytics) pour permettre la communication avec le maître ROS. Pour garantir une connexion stable, nous incluons une brève pause, ce qui laisse au nœud suffisamment de temps pour établir la connexion avant de continuer.

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 seront utilisés pour publier les images annotées, les rendant accessibles pour un traitement ultérieur. La communication entre les nœuds est facilitée à l'aide 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 sur le topic /camera/color/image_raw et appelle une fonction de rappel pour chaque nouveau message. Cette fonction de rappel reçoit des messages de type sensor_msgs/Image, les convertit en un 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 publie à nouveau sur les topics respectifs : /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 (Robot Operating System) 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 te permet d'afficher les messages publiés sur un topic spécifique, t'aidant à inspecter le flux de données.
  2. rostopic list : Utilise cette commande pour lister tous les topics disponibles dans le système ROS, te donnant 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, fournissant des informations sur la manière dont les nœuds sont interconnectés et interagissent.
  4. Pour des visualisations plus complexes, telles que des représentations 3D, tu peux utiliser RViz. RViz (ROS Visualization) est un outil de visualisation 3D puissant pour ROS. Il te permet de visualiser l'état de ton robot et de son environnement en temps réel. Avec RViz, tu peux afficher des données de capteurs (par ex., sensor_msgs/Image), les états du modèle de 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 des messages std_msgs/String. Dans de nombreuses applications, il n'est pas nécessaire de republier l'image annotée entière ; à la place, 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 sujet /ultralytics/detection/classes. Ces messages sont plus légers et fournissent des informations essentielles, ce qui les rend précieux pour diverses applications.

Exemple de cas d'utilisation#

Considère 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 que "box", "pallet" et "forklift", il publie ces classes sur le topic /ultralytics/detection/classes. Ces informations peuvent ensuite être utilisées par un système de surveillance central pour suivre l'inventaire en temps réel, optimiser la planification de la trajectoire du robot pour éviter les obstacles, ou déclencher des actions spécifiques telles que la saisie d'une boîte détectée. Cette approche réduit la bande passante requise pour la communication et se concentre sur la transmission de données critiques.

Utilisation étape par étape des chaînes (String)#

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 entrante à l'aide de YOLO, et publions les objets détectés sur le nouveau topic /ultralytics/detection/classes en utilisant des messages std_msgs/String. Le package ros_numpy est utilisé pour convertir le message d'image ROS en un tableau NumPy pour le traitement 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 ROS#

En plus des images RVB, 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 cruciales pour les applications robotiques telles que l'évitement d'obstacles, la cartographie 3D et la localisation.

Une image de profondeur est une image où chaque pixel représente la distance de la caméra à un objet. Contrairement aux images RGB qui capturent la couleur, les images de profondeur capturent des informations spatiales, permettant aux robots de percevoir la structure 3D de leur environnement.

Obtention d'images de profondeur

Les images de profondeur peuvent être obtenues en utilisant divers capteurs :

  1. Caméras stéréo : Utiliser deux caméras pour calculer la profondeur en fonction de la disparité des images.
  2. Caméras ToF (Time-of-Flight) : Mesurer le temps mis par la lumière pour revenir d'un objet.
  3. Capteurs à lumière structurée : Projeter un motif et mesurer 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 comprend des champs pour l'encodage, la hauteur, la largeur et les données de pixels. Le champ d'encodage pour les images de profondeur utilise souvent un format tel que "16UC1", indiquant un entier non signé de 16 bits par pixel, où chaque valeur représente la distance jusqu'à l'objet. Les images de profondeur sont couramment utilisées conjointement avec les images RVB pour fournir une vue plus complète de l'environnement.

En utilisant YOLO, il est possible d'extraire et de combiner des informations provenant à la fois des images RGB et de profondeur. Par exemple, YOLO peut détecter des objets dans une image RGB, et cette détection peut être utilisée pour localiser les régions correspondantes dans l'image de profondeur. Cela permet l'extraction d'informations de profondeur précises pour les objets détectés, améliorant la capacité du robot à comprendre son environnement en trois dimensions.

Caméras RGB-D

Lorsqu'on travaille avec des images de profondeur, il est essentiel de s'assurer que les images RVB et de profondeur sont correctement alignées. Les caméras RVB-D, telles que la série Intel RealSense, fournissent des images RVB et de profondeur synchronisées, ce qui facilite la combinaison des informations provenant des deux sources. Si tu utilises des caméras RVB et de profondeur séparées, il est crucial de les calibrer pour assurer un alignement précis.

Utilisation étape par étape de la profondeur#

Dans cet exemple, nous utilisons YOLO pour segmenter une image et appliquer le masque extrait pour segmenter l'objet dans l'image de profondeur. Cela nous permet de déterminer la distance de chaque pixel de l'objet d'intérêt par rapport au centre focal de la caméra. En obtenant cette information de distance, nous pouvons calculer la distance entre la caméra et l'objet spécifique 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 et 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 rappel qui traite le message d'image de profondeur entrant. La fonction attend les messages d'image de profondeur et d'image RVB, les convertit en tableaux NumPy et applique le modèle de segmentation à l'image RVB. Elle extrait ensuite le masque de segmentation pour 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 clipping, au-delà de laquelle les valeurs sont représentées comme inf (np.inf). Avant le traitement, il est important de filtrer ces valeurs nulles et de leur assigner une valeur de 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 fait partie intégrante des applications robotiques, permettant 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 de données représentent la surface externe d'un objet ou d'une scène, capturée via des technologies de numérisation 3D. Chaque point du nuage possède des coordonnées X, Y et Z, qui correspondent à sa position dans l'espace, et peut également inclure des informations supplémentaires telles que la couleur et l'intensité.

Cadre de référence

Lors de l'utilisation de sensor_msgs/PointCloud2, il est essentiel de prendre en compte le 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 de référence en écoutant le topic /tf_static. Cependant, selon les exigences spécifiques de ton application, tu pourrais avoir besoin de convertir le nuage de points dans un autre repère de référence. Cette transformation peut être réalisée à l'aide du package tf2_ros, qui fournit des outils pour gérer les repères de coordonnées et transformer les données entre eux.

Obtention de nuages de points

Les nuages de points peuvent être obtenus en utilisant divers capteurs :

  1. LIDAR (Light Detection and Ranging) : Utilise des impulsions laser pour mesurer les distances aux objets et créer des cartes 3D de haute précision.
  2. Caméras de profondeur : Capturent des informations de profondeur pour chaque pixel, permettant la reconstruction 3D de la scène.
  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 la déformation pour calculer la profondeur.

Utiliser YOLO avec des nuages de points#

Pour intégrer YOLO avec des messages de type sensor_msgs/PointCloud2, nous pouvons employer une méthode similaire à celle utilisée pour les cartes de profondeur. En exploitant les informations de couleur intégrées dans le nuage de points, nous pouvons extraire une image 2D, effectuer une segmentation sur cette image à l'aide de YOLO, puis appliquer le masque résultant aux points tridimensionnels pour isoler l'objet 3D d'intérêt.

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

Utilisation étape par étape des nuages de points#

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 basés sur width et height de l'image acquise. Par exemple, une image 480 x 640 aura des points 307,200. Chaque point comprend trois coordonnées spatiales (xyz) et la couleur correspondante au format RGB. Ceux-ci peuvent être considérés comme deux canaux d'information distincts.

La fonction renvoie les coordonnées xyz et les valeurs RGB au format de la résolution de caméra d'origine (width x height). La plupart des capteurs ont une distance maximale, appelée distance de clipping, au-delà de laquelle les valeurs sont représentées comme inf (np.inf). Avant le traitement, il est important de filtrer ces valeurs nulles et de leur assigner une valeur de 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 de nuage de points et convertis le message sensor_msgs/PointCloud2 en tableaux NumPy contenant les coordonnées XYZ et les valeurs RVB (à l'aide de la fonction pointcloud2_to_array). Traite l'image RVB à l'aide du modèle YOLO pour extraire les objets segmentés. Pour chaque objet détecté, extrais le masque de segmentation et applique-le à la fois à l'image RVB et aux coordonnées XYZ pour isoler l'objet dans l'espace 3D.

Le traitement du masque est simple puisqu'il est constitué de valeurs binaires, 1 indiquant la présence de l'objet et 0 indiquant son absence. Pour appliquer le masque, multiplie simplement les canaux d'origine par le masque. Cette opération isole efficacement l'objet d'intérêt dans l'image. Enfin, crée un objet de 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é dans ROS, ton robot peut exécuter la détection d'objets et la segmentation sur des images RVB, des images de profondeur et des nuages de points, transformant les flux de capteurs bruts en une perception exploitable. À partir de là, explore le mode Prédiction pour plus d'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#

Qu'est-ce que le Robot Operating System (ROS) ?#

Le Robot Operating System (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 construire des systèmes robotiques et s'interfacer avec eux, facilitant le développement d'applications complexes. ROS prend en charge la communication entre les nœuds à l'aide de messages sur des topics ou des services.

Comment intégrer Ultralytics YOLO avec ROS pour la détection d'objets en temps réel ?#

L'intégration d'Ultralytics YOLO avec ROS implique la configuration d'un environnement ROS et l'utilisation de YOLO pour traiter les données des capteurs. Commence par installer les dépendances requises telles que 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 entrantes pour 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()

Quels sont les topics ROS et comment sont-ils utilisés dans Ultralytics YOLO ?#

Les topics ROS facilitent la communication entre les nœuds d'un réseau ROS en utilisant 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. Dans le contexte d'Ultralytics YOLO, tu peux faire en sorte qu'un nœud s'abonne à un topic d'image, traite les images à l'aide de YOLO pour des tâches telles que la détection ou la segmentation, et publie les résultats sur de nouveaux topics.

Par exemple, abonne-toi à un topic de caméra et traite l'image entrante pour la détection :

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

Pourquoi utiliser des images de profondeur avec Ultralytics YOLO dans ROS ?#

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 crucial pour des tâches telles que l'évitement d'obstacles, la cartographie 3D et la localisation. En utilisant les informations de profondeur conjointement avec les images RVB, les robots peuvent mieux comprendre leur environnement 3D.

Avec YOLO, tu peux extraire des masques de segmentation à partir d'images RVB et appliquer ces masques aux images de profondeur pour obtenir des informations précises sur les objets 3D, améliorant ainsi la capacité du robot à naviguer et à interagir avec son environnement.

Comment puis-je visualiser des nuages de points 3D avec YOLO dans ROS ?#

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

  1. Convertir 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