Kurzanleitung zu ROS (Betriebssystem für Roboter)#
Diese Anleitung zeigt dir, wie du Ultralytics YOLO mit ROS1 (rospy) oder ROS2 (rclpy) integrierst, um Objekterkennung und Segmentierung in Echtzeit auf RGB-Bildern, Tiefenbildern und Punktwolken auszuführen.
Springe direkt zum Abschnitt YOLO mit ROS einrichten und arbeite anschließend mit RGB-Bildern, Tiefenbildern oder Punktwolken.
Was ist ROS?#
Das Betriebssystem für Roboter (ROS) ist ein quelloffenes Framework, das in der Robotikforschung und -industrie weit verbreitet ist. ROS stellt eine Sammlung von Bibliotheken und Werkzeugen bereit, die Entwickler beim Erstellen von Roboteranwendungen unterstützen. ROS ist für die Arbeit mit verschiedenen Robotikplattformen ausgelegt und dadurch ein flexibles und leistungsfähiges Werkzeug für Robotikentwickler. Eine kurze Einführung bietet das dreiminütige Video ROS Introduction von Open Robotics.
Wichtige Funktionen von ROS#
-
Modulare Architektur: ROS verfügt über eine modulare Architektur, mit der Entwickler komplexe Systeme durch die Kombination kleinerer, wiederverwendbarer Komponenten, sogenannter Knoten, erstellen können. Jeder Knoten übernimmt normalerweise eine bestimmte Funktion, und Knoten kommunizieren über Nachrichten mittels Topics oder Diensten miteinander.
-
Kommunikationsmiddleware: ROS bietet eine robuste Kommunikationsinfrastruktur, die die Kommunikation zwischen Prozessen und verteiltes Rechnen unterstützt. Dies wird durch ein Publish-Subscribe-Modell für Datenströme (Topics) und ein Request-Reply-Modell für Dienstaufrufe erreicht.
-
Hardwareabstraktion: ROS stellt eine Abstraktionsebene über der Hardware bereit, sodass Entwickler geräteunabhängigen Code schreiben können. Dadurch kann derselbe Code mit unterschiedlichen Hardwarekonfigurationen verwendet werden, was Integration und Experimente erleichtert.
-
Werkzeuge und Hilfsprogramme: ROS wird mit einer umfangreichen Sammlung von Werkzeugen und Hilfsprogrammen für Visualisierung, Fehleranalyse und Simulation ausgeliefert. RViz dient beispielsweise zur Visualisierung von Sensordaten und Informationen zum Roboterzustand, während Gazebo eine leistungsfähige Simulationsumgebung zum Testen von Algorithmen und Roboterentwürfen bereitstellt.
-
Umfangreiches Ökosystem: Das ROS-Ökosystem ist groß und wächst kontinuierlich. Es stehen zahlreiche Pakete für verschiedene Roboteranwendungen zur Verfügung, darunter Navigation, Manipulation, Wahrnehmung und mehr. Die Community beteiligt sich aktiv an der Entwicklung und Pflege dieser Pakete.
Entwicklung der ROS-Versionen
Seit seiner Entwicklung im Jahr 2007 hat sich ROS durch mehrere Versionen weiterentwickelt und wurde in ROS 1 und ROS 2 aufgeteilt. Die vorhandenen Beispiele unten verwenden ROS1 Noetic; die kompakten Adapter unter ROS2 verwenden zeigen die entsprechenden rclpy-Schnittstellen für aktuelle ROS2-Versionen.
ROS 1 im Vergleich zu ROS 2#
Während ROS 1 eine solide Grundlage für die Roboterentwicklung bot, behebt ROS 2 dessen Schwächen durch folgende Funktionen:
- Echtzeitverhalten: Verbesserte Unterstützung für Echtzeitsysteme und deterministisches Verhalten.
- Sicherheit: Erweiterte Sicherheitsfunktionen für einen sicheren und zuverlässigen Betrieb in verschiedenen Umgebungen.
- Skalierbarkeit: Bessere Unterstützung für Multi-Roboter-Systeme und groß angelegte Bereitstellungen.
- Plattformübergreifende Unterstützung: Erweiterte Kompatibilität mit verschiedenen Betriebssystemen neben Linux, darunter Windows und macOS.
- Flexible Kommunikation: Verwendung von DDS für eine flexiblere und effizientere Kommunikation zwischen Prozessen.
ROS-Nachrichten und Topics#
In ROS wird die Kommunikation zwischen Knoten durch Nachrichten und Topics ermöglicht. Eine Nachricht ist eine Datenstruktur, die die zwischen Knoten ausgetauschten Informationen definiert, während ein Topic ein benannter Kanal ist, über den Nachrichten gesendet und empfangen werden. Knoten können Nachrichten an ein Topic veröffentlichen oder Nachrichten von einem Topic abonnieren und so miteinander kommunizieren. Dieses Publish-Subscribe-Modell ermöglicht asynchrone Kommunikation und eine Entkopplung zwischen Knoten. Jeder Sensor oder Aktor in einem Robotersystem veröffentlicht normalerweise Daten in einem Topic, die anschließend von anderen Knoten zur Verarbeitung oder Steuerung verwendet werden können. In dieser Anleitung konzentrieren wir uns auf Image-, Depth- und PointCloud-Nachrichten sowie Kameratopics.
Ultralytics YOLO mit ROS einrichten#
Die ROS1-Beispiele wurden mit dieser ROS-Umgebung getestet, einem Fork des ROS-Repositorys von ROSbot. Die Verarbeitung mit YOLO und NumPy ist in ROS2 identisch; nur der Lebenszyklus des Knotens und die Nachrichtenkonvertierung unterscheiden sich.
Installation der Abhängigkeiten#
Zusätzlich zur ROS-Umgebung musst du die folgenden Abhängigkeiten installieren:
-
ROS-NumPy-Paket: Dieses Paket wird für die schnelle Konvertierung zwischen ROS-Image-Nachrichten und NumPy-Arrays benötigt.
pip install ros_numpy -
Ultralytics-Paket:
pip install ultralytics
ROS2 verwenden#
ROS2 ersetzt rospy durch rclpy sowie die Bildkonvertierung ros_numpy durch cv_bridge. Der folgende Knoten ist das vollständige ROS2-Gegenstück zum darunter beschriebenen RGB-Erkennungsablauf; instanziiere Modelle einmal und verwende sie in mehreren Callbacks wieder.
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()Für Tiefenbilder kannst du den unten stehenden Code zur Tiefenverarbeitung wiederverwenden und nur das Abrufen und Konvertieren der Nachrichten ersetzen:
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.Für Punktwolken stellt ROS2 sensor_msgs_py.point_cloud2 bereit. Konvertiere die organisierte Punktwolke einmal und verwende anschließend die NumPy-Segmentierung und die unten beschriebene 3D-Zuordnung wieder:
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)Ultralytics mit ROS sensor_msgs/Image verwenden#
Der sensor_msgs/Image-Nachrichtentyp wird in ROS häufig zur Darstellung von Bilddaten verwendet. Er enthält Felder für Codierung, Höhe, Breite und Pixeldaten und eignet sich dadurch zur Übertragung von Bildern, die von Kameras oder anderen Sensoren aufgenommen wurden. Bildnachrichten werden in Roboteranwendungen häufig für Aufgaben wie visuelle Wahrnehmung, Objekterkennung und Navigation verwendet.
Schrittweise Verwendung von Bildern#
Das folgende Codefragment zeigt, wie du das Ultralytics-YOLO-Paket mit ROS verwendest. In diesem Beispiel abonnieren wir ein Kameratopic, verarbeiten das eingehende Bild mit YOLO und veröffentlichen die erkannten Objekte in neuen Topics für Erkennung und Segmentierung.
Importiere zunächst die erforderlichen Bibliotheken und instanziiere zwei Modelle: eines für Segmentierung und eines für Erkennung. Initialisiere einen ROS-Knoten mit dem Namen ultralytics, um die Kommunikation mit dem ROS-Master zu ermöglichen. Um eine stabile Verbindung sicherzustellen, fügen wir eine kurze Pause ein, damit der Knoten vor dem Fortfahren ausreichend Zeit zum Verbindungsaufbau hat.
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)Initialisiere zwei ROS-Topics: eines für Erkennung und eines für Segmentierung. Diese Topics werden zum Veröffentlichen der annotierten Bilder verwendet, sodass sie für die weitere Verarbeitung verfügbar sind. Die Kommunikation zwischen den Knoten erfolgt über sensor_msgs/Image-Nachrichten.
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)Erstelle schließlich einen Subscriber, der Nachrichten auf dem Topic /camera/color/image_raw empfängt und für jede neue Nachricht eine Callback-Funktion aufruft. Diese Callback-Funktion empfängt Nachrichten vom Typ sensor_msgs/Image, konvertiert sie mit ros_numpy in ein NumPy-Array, verarbeitet die Bilder mit den zuvor instanziierten YOLO-Modellen, annotiert die Bilder und veröffentlicht sie anschließend wieder in den jeweiligen Topics: /ultralytics/detection/image für Erkennung und /ultralytics/segmentation/image für Segmentierung.
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()Vollständiger Code
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()Fehleranalyse
Die Fehleranalyse von ROS-Knoten (Betriebssystem für Roboter) kann aufgrund der verteilten Struktur des Systems anspruchsvoll sein. Mehrere Werkzeuge können dich dabei unterstützen:
rostopic echo <TOPIC-NAME>: Mit diesem Befehl kannst du die in einem bestimmten Topic veröffentlichten Nachrichten anzeigen und so den Datenfluss untersuchen.rostopic list: Mit diesem Befehl listest du alle verfügbaren Topics im ROS-System auf und erhältst einen Überblick über die aktiven Datenströme.rqt_graph: Dieses Visualisierungswerkzeug zeigt den Kommunikationsgraphen zwischen Knoten und gibt Aufschluss darüber, wie die Knoten miteinander verbunden sind und interagieren.- Für komplexere Visualisierungen, etwa 3D-Darstellungen, kannst du RViz verwenden. RViz (ROS-Visualisierung) ist ein leistungsfähiges 3D-Visualisierungswerkzeug für ROS. Damit kannst du den Zustand deines Roboters und seiner Umgebung in Echtzeit visualisieren. Mit RViz kannst du Sensordaten (z. B.
sensor_msgs/Image), Zustände des Robotermodells und verschiedene andere Informationstypen anzeigen, wodurch sich das Verhalten deines Robotersystems leichter analysieren und verstehen lässt.
Erkannte Klassen mit std_msgs/String veröffentlichen#
Zu den standardmäßigen ROS-Nachrichten gehören auch std_msgs/String-Nachrichten. In vielen Anwendungen ist es nicht erforderlich, das gesamte annotierte Bild erneut zu veröffentlichen; stattdessen werden nur die im Sichtfeld des Roboters vorhandenen Klassen benötigt. Das folgende Beispiel zeigt, wie du std_msgs/String-Nachrichten verwendest, um die erkannten Klassen im Topic /ultralytics/detection/classes erneut zu veröffentlichen. Diese Nachrichten sind leichter und enthalten die wesentlichen Informationen, wodurch sie für verschiedene Anwendungen wertvoll sind.
Beispielanwendungsfall#
Betrachte einen Lagerroboter, der mit einer Kamera und einem Erkennungsmodell für Objekte ausgestattet ist. Anstatt große annotierte Bilder über das Netzwerk zu senden, kann der Roboter eine Liste erkannter Klassen als std_msgs/String-Nachrichten veröffentlichen. Wenn der Roboter beispielsweise Objekte wie „Kiste“, „Palette“ und „Gabelstapler“ erkennt, veröffentlicht er diese Klassen im Topic /ultralytics/detection/classes. Ein zentrales Überwachungssystem kann diese Informationen anschließend verwenden, um den Bestand in Echtzeit zu verfolgen, die Routenplanung des Roboters zur Vermeidung von Hindernissen zu optimieren oder bestimmte Aktionen auszulösen, etwa das Aufnehmen einer erkannten Kiste. Dieser Ansatz reduziert die für die Kommunikation erforderliche Bandbreite und konzentriert sich auf die Übertragung wichtiger Daten.
Schrittweise Verwendung von Zeichenketten#
Dieses Beispiel zeigt, wie du das Ultralytics-YOLO-Paket mit ROS verwendest. In diesem Beispiel abonnieren wir ein Kameratopic, verarbeiten das eingehende Bild mit YOLO und veröffentlichen die erkannten Objekte im neuen Topic /ultralytics/detection/classes mithilfe von std_msgs/String-Nachrichten. Das Paket ros_numpy wird verwendet, um die ROS-Image-Nachricht für die Verarbeitung mit YOLO in ein NumPy-Array zu konvertieren.
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()Ultralytics mit ROS-Tiefenbildern verwenden#
Zusätzlich zu RGB-Bildern unterstützt ROS Tiefenbilder, die Informationen über die Entfernung von Objekten zur Kamera liefern. Tiefenbilder sind für Roboteranwendungen wie Hindernisvermeidung, 3D-Kartierung und Lokalisierung von entscheidender Bedeutung.
Ein Tiefenbild ist ein Bild, in dem jedes Pixel die Entfernung von der Kamera zu einem Objekt darstellt. Im Gegensatz zu RGB-Bildern, die Farben erfassen, erfassen Tiefenbilder räumliche Informationen und ermöglichen Robotern dadurch, die 3D-Struktur ihrer Umgebung wahrzunehmen.
Tiefenbilder können mit verschiedenen Sensoren erfasst werden:
- Stereokameras: Verwenden zwei Kameras, um die Tiefe anhand der Bildverschiebung zu berechnen.
- Time-of-Flight-Kameras (ToF): Messen die Zeit, die Licht für die Rückkehr von einem Objekt benötigt.
- Sensoren mit strukturiertem Licht: Projizieren ein Muster und messen dessen Verformung auf Oberflächen.
YOLO mit Tiefenbildern verwenden#
In ROS werden Tiefenbilder durch den Nachrichtentyp sensor_msgs/Image dargestellt, der Felder für Codierung, Höhe, Breite und Pixeldaten enthält. Für die Codierung von Tiefenbildern wird häufig ein Format wie „16UC1“ verwendet. Dies bezeichnet eine vorzeichenlose 16-Bit-Ganzzahl pro Pixel, wobei jeder Wert die Entfernung zum Objekt darstellt. Tiefenbilder werden häufig zusammen mit RGB-Bildern verwendet, um ein umfassenderes Bild der Umgebung bereitzustellen.
Mit YOLO ist es möglich, Informationen aus RGB- und Tiefenbildern zu extrahieren und zu kombinieren. YOLO kann beispielsweise Objekte in einem RGB-Bild erkennen, und diese Erkennung kann verwendet werden, um entsprechende Bereiche im Tiefenbild zu bestimmen. Dadurch lassen sich präzise Tiefeninformationen für erkannte Objekte extrahieren, was die Fähigkeit des Roboters verbessert, seine Umgebung dreidimensional zu verstehen.
Bei der Arbeit mit Tiefenbildern musst du unbedingt sicherstellen, dass RGB- und Tiefenbilder korrekt ausgerichtet sind. RGB-D-Kameras wie die Intel-RealSense-Serie liefern synchronisierte RGB- und Tiefenbilder und erleichtern dadurch die Kombination von Informationen aus beiden Quellen. Wenn du separate RGB- und Tiefenkameras verwendest, musst du sie für eine genaue Ausrichtung unbedingt kalibrieren.
Schrittweise Verwendung von Tiefenbildern#
In diesem Beispiel verwenden wir YOLO, um ein Bild zu segmentieren, und wenden die extrahierte Maske an, um das Objekt im Tiefenbild zu segmentieren. So können wir die Entfernung jedes Pixels des interessierenden Objekts vom Brennpunktzentrum der Kamera bestimmen. Mithilfe dieser Entfernungsinformationen können wir die Entfernung zwischen der Kamera und dem jeweiligen Objekt in der Szene berechnen. Importiere zunächst die erforderlichen Bibliotheken, erstelle einen ROS-Knoten und instanziiere ein Segmentierungsmodell sowie ein ROS-Topic.
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)Definiere anschließend eine Callback-Funktion, die die eingehende Tiefenbildnachricht verarbeitet. Die Funktion wartet auf die Nachrichten des Tiefenbilds und des RGB-Bilds, konvertiert sie in NumPy-Arrays und wendet das Segmentierungsmodell auf das RGB-Bild an. Anschließend extrahiert sie die Segmentierungsmaske für jedes erkannte Objekt und berechnet mithilfe des Tiefenbilds die durchschnittliche Entfernung des Objekts von der Kamera. Die meisten Sensoren haben eine maximale Entfernung, die als Clip-Distanz bezeichnet wird und jenseits derer Werte als inf (np.inf) dargestellt werden. Vor der Verarbeitung müssen diese Nullwerte herausgefiltert und durch 0 ersetzt werden. Schließlich veröffentlicht die Funktion die erkannten Objekte zusammen mit ihren durchschnittlichen Entfernungen im 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()Vollständiger Code
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()Ultralytics mit ROS sensor_msgs/PointCloud2 verwenden#
Der sensor_msgs/PointCloud2-Nachrichtentyp ist eine Datenstruktur, die in ROS zur Darstellung von 3D-Punktwolkendaten verwendet wird. Dieser Nachrichtentyp ist für Roboteranwendungen unverzichtbar und ermöglicht Aufgaben wie 3D-Kartierung, Objekterkennung und Lokalisierung.
Eine Punktwolke ist eine Sammlung von Datenpunkten in einem dreidimensionalen Koordinatensystem. Diese Datenpunkte stellen die äußere Oberfläche eines Objekts oder einer Szene dar, die mithilfe von 3D-Scantechnologien erfasst wurde. Jeder Punkt der Wolke besitzt die Koordinaten X, Y und Z, die seiner Position im Raum entsprechen, und kann zusätzliche Informationen wie Farbe und Intensität enthalten.
Bei der Arbeit mit sensor_msgs/PointCloud2 musst du unbedingt den Bezugsrahmen des Sensors berücksichtigen, von dem die Punktwolkendaten erfasst wurden. Die Punktwolke wird zunächst im Bezugsrahmen des Sensors erfasst. Du kannst diesen Bezugsrahmen bestimmen, indem du das Topic /tf_static abhörst. Je nach den Anforderungen deiner Anwendung musst du die Punktwolke jedoch möglicherweise in einen anderen Bezugsrahmen umwandeln. Diese Transformation kann mit dem Paket tf2_ros durchgeführt werden, das Werkzeuge zur Verwaltung von Koordinatensystemen und zur Transformation von Daten zwischen ihnen bereitstellt.
Punktwolken können mit verschiedenen Sensoren erfasst werden:
- LIDAR (Lichterkennung und Entfernungsmessung): Verwendet Laserpulse, um Entfernungen zu Objekten zu messen und 3D-Karten mit hoher Präzision zu erstellen.
- Tiefenkameras: Erfassen Tiefeninformationen für jedes Pixel und ermöglichen die 3D-Rekonstruktion der Szene.
- Stereokameras: Verwenden zwei oder mehr Kameras, um durch Triangulation Tiefeninformationen zu ermitteln.
- Scanner mit strukturiertem Licht: Projizieren ein bekanntes Muster auf eine Oberfläche und messen die Verformung, um die Tiefe zu berechnen.
YOLO mit Punktwolken verwenden#
Um YOLO in Nachrichtentypen sensor_msgs/PointCloud2 zu integrieren, können wir eine ähnliche Methode wie bei Tiefenkarten verwenden. Indem wir die in der Punktwolke enthaltenen Farbinformationen nutzen, können wir ein 2D-Bild extrahieren, dieses Bild mit YOLO segmentieren und die resultierende Maske anschließend auf die dreidimensionalen Punkte anwenden, um das gewünschte 3D-Objekt zu isolieren.
Für die Verarbeitung von Punktwolken empfehlen wir Open3D (pip install open3d), eine benutzerfreundliche Python-Bibliothek. Open3D stellt robuste Werkzeuge zur Verwaltung und Visualisierung von Punktwolkendatenstrukturen sowie zur nahtlosen Ausführung komplexer Operationen bereit. Diese Bibliothek kann den Prozess erheblich vereinfachen und unsere Möglichkeiten zur Bearbeitung und Analyse von Punktwolken in Verbindung mit YOLO-basierter Segmentierung verbessern.
Schrittweise Verwendung von Punktwolken#
Importiere die erforderlichen Bibliotheken und instanziiere das YOLO-Modell für die Segmentierung.
import time
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")Erstelle die Funktion pointcloud2_to_array, die eine sensor_msgs/PointCloud2-Nachricht in zwei NumPy-Arrays umwandelt. Die sensor_msgs/PointCloud2-Nachrichten enthalten n Punkte auf Grundlage von width und height des erfassten Bildes. Ein 480 x 640-Bild enthält beispielsweise 307,200 Punkte. Jeder Punkt umfasst drei räumliche Koordinaten (xyz) sowie die zugehörige Farbe im Format RGB. Diese können als zwei getrennte Informationskanäle betrachtet werden.
Die Funktion gibt die Koordinaten xyz und die Werte RGB im Format der ursprünglichen Kameraauflösung (width x height) zurück. Die meisten Sensoren haben eine maximale Entfernung, die als Clip-Distanz bezeichnet wird und jenseits derer Werte als inf (np.inf) dargestellt werden. Vor der Verarbeitung müssen diese Nullwerte herausgefiltert und durch 0 ersetzt werden.
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, rgbAbonniere anschließend das Topic /camera/depth/points, um die Punktwolkenachricht zu empfangen, und konvertiere die sensor_msgs/PointCloud2-Nachricht mithilfe der Funktion pointcloud2_to_array in NumPy-Arrays mit XYZ-Koordinaten und RGB-Werten. Verarbeite das RGB-Bild mit dem YOLO-Modell, um segmentierte Objekte zu extrahieren. Extrahiere für jedes erkannte Objekt die Segmentierungsmaske und wende sie sowohl auf das RGB-Bild als auch auf die XYZ-Koordinaten an, um das Objekt im 3D-Raum zu isolieren.
Die Maske lässt sich unkompliziert verarbeiten, da sie aus Binärwerten besteht: 1 steht für das Vorhandensein des Objekts und 0 für dessen Abwesenheit. Um die Maske anzuwenden, multiplizierst du einfach die ursprünglichen Kanäle mit der Maske. Dadurch wird das gewünschte Objekt im Bild effektiv isoliert. Erstelle schließlich ein Open3D-Punktwolkenobjekt und visualisiere das segmentierte Objekt mit den zugehörigen Farben im 3D-Raum.
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])Vollständiger Code
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])
Fazit#
Mit der Integration von Ultralytics YOLO in ROS kann dein Roboter Objekterkennung und Segmentierung auf RGB-Bildern, Tiefenbildern und Punktwolken ausführen und so rohe Sensorströme in verwertbare Wahrnehmungsdaten umwandeln. Als Nächstes kannst du den Vorhersagemodus für weitere Inferenzoptionen erkunden oder den Schritten eines Computer-Vision-Projekts folgen, um deine Roboteranwendung vom Prototyp bis zur Produktion zu führen.
FAQ#
Das Betriebssystem für Roboter (ROS) ist ein quelloffenes Framework, das häufig in der Robotik eingesetzt wird und Entwickler beim Erstellen robuster Roboteranwendungen unterstützt. Es stellt eine Sammlung von Bibliotheken und Werkzeugen zum Aufbau und zur Anbindung von Robotersystemen bereit und erleichtert dadurch die Entwicklung komplexer Anwendungen. ROS unterstützt die Kommunikation zwischen Knoten über Nachrichten mittels Topics oder Diensten.
Die Integration von Ultralytics YOLO mit ROS umfasst das Einrichten einer ROS-Umgebung und die Verwendung von YOLO zur Verarbeitung von Sensordaten. Installiere zunächst die erforderlichen Abhängigkeiten wie
ros_numpyund Ultralytics YOLO:pip install ros_numpy ultralyticsErstelle anschließend einen ROS-Knoten und abonniere ein Bildtopic, um die eingehenden Daten für die Objekterkennung zu verarbeiten. Hier ist ein minimales Beispiel:
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()ROS-Topics ermöglichen die Kommunikation zwischen Knoten in einem ROS-Netzwerk mithilfe eines Publish-Subscribe-Modells. Ein Topic ist ein benannter Kanal, über den Knoten asynchron Nachrichten senden und empfangen. Im Kontext von Ultralytics YOLO kannst du einen Knoten ein Bildtopic abonnieren lassen, die Bilder mit YOLO für Aufgaben wie Erkennung oder Segmentierung verarbeiten und die Ergebnisse in neuen Topics veröffentlichen.
Abonniere beispielsweise ein Kameratopic und verarbeite das eingehende Bild zur Erkennung:
rospy.Subscriber("/camera/color/image_raw", Image, callback)Tiefenbilder in ROS, dargestellt durch
sensor_msgs/Image, liefern die Entfernung von Objekten zur Kamera und sind für Aufgaben wie Hindernisvermeidung, 3D-Kartierung und Lokalisierung entscheidend. Durch die Nutzung von Tiefeninformationen zusammen mit RGB-Bildern können Roboter ihre 3D-Umgebung besser verstehen.Mit YOLO kannst du Segmentierungsmasken aus RGB-Bildern extrahieren und auf Tiefenbilder anwenden, um präzise 3D-Informationen zu Objekten zu erhalten. Dadurch verbessert sich die Fähigkeit des Roboters, sich in seiner Umgebung zu bewegen und mit ihr zu interagieren.
So visualisierst du 3D-Punktwolken in ROS mit YOLO:
- Wandle
sensor_msgs/PointCloud2-Nachrichten in NumPy-Arrays um. - Verwende YOLO, um RGB-Bilder zu segmentieren.
- Wende die Segmentierungsmaske auf die Punktwolke an.
Hier ist ein Beispiel mit Open3D zur Visualisierung:
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])Dieser Ansatz ermöglicht eine 3D-Visualisierung segmentierter Objekte, die für Aufgaben wie Navigation und Manipulation in Robotikanwendungen nützlich ist.
- Wandle