Ultralytics YOLO27:
Get Started

ROS (Robot Operating System): Schnellstartanleitung#

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 zur Einrichtung von YOLO mit ROS und arbeite anschließend mit RGB-Bildern, Tiefenbildern oder Punktwolken.

Was ist ROS?#

Das Robot Operating System (ROS) ist ein quelloffenes Framework, das in der Robotikforschung und -industrie weit verbreitet ist. ROS bietet eine Sammlung von Bibliotheken und Werkzeugen, die Entwickler beim Erstellen von Roboteranwendungen unterstützen. ROS ist für die Verwendung mit verschiedenen Roboterplattformen ausgelegt und damit ein flexibles und leistungsstarkes Werkzeug für die Robotik. Eine kurze Einführung erhältst du im dreiminütigen Video Einführung in ROS von Open Robotics.

Wichtige Funktionen von ROS#

  1. Modulare Architektur: ROS hat eine modulare Architektur, mit der Entwickler komplexe Systeme aus kleineren, wiederverwendbaren Komponenten, den sogenannten Knoten, aufbauen können. Jeder Knoten erfüllt in der Regel eine bestimmte Funktion. Die Knoten kommunizieren über Nachrichten auf Topics oder über Dienste miteinander.

  2. Kommunikations-Middleware: ROS bietet eine robuste Kommunikationsinfrastruktur, die Kommunikation zwischen Prozessen und verteiltes Rechnen unterstützt. Dies wird durch ein Publish-Subscribe-Modell für Datenströme (Topics) und ein Anfrage-Antwort-Modell für Dienstaufrufe erreicht.

  3. Hardwareabstraktion: ROS bietet eine Abstraktionsschicht für die Hardware, mit der Entwickler geräteunabhängigen Code schreiben können. Dadurch lässt sich derselbe Code mit verschiedenen Hardwarekonfigurationen verwenden, was die Integration und das Experimentieren erleichtert.

  4. Werkzeuge und Hilfsprogramme: ROS umfasst zahlreiche Werkzeuge und Hilfsprogramme zur Visualisierung, Fehlersuche und Simulation. RViz dient beispielsweise zur Visualisierung von Sensordaten und Zustandsinformationen des Roboters, während Gazebo eine leistungsstarke Simulationsumgebung zum Testen von Algorithmen und Roboterentwürfen bietet.

  5. Umfangreiches Ökosystem: Das ROS-Ökosystem ist groß und wächst stetig. Es umfasst zahlreiche Pakete für verschiedene Anwendungen in der Robotik, 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 weiterentwickelt und umfasst mehrere Versionen, aufgeteilt in ROS 1 und ROS 2. Die folgenden Beispiele verwenden ROS1 Noetic. Die kompakten Adapter unter ROS2 verwenden zeigen die entsprechenden Schnittstellen von rclpy für aktuelle ROS2-Versionen.

ROS 1 im Vergleich zu ROS 2#

Während ROS 1 eine solide Grundlage für die Robotikentwicklung bot, behebt ROS 2 seine Schwächen durch:

  • Echtzeitfähigkeit: 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 Mehrrobotersysteme und groß angelegte Implementierungen.
  • Plattformübergreifende Unterstützung: Erweiterte Kompatibilität mit verschiedenen Betriebssystemen jenseits von Linux, darunter Windows und macOS.
  • Flexible Kommunikation: Einsatz von DDS für eine flexiblere und effizientere Kommunikation zwischen Prozessen.

ROS-Nachrichten und -Topics#

In ROS wird die Kommunikation zwischen Knoten über Nachrichten und Topics ermöglicht. Eine Nachricht ist eine Datenstruktur, die festlegt, welche Informationen zwischen Knoten ausgetauscht werden, während ein Topic ein benannter Kanal ist, über den Nachrichten gesendet und empfangen werden. Knoten können Nachrichten zu einem Topic veröffentlichen oder Nachrichten von einem Topic abonnieren und so miteinander kommunizieren. Dieses Publish-Subscribe-Modell ermöglicht asynchrone Kommunikation und eine Entkopplung der Knoten. Jeder Sensor oder Aktor in einem Robotersystem veröffentlicht typischerweise Daten zu einem Topic, die dann 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 ROSbot-ROS-Repositorys. Die Verarbeitung mit YOLO und NumPy funktioniert in ROS2 genauso; nur der Lebenszyklus der Knoten und die Nachrichtenkonvertierung unterscheiden sich.

Husarion ROSbot 2 PRO autonomous robot platform

Abhängigkeiten installieren#

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 und die Bildkonvertierung mit ros_numpy durch cv_bridge. Der folgende Knoten ist das vollständige ROS2-Pendant zum unten gezeigten RGB-Erkennungsablauf; instanziiere die Modelle einmal und verwende sie in den 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()

Verwende für Tiefenbilder den unten stehenden Code zur Tiefenverarbeitung erneut und ersetze nur das Abrufen und Konvertieren der Nachrichten:

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")
    # Wende die NumPy-Maske und die Entfernungsberechnung aus dem Tiefenbeispiel unten an.

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 3D-Zuordnung unten erneut:

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 verwendet, um Bilddaten darzustellen. Er enthält Felder für Kodierung, Höhe, Breite und Pixeldaten und eignet sich dadurch zur Übertragung von Bildern, die mit Kameras oder anderen Sensoren aufgenommen wurden. Bildnachrichten werden in der Robotik häufig für Aufgaben wie visuelle Wahrnehmung, Objekterkennung und Navigation eingesetzt.

Detection and Segmentation in ROS Gazebo

Schrittweise Verwendung von Image-Nachrichten#

Der folgende Codeausschnitt 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 zuerst die benötigten Bibliotheken und instanziiere zwei Modelle: eines für die Segmentierung und eines für die Erkennung. Initialisiere einen ROS-Knoten mit dem Namen ultralytics, damit die Kommunikation mit dem ROS-Master möglich ist. Um eine stabile Verbindung sicherzustellen, warten wir kurz, damit der Knoten genügend Zeit hat, die Verbindung herzustellen, bevor es weitergeht.

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 die Erkennung und eines für die Segmentierung. Über diese Topics werden die annotierten Bilder veröffentlicht und für die weitere Verarbeitung bereitgestellt. 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 abschließend 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, wandelt sie mithilfe von ros_numpy in ein NumPy-Array um, verarbeitet die Bilder mit den zuvor instanziierten YOLO-Modellen, annotiert die Bilder und veröffentlicht sie anschließend in den entsprechenden Topics: /ultralytics/detection/image für die Erkennung und /ultralytics/segmentation/image für die 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()
Fehlerbehebung

Das Debuggen von ROS-Knoten (Robotersystem) kann aufgrund der verteilten Architektur des Systems eine Herausforderung sein. Verschiedene Werkzeuge können dabei helfen:

  1. rostopic echo <TOPIC-NAME> : Mit diesem Befehl kannst du die auf einem bestimmten Topic veröffentlichten Nachrichten anzeigen und so den Datenfluss untersuchen.
  2. rostopic list: Mit diesem Befehl kannst du alle verfügbaren Topics im ROS-System auflisten und dir einen Überblick über die aktiven Datenströme verschaffen.
  3. rqt_graph: Dieses Visualisierungswerkzeug zeigt den Kommunikationsgraphen zwischen den Knoten an und gibt Aufschluss darüber, wie die Knoten verbunden sind und miteinander interagieren.
  4. Für komplexere Visualisierungen, beispielsweise 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 darstellen. Mit RViz kannst du Sensordaten (z. B. sensor_msgs/Image), Zustände des Robotermodells und verschiedene andere Informationen anzeigen. So lassen sich das Verhalten deines Robotersystems leichter debuggen und nachvollziehen.

Erkannte Klassen mit std_msgs/String veröffentlichen#

Zu den Standardnachrichten in ROS gehören auch std_msgs/String-Nachrichten. In vielen Anwendungen ist es nicht nötig, das gesamte annotierte Bild erneut zu veröffentlichen; es reicht aus, nur die Klassen zu übermitteln, die sich im Sichtfeld des Roboters befinden. Das folgende Beispiel zeigt, wie du std_msgs/String-Nachrichten verwendest, um die erkannten Klassen erneut im Topic /ultralytics/detection/classes zu veröffentlichen. Diese Nachrichten sind kompakter und enthalten wichtige Informationen, wodurch sie für viele Anwendungen nützlich sind.

Anwendungsbeispiel#

Stell dir einen Lagerroboter mit einer Kamera und einem Objekterkennungsmodell vor. Anstatt große annotierte Bilder über das Netzwerk zu senden, kann der Roboter eine Liste erkannter Klassen als std_msgs/String-Nachrichten veröffentlichen. Erkennt der Roboter beispielsweise Objekte wie „Kiste“, „Palette“ und „Gabelstapler“, veröffentlicht er diese Klassen im Topic /ultralytics/detection/classes. Ein zentrales Überwachungssystem kann diese Informationen dann verwenden, um den Lagerbestand 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 verringert die erforderliche Bandbreite und konzentriert die Übertragung auf wesentliche Daten.

Schrittweise Verwendung von String-Nachrichten#

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 mithilfe von std_msgs/String-Nachrichten im neuen Topic /ultralytics/detection/classes. Das Paket ros_numpy konvertiert die ROS-Image-Nachricht in ein NumPy-Array, das mit YOLO verarbeitet werden kann.

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#

Neben RGB-Bildern unterstützt ROS auch Tiefenbilder, die Informationen über den Abstand von Objekten zur Kamera enthalten. Tiefenbilder sind für Anwendungen in der Robotik wie Hindernisvermeidung, 3D-Kartierung und Lokalisierung von entscheidender Bedeutung.

Ein Tiefenbild ist ein Bild, bei dem jedes Pixel den Abstand zwischen der Kamera und einem Objekt angibt. Anders als RGB-Bilder, die Farben erfassen, enthalten Tiefenbilder räumliche Informationen. Dadurch können Roboter die 3D-Struktur ihrer Umgebung wahrnehmen.

Tiefenbilder erfassen

Tiefenbilder lassen sich mit verschiedenen Sensoren erfassen:

  1. Stereokameras: Verwenden zwei Kameras, um die Tiefe anhand der Bilddisparität zu berechnen.
  2. Laufzeitkameras (ToF): Messen, wie lange das Licht benötigt, um von einem Objekt zurückzukehren.
  3. Strukturiertes-Licht-Sensoren: 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 Kodierung, Höhe, Breite und Pixeldaten enthält. Für Tiefenbilder wird häufig eine Kodierung wie „16UC1“ verwendet. Sie gibt an, dass jedes Pixel durch eine 16-Bit-Ganzzahl ohne Vorzeichen dargestellt wird und jeder Wert den Abstand zum Objekt angibt. Tiefenbilder werden häufig zusammen mit RGB-Bildern verwendet, um ein umfassenderes Bild der Umgebung zu erhalten.

Mit YOLO lassen sich Informationen aus RGB- und Tiefenbildern extrahieren und kombinieren. So kann YOLO beispielsweise Objekte in einem RGB-Bild erkennen. Anhand dieser Erkennung lassen sich die entsprechenden Bereiche im Tiefenbild bestimmen. Dadurch können präzise Tiefeninformationen für erkannte Objekte gewonnen werden, sodass der Roboter seine Umgebung besser dreidimensional erfassen kann.

RGB-D-Kameras

Bei der Arbeit mit Tiefenbildern musst du sicherstellen, dass RGB- und Tiefenbilder korrekt aufeinander ausgerichtet sind. RGB-D-Kameras wie die Intel RealSense-Reihe liefern synchronisierte RGB- und Tiefenbilder, sodass sich Informationen aus beiden Quellen leichter kombinieren lassen. Wenn du separate RGB- und Tiefenkameras verwendest, musst du sie unbedingt kalibrieren, damit die Bilder korrekt ausgerichtet sind.

Schrittweise Verwendung von Tiefenbildern#

In diesem Beispiel verwenden wir YOLO, um ein Bild zu segmentieren, und wenden die extrahierte Maske auf das Tiefenbild an, um das Objekt darin zu segmentieren. So können wir den Abstand jedes Pixels des interessierenden Objekts zum optischen Zentrum der Kamera bestimmen. Anhand dieser Entfernungsinformationen lässt sich der Abstand 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 als Nächstes eine Callback-Funktion, die die eingehende Tiefenbildnachricht verarbeitet. Die Funktion wartet auf die Nachrichten mit dem Tiefenbild und dem RGB-Bild, wandelt sie in NumPy-Arrays um und wendet das Segmentierungsmodell auf das RGB-Bild an. Anschließend extrahiert sie für jedes erkannte Objekt die Segmentierungsmaske und berechnet mithilfe des Tiefenbilds den durchschnittlichen Abstand des Objekts zur Kamera. Die meisten Sensoren haben eine maximale Reichweite, die als Clip-Distanz bezeichnet wird. Jenseits davon werden Werte als inf (np.inf) dargestellt. Vor der Verarbeitung müssen diese Nullwerte herausgefiltert und durch 0 ersetzt werden. Abschließend veröffentlicht die Funktion die erkannten Objekte samt ihren durchschnittlichen Abständen 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, 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()
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, 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()

Ultralytics mit ROS sensor_msgs/PointCloud2 verwenden#

Detection and Segmentation in ROS Gazebo

Der Nachrichtentyp sensor_msgs/PointCloud2 ist eine Nachricht, die in ROS zur Darstellung von 3D-Punktwolken verwendet wird. Dieser Nachrichtentyp ist für Anwendungen in der Robotik wesentlich 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 mit 3D-Scanverfahren erfasst wurde. Jeder Punkt der Punktwolke hat die Koordinaten X, Y und Z, die seine Position im Raum angeben. Außerdem kann jeder Punkt weitere Informationen wie Farbe und Intensität enthalten.

Bezugssystem

Bei der Arbeit mit sensor_msgs/PointCloud2 musst du das Bezugssystem des Sensors berücksichtigen, mit dem die Punktwolke erfasst wurde. Zunächst wird die Punktwolke im Bezugssystem des Sensors aufgenommen. Du kannst dieses Bezugssystem ermitteln, indem du das Topic /tf_static abonnierst. Je nach den Anforderungen deiner Anwendung musst du die Punktwolke jedoch möglicherweise in ein anderes Bezugssystem umwandeln. Diese Transformation lässt sich mit dem Paket tf2_ros durchführen, das Werkzeuge zum Verwalten von Koordinatenrahmen und zum Transformieren von Daten zwischen ihnen bereitstellt.

Punktwolken erfassen

Punktwolken lassen sich mit verschiedenen Sensoren erfassen:

  1. LIDAR (Lichterkennung und Entfernungsmessung): Verwendet Laserimpulse, um Abstände zu Objekten zu messen und hochpräzise 3D-Karten zu erstellen.
  2. Tiefenkameras: Erfassen Tiefeninformationen für jedes Pixel und ermöglichen so die 3D-Rekonstruktion der Szene.
  3. Stereokameras: Nutzen zwei oder mehr Kameras, um durch Triangulation Tiefeninformationen zu gewinnen.
  4. Strukturiert-Licht-Scanner: Projizieren ein bekanntes Muster auf eine Oberfläche und messen dessen Verformung, um die Tiefe zu berechnen.

YOLO mit Punktwolken verwenden#

Um YOLO mit Nachrichten des Typs sensor_msgs/PointCloud2 zu kombinieren, können wir ähnlich wie bei Tiefenkarten vorgehen. Mithilfe der in der Punktwolke enthaltenen Farbinformationen können wir ein 2D-Bild extrahieren, dieses Bild mit YOLO segmentieren und anschließend die resultierende Maske 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 leistungsfähige Werkzeuge zur Verwaltung und Visualisierung von Punktwolken sowie zur nahtlosen Ausführung komplexer Operationen bereit. Diese Bibliothek kann die Verarbeitung erheblich vereinfachen und unsere Möglichkeiten verbessern, Punktwolken zusammen mit der YOLO-basierten Segmentierung zu bearbeiten und zu analysieren.

Schrittweise Verarbeitung 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 eine Funktion pointcloud2_to_array, die eine Nachricht vom Typ sensor_msgs/PointCloud2 in zwei NumPy-Arrays umwandelt. Die Nachrichten sensor_msgs/PointCloud2 enthalten n Punkte, die auf width und height des aufgenommenen Bildes basieren. Ein Bild mit 480 x 640 hat beispielsweise 307,200 Punkte. Jeder Punkt umfasst drei räumliche Koordinaten (xyz) und die zugehörige Farbe im Format RGB. Diese lassen sich als zwei separate Informationskanäle betrachten.

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 Reichweite, die als Clip-Distanz bezeichnet wird. Jenseits davon werden Werte als inf (np.inf) dargestellt. 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, rgb

Abonniere als Nächstes das Topic /camera/depth/points, um die Punktwolken-Nachricht zu empfangen, und wandle die Nachricht sensor_msgs/PointCloud2 mithilfe der Funktion pointcloud2_to_array in NumPy-Arrays mit den XYZ-Koordinaten und RGB-Werten um. 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 einfach verarbeiten, da sie aus binären Werten besteht: 1 kennzeichnet das Vorhandensein des Objekts und 0 dessen Abwesenheit. Um die Maske anzuwenden, multipliziere einfach die ursprünglichen Kanäle mit der Maske. Dadurch wird das gewünschte Objekt im Bild effektiv isoliert. Erstelle abschließend 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, retina_masks=True)  # Masken in der ursprünglichen Bildauflösung

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, retina_masks=True)  # Masken in der ursprünglichen Bildauflösung

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

Fazit#

Mit Ultralytics YOLO in ROS kann dein Roboter Objekterkennung und Segmentierung auf RGB-Bildern, Tiefenbildern und Punktwolken ausführen und rohe Sensordaten in verwertbare Wahrnehmungsdaten umwandeln. Entdecke als Nächstes den Vorhersagemodus für weitere Inferenzoptionen oder folge den Schritten eines Computer-Vision-Projekts, um deine Robotikanwendung vom Prototyp bis zum produktiven Einsatz zu bringen.

Häufig gestellte Fragen#

  • Das Betriebssystem für Roboter (ROS) ist ein Open-Source-Framework, das in der Robotik häufig verwendet wird, um Entwickler bei der Erstellung robuster Roboteranwendungen zu unterstützen. Es bietet eine Sammlung von Bibliotheken und Werkzeugen zum Aufbau von Robotersystemen und zur Kommunikation mit ihnen und erleichtert so die Entwicklung komplexer Anwendungen. ROS unterstützt die Kommunikation zwischen Knoten über Nachrichten, die über Topics oder Services ausgetauscht werden.

  • Um Ultralytics YOLO mit ROS zu integrieren, musst du eine ROS-Umgebung einrichten und YOLO zur Verarbeitung von Sensordaten verwenden. Installiere zunächst die erforderlichen Abhängigkeiten wie ros_numpy und Ultralytics YOLO:

    pip install ros_numpy ultralytics

    Erstelle als Nächstes einen ROS-Knoten und abonniere ein Bild-Topic, um die eingehenden Daten für die Objekterkennung zu verarbeiten. Hier 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 Zusammenhang mit Ultralytics YOLO kannst du einen Knoten ein Bild-Topic abonnieren lassen, die Bilder mit YOLO für Aufgaben wie Erkennung oder Segmentierung verarbeiten und die Ergebnisse in neuen Topics veröffentlichen.

    Abonniere zum Beispiel 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, geben den Abstand von Objekten zur Kamera an. Das ist entscheidend für Aufgaben wie Hindernisvermeidung, 3D-Kartierung und Lokalisierung. Durch die Kombination von RGB-Bildern mit Tiefeninformationen können Roboter ihre 3D-Umgebung besser erfassen.

    Mit YOLO kannst du Segmentierungsmasken aus RGB-Bildern extrahieren und auf Tiefenbilder anwenden, um präzise 3D-Informationen über Objekte zu erhalten. Dadurch kann sich der Roboter besser in seiner Umgebung bewegen und mit ihr interagieren.

  • So visualisierst du 3D-Punktwolken in ROS mit YOLO:

    1. Wandle sensor_msgs/PointCloud2-Nachrichten in NumPy-Arrays um.
    2. Verwende YOLO, um RGB-Bilder zu segmentieren.
    3. Wende die Segmentierungsmaske auf die Punktwolke an.

    Hier ein Beispiel für die Visualisierung mit Open3D:

    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)  # Masken in der ursprünglichen Bildauflösung
    
    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 die 3D-Visualisierung segmentierter Objekte und eignet sich für Aufgaben wie Navigation und Manipulation in Robotikanwendungen.

Kommentare