Guide de démarrage rapide de ROS (système d’exploitation robotique)#
Ce guide te 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.
Accède à la section Configurer 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 en robotique et dans l’industrie. ROS fournit un ensemble de bibliothèques et d’outils qui aident 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 spécialistes de la robotique. Pour une brève introduction, regarde la vidéo Introduction à ROS d’Open Robotics, qui dure trois minutes.
Principales fonctionnalités de ROS#
-
Architecture modulaire : ROS possède une architecture modulaire qui permet aux développeurs de construire des systèmes complexes en combinant de petits composants réutilisables appelés nœuds. Chaque nœud remplit généralement une fonction précise et les nœuds communiquent entre eux à l’aide de messages transmis via des topics ou des services.
-
Intergiciel de communication : ROS offre une infrastructure de communication robuste qui prend en charge la communication interprocessus et l’informatique distribuée. Celle-ci repose sur un modèle publication-abonnement pour les flux de données (topics) et un modèle requête-réponse pour les appels de service.
-
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.
-
Outils et utilitaires : ROS est fourni avec un riche ensemble d’outils et d’utilitaires de visualisation, de débogage et de simulation. Par exemple, RViz sert à 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.
-
Écosystème étendu : L’écosystème ROS est vaste et en constante évolution, avec de nombreux packages disponibles pour diverses applications robotiques, notamment la navigation, la manipulation, la perception et bien d’autres. 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 ci-dessous utilisent ROS1 Noetic ; les adaptateurs compacts de la section Utiliser ROS2 montrent les interfaces rclpy correspondantes pour les versions actuelles de ROS2.
ROS 1 ou ROS 2#
Alors que ROS 1 fournissait une base solide pour le développement robotique, ROS 2 remédie à ses lacunes en proposant :
- Performances en temps réel : prise en charge améliorée des systèmes temps réel et du 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 efficace.
Messages et topics ROS#
Dans ROS, la communication entre les nœuds s’effectue par le biais 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 de 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, que d’autres nœuds peuvent ensuite utiliser 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 à l’aide de cet environnement ROS, un fork du dépôt ROS ROSbot. Le même traitement avec YOLO et NumPy s’applique dans ROS2 ; seul le cycle de vie des nœuds et la conversion des messages diffèrent.
Installation des dépendances#
En plus de l’environnement ROS, tu dois installer les dépendances suivantes :
-
Paquet ROS NumPy : il est nécessaire pour convertir rapidement les messages ROS Image en tableaux NumPy.
pip install ros_numpy -
Paquet Ultralytics :
pip install ultralytics
Utilisation de ROS2#
ROS2 remplace rospy par rclpy et la conversion d’images ros_numpy par cv_bridge. Le nœud suivant est l’équivalent ROS2 complet du flux de détection RGB ci-dessous ; 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")
# Applique le masque NumPy et le calcul de distance de l’exemple de profondeur ci-dessous.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 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 sensor_msgs/Image de ROS#
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 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.
Utilisation d’Image étape par étape#
L’extrait de code suivant montre comment utiliser le paquet Ultralytics YOLO avec ROS. Dans cet exemple, nous nous abonnons à un topic de caméra, traitons l’image entrante avec YOLO et publions les objets détectés sur de nouveaux topics de détection et de 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 (nommé ultralytics) pour permettre la communication avec le maître ROS. Pour garantir une connexion stable, nous ajoutons 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, afin de les rendre accessibles pour un traitement ultérieur. La communication entre les nœuds s’effectue à 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 tableau NumPy avec ros_numpy, traite les images avec les modèles YOLO instanciés précédemment, 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 (Robot Operating System) peut être difficile en raison de la nature distribuée du système. Plusieurs outils peuvent faciliter cette tâche :
rostopic echo <TOPIC-NAME>: cette commande te permet d’afficher les messages publiés sur un topic spécifique et d’inspecter le flux de données.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.rqt_graph: cet outil de visualisation affiche le graphe de communication entre les nœuds et permet de comprendre comment ils sont interconnectés et interagissent.- Pour des visualisations plus complexes, comme les représentations 3D, tu peux utiliser RViz. RViz (ROS Visualization) 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 comprennent également des 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 requises. L’exemple suivant montre comment utiliser des 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 d’utilisation#
Prenons l’exemple d’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 des classes détectées sous forme de messages std_msgs/String. Par exemple, lorsqu’il détecte des objets comme une « boîte », une « palette » et un « chariot élévateur », le robot publie ces classes sur le topic /ultralytics/detection/classes. Un système de surveillance central peut ensuite utiliser ces informations pour suivre les stocks en temps réel, optimiser la planification de trajectoire du robot afin d’éviter les obstacles ou déclencher des actions spécifiques, comme récupérer une boîte détectée. Cette approche réduit la bande passante nécessaire à la communication et privilégie la transmission des données essentielles.
Utilisation de String étape par étape#
Cet exemple montre comment utiliser le paquet Ultralytics YOLO avec ROS. Nous nous abonnons à un topic de caméra, traitons l’image entrante avec YOLO et publions les objets détectés sur le nouveau topic /ultralytics/detection/classes à l’aide de messages std_msgs/String. Le paquet ros_numpy sert à convertir le message ROS Image en 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 RGB, ROS prend en charge les images de profondeur, qui fournissent des informations sur la distance entre les objets et la caméra. Les images de profondeur sont essentielles dans les applications robotiques telles que l’évitement des 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 et permettent aux robots de percevoir la structure 3D de leur environnement.
Les images de profondeur peuvent être obtenues à l’aide de divers capteurs :
- Caméras stéréo : utilisent deux caméras pour calculer la profondeur à partir de la disparité des images.
- Caméras à temps de vol (ToF) : mesurent le temps que met la lumière à revenir après avoir atteint un objet.
- 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 comprend des champs pour l’encodage, la hauteur, la largeur et les données de pixels. Le champ d’encodage des images de profondeur utilise souvent un format comme « 16UC1 », qui indique 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 des informations provenant d’images RGB et de profondeur. Par exemple, YOLO peut détecter des objets dans une image RGB, puis cette détection peut servir à repérer 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 améliore la capacité du robot à comprendre son environnement en trois dimensions.
Lorsqu’on travaille 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 gamme 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 d’intérêt 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, puis 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 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 entre l’objet et 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, la fonction publie les objets détectés et 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, retina_masks=True) # masks at the original image resolution
all_objects = []
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
all_objects.append(f"{name}: {avg_distance:.2f}m")
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()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, retina_masks=True) # masks at the original image resolution
all_objects = []
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
all_objects.append(f"{name}: {avg_distance:.2f}m")
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()Utiliser Ultralytics avec sensor_msgs/PointCloud2 de ROS#
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, car il permet d’effectuer 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 des 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é.
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. Cependant, selon les exigences de ton application, tu peux avoir besoin de convertir le nuage de points vers un autre repère de référence. Cette transformation peut être effectuée à l’aide du paquet tf2_ros, qui fournit des outils pour gérer les repères de coordonnées et transformer les données d’un repère à l’autre.
Les nuages de points peuvent être obtenus à l’aide de divers capteurs :
- LIDAR (détection et télémétrie par ondes lumineuses) : utilise des impulsions laser pour mesurer la distance jusqu’aux objets et créer des cartes 3D de haute précision.
- Caméras de profondeur : capturent les informations de profondeur pour chaque pixel, ce qui permet de reconstruire la scène en 3D.
- Caméras stéréo : utilisent deux caméras ou plus pour obtenir des informations de profondeur par triangulation.
- 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 aux 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, segmenter cette image avec YOLO, puis appliquer le masque obtenu aux points tridimensionnels afin d’isoler l’objet 3D d’intérêt.
Pour gérer les nuages de points, nous recommandons Open3D (pip install open3d), une bibliothèque Python facile à utiliser. Open3D fournit des outils robustes pour gérer les structures de données des nuages de points, les visualiser et effectuer facilement des opérations complexes. Cette bibliothèque peut simplifier considérablement le processus et améliorer notre capacité à manipuler et analyser les nuages de points avec la segmentation basé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 basés sur le width et le 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. Ces éléments 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 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, rgbEnsuite, 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 comme aux coordonnées XYZ afin d’isoler l’objet dans l’espace 3D.
Le traitement du masque est simple, car il contient des valeurs binaires : 1 indique la présence de l’objet et 0 son absence. Pour appliquer le masque, il suffit de multiplier les canaux d’origine par celui-ci. Cette opération isole efficacement l’objet d’intérêt dans l’image. Enfin, crée un objet nuage de points Open3D et visualise l’objet segmenté dans l’espace 3D avec ses couleurs.
import sys
import open3d as o3d
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb, retina_masks=True) # masques à la résolution d’origine de l’image
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, retina_masks=True) # masques à la résolution d’origine de l’image
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])
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 perceptives exploitables. Ensuite, découvre le mode Predict pour 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 créer des systèmes robotiques et s’interfacer 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 échangés sur des topics ou des services.
Pour intégrer Ultralytics YOLO à ROS, tu dois configurer un environnement ROS et utiliser YOLO pour traiter les données des capteurs. Commence par installer les dépendances requises, comme
ros_numpy, ainsi que Ultralytics YOLO :pip install ros_numpy ultralyticsEnsuite, crée un nœud ROS et abonne-toi à un topic d’image pour traiter les données entrantes en vue 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 de 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 entrante pour la détection :
rospy.Subscriber("/camera/color/image_raw", Image, callback)Les images de profondeur dans ROS, représentées par
sensor_msgs/Image, indiquent la distance entre les objets et la caméra, une information essentielle pour des tâches comme l’évitement des 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 les appliquer aux images de profondeur pour obtenir des informations 3D précises sur les objets, ce qui améliore la capacité du robot à se déplacer dans son environnement et à interagir avec celui-ci.
Pour visualiser des nuages de points 3D dans ROS avec YOLO :
- Convertis les messages
sensor_msgs/PointCloud2en tableaux NumPy. - Utilise YOLO pour segmenter les images RGB.
- 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, retina_masks=True) # masques à la résolution d’origine de l’image 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 comme la navigation et la manipulation dans les applications robotiques.
- Convertis les messages