YOLO Vision 2026:

ROS (Robot Operating System) Kurzanleitung#

Dieser Leitfaden zeigt dir, wie du Ultralytics YOLO in ROS1 (rospy) oder ROS2 (rclpy) integrierst, um Echtzeit-Objekterkennung und -segmentierung auf RGB-Bildern, Tiefenbildern und Punktwolken durchzuführen.

Springe zur Einrichtung von YOLO mit ROS und arbeite dann mit RGB-Bildern, Tiefenbildern oder Punktwolken.

Was ist ROS?#

Das Robot Operating System (ROS) ist ein Open-Source-Framework, das in der Robotikforschung und -industrie weit verbreitet ist. ROS bietet eine Sammlung von Bibliotheken und Tools, die Entwicklern helfen, Roboteranwendungen zu erstellen. ROS ist so konzipiert, dass es mit verschiedenen Roboterplattformen zusammenarbeitet, was es zu einem flexiblen und leistungsstarken Werkzeug für Robotik-Entwickler macht. Für eine kurze Einführung kannst du dir das dreiminütige Video ROS Introduction von Open Robotics ansehen.

Hauptmerkmale von ROS#

  1. Modulare Architektur: ROS verfügt über eine modulare Architektur, die es Entwicklern ermöglicht, komplexe Systeme durch die Kombination kleinerer, wiederverwendbarer Komponenten, sogenannter Nodes, aufzubauen. Jeder Node erfüllt in der Regel eine spezifische Funktion, und Nodes kommunizieren über Nachrichten auf Topics oder Services miteinander.

  2. Kommunikations-Middleware: ROS bietet eine robuste Kommunikationsinfrastruktur, die Inter-Prozess-Kommunikation und verteiltes Rechnen unterstützt. Dies wird durch ein Publish-Subscribe-Modell für Datenströme (Topics) und ein Request-Reply-Modell für Service-Aufrufe erreicht.

  3. Hardware-Abstraktion: ROS bietet eine Abstraktionsschicht über der Hardware, wodurch Entwickler geräteunabhängigen Code schreiben können. Dies erlaubt es, denselben Code mit unterschiedlichen Hardware-Setups zu verwenden, was die Integration und Experimente erleichtert.

  4. Tools und Dienstprogramme: ROS enthält eine reichhaltige Auswahl an Tools und Dienstprogrammen für Visualisierung, Debugging und Simulation. Zum Beispiel wird RViz zur Visualisierung von Sensordaten und Roboterzustandsinformationen verwendet, während Gazebo eine leistungsstarke Simulationsumgebung für das Testen von Algorithmen und Roboterdesigns bereitstellt.

  5. Umfangreiches Ökosystem: Das ROS-Ökosystem ist riesig und wächst stetig, mit zahlreichen verfügbaren Paketen für verschiedene Roboteranwendungen, einschließlich Navigation, Manipulation, Wahrnehmung und mehr. Die Community trägt aktiv zur Entwicklung und Wartung dieser Pakete bei.

Entwicklung der ROS-Versionen

Seit seiner Entwicklung im Jahr 2007 hat sich ROS durch mehrere Versionen weiterentwickelt, aufgeteilt in ROS 1 und ROS 2. Die folgenden bestehenden Beispiele verwenden ROS1 Noetic; die kompakten Adapter in Verwendung von ROS2 zeigen die entsprechenden rclpy-Schnittstellen für aktuelle ROS2-Releases.

ROS 1 vs. ROS 2#

Während ROS 1 eine solide Grundlage für die Robotikentwicklung bot, behebt ROS 2 dessen Schwachstellen durch folgende Neuerungen:

  • 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 Multi-Roboter-Systeme und groß angelegte Implementierungen.
  • Plattformübergreifende Unterstützung: Erweiterte Kompatibilität mit verschiedenen Betriebssystemen jenseits von Linux, einschließlich Windows und macOS.
  • Flexible Kommunikation: Verwendung von DDS für eine flexiblere und effizientere Inter-Prozess-Kommunikation.

ROS Nachrichten und Topics#

In ROS erfolgt die Kommunikation zwischen Nodes über Nachrichten und Topics. Eine Nachricht ist eine Datenstruktur, die die zwischen Nodes ausgetauschten Informationen definiert, während ein Topic ein benannter Kanal ist, über den Nachrichten gesendet und empfangen werden. Nodes können Nachrichten an ein Topic senden (publish) oder Nachrichten von einem Topic empfangen (subscribe), was ihnen die Kommunikation untereinander ermöglicht. Dieses Publish-Subscribe-Modell erlaubt eine asynchrone Kommunikation und Entkopplung zwischen den Nodes. Jeder Sensor oder Aktor in einem Robotersystem veröffentlicht Daten in der Regel in einem Topic, die dann von anderen Nodes zur Verarbeitung oder Steuerung verwendet werden können. Für diesen Leitfaden konzentrieren wir uns auf Image-, Depth- und PointCloud-Nachrichten sowie Kamera-Topics.

Einrichtung von Ultralytics YOLO mit ROS#

Die ROS1-Beispiele wurden mit dieser ROS-Umgebung getestet, einem Fork des ROSbot ROS-Repositorys. Die gleiche YOLO- und NumPy-Verarbeitung gilt auch für ROS2; lediglich der Node-Lebenszyklus und die Nachrichtenkonvertierung unterscheiden sich.

Husarion ROSbot 2 PRO autonomous robot platform

Installation der Abhängigkeiten#

Neben der ROS-Umgebung musst du die folgenden Abhängigkeiten installieren:

  • ROS NumPy-Paket: Dies wird für die schnelle Konvertierung zwischen ROS Image-Nachrichten und NumPy-Arrays benötigt.

    pip install ros_numpy
  • Ultralytics-Paket:

    pip install ultralytics

Verwendung von ROS2#

ROS2 ersetzt rospy durch rclpy und die ros_numpy-Bildkonvertierung durch cv_bridge. Der folgende Node ist das vollständige ROS2-Äquivalent zum RGB-Erkennungsablauf unten; instanziiere Modelle einmal und verwende sie in 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 verwende den unten stehenden Code zur Tiefenverarbeitung wieder und ersetze nur die Erfassung und Konvertierung 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")
    # 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 Wolke einmal und verwende dann die NumPy-Segmentierung und 3D-Zuordnung unten 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)

Verwende Ultralytics mit ROS sensor_msgs/Image#

Der Nachrichtentyp sensor_msgs/Image wird in ROS häufig zur Darstellung von Bilddaten verwendet. Er enthält Felder für Kodierung, Höhe, Breite und Pixeldaten und eignet sich daher für die Übertragung von Bildern, die von Kameras oder anderen Sensoren aufgenommen wurden. Bild-Nachrichten werden in Roboteranwendungen häufig für Aufgaben wie visuelle Wahrnehmung, Objekterkennung und Navigation verwendet.

Detection and Segmentation in ROS Gazebo

Schritt-für-Schritt-Anleitung zur Bildnutzung#

Das folgende Code-Snippet zeigt, wie du das Ultralytics YOLO-Paket mit ROS verwendest. In diesem Beispiel abonnieren wir ein Kamera-Topic, verarbeiten das eingehende Bild mit YOLO und veröffentlichen die erkannten Objekte in neuen Topics zur Erkennung und Segmentierung.

Importiere zunächst die erforderlichen Bibliotheken und instanziiere zwei Modelle: eines für die Segmentierung und eines für die Erkennung. Initialisiere einen ROS-Node (mit dem Namen ultralytics), um die Kommunikation mit dem ROS-Master zu ermöglichen. Um eine stabile Verbindung zu gewährleisten, fügen wir eine kurze Pause ein, die dem Node ausreichend Zeit gibt, die Verbindung vor dem Fortfahren aufzubauen.

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. Diese Topics werden verwendet, um die annotierten Bilder zu veröffentlichen, sodass sie für die weitere Verarbeitung zugänglich sind. Die Kommunikation zwischen den Nodes wird über sensor_msgs/Image-Nachrichten erleichtert.

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 auf Nachrichten im Topic /camera/color/image_raw lauscht und für jede neue Nachricht eine Callback-Funktion aufruft. Diese Callback-Funktion empfängt Nachrichten vom Typ sensor_msgs/Image, konvertiert sie mithilfe von 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 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()
Debugging

Das Debugging von ROS (Robot Operating System)-Nodes kann aufgrund der verteilten Natur des Systems eine Herausforderung sein. Mehrere Tools können bei diesem Prozess helfen:

  1. rostopic echo <TOPIC-NAME> : Mit diesem Befehl kannst du Nachrichten anzeigen, die in einem bestimmten Topic veröffentlicht wurden, was dir hilft, den Datenfluss zu inspizieren.
  2. rostopic list: Verwende diesen Befehl, um alle verfügbaren Topics im ROS-System aufzulisten, sodass du einen Überblick über die aktiven Datenströme erhältst.
  3. rqt_graph: Dieses Visualisierungstool zeigt den Kommunikationsgraphen zwischen den Nodes an und gibt Aufschluss darüber, wie Nodes miteinander verknüpft sind und wie sie interagieren.
  4. Für komplexere Visualisierungen, wie z. B. 3D-Darstellungen, kannst du RViz verwenden. RViz (ROS Visualization) ist ein leistungsstarkes 3D-Visualisierungstool 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), Roboterungsmodellzustände und verschiedene andere Arten von Informationen anzeigen, was das Debugging und das Verständnis des Verhaltens deines Robotersystems erleichtert.

Erkannte Klassen mit std_msgs/String veröffentlichen#

Standard-ROS-Nachrichten umfassen auch std_msgs/String-Nachrichten. In vielen Anwendungen ist es nicht notwendig, das gesamte annotierte Bild erneut zu veröffentlichen; stattdessen werden nur die Klassen benötigt, die im Sichtfeld des Roboters vorhanden sind. 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 leichtgewichtiger und liefern wesentliche Informationen, wodurch sie für verschiedene Anwendungen wertvoll sind.

Anwendungsbeispiel#

Betrachte einen Lagerroboter, der mit einer Kamera und einem Erkennungsmodell ausgestattet ist. Anstatt große annotierte Bilder über das Netzwerk zu senden, kann der Roboter eine Liste der erkannten Klassen als std_msgs/String-Nachrichten veröffentlichen. Wenn der Roboter beispielsweise Objekte wie „box“, „pallet“ und „forklift“ erkennt, veröffentlicht er diese Klassen im Topic /ultralytics/detection/classes. Diese Informationen können dann von einem zentralen Überwachungssystem verwendet werden, um den Bestand in Echtzeit zu verfolgen, die Pfadplanung des Roboters zur Vermeidung von Hindernissen zu optimieren oder spezifische Aktionen auszulösen, wie z. B. das Aufheben einer erkannten Box. Dieser Ansatz reduziert die für die Kommunikation erforderliche Bandbreite und konzentriert sich auf die Übertragung kritischer Daten.

Schritt-für-Schritt-Anleitung zur String-Nutzung#

Dieses Beispiel zeigt, wie du das Ultralytics YOLO-Paket mit ROS verwendest. In diesem Beispiel abonnieren wir ein Kamera-Topic, verarbeiten das eingehende Bild mit YOLO und veröffentlichen die erkannten Objekte im neuen Topic /ultralytics/detection/classes unter Verwendung von std_msgs/String-Nachrichten. Das Paket ros_numpy wird verwendet, um die ROS Image-Nachricht zur 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()

Verwendung von Ultralytics mit ROS-Tiefenbildern#

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-Mapping und Lokalisierung von entscheidender Bedeutung.

Ein Tiefenbild ist ein Bild, bei dem jeder Pixel den Abstand von der Kamera zu einem Objekt darstellt. Im Gegensatz zu RGB-Bildern, die Farbe erfassen, erfassen Tiefenbilder räumliche Informationen, was es Robotern ermöglicht, die 3D-Struktur ihrer Umgebung wahrzunehmen.

Erhalten von Tiefenbildern

Tiefenbilder können mit verschiedenen Sensoren gewonnen werden:

  1. Stereokameras: Verwenden zwei Kameras, um die Tiefe basierend auf der Bilddisparität zu berechnen.
  2. Time-of-Flight-Kameras (ToF): Messen die Zeit, die das Licht benötigt, um von einem Objekt zurückzukehren.
  3. Strukturierte Lichtsensoren: Projizieren ein Muster und messen dessen Verformung auf Oberflächen.

Verwendung von YOLO mit Tiefenbildern#

In ROS werden Tiefenbilder durch den Nachrichtentyp sensor_msgs/Image dargestellt, der Felder für Kodierung, Höhe, Breite und Pixeldaten enthält. Das Kodierungsfeld für Tiefenbilder verwendet häufig ein Format wie „16UC1“, was einen 16-Bit-Ganzzahlenwert ohne Vorzeichen pro Pixel angibt, wobei jeder Wert die Entfernung zum Objekt darstellt. Tiefenbilder werden häufig in Verbindung mit RGB-Bildern verwendet, um eine umfassendere Sicht auf die Umgebung zu ermöglichen.

Mit YOLO ist es möglich, Informationen aus RGB- und Tiefenbildern zu extrahieren und zu kombinieren. Zum Beispiel kann YOLO Objekte in einem RGB-Bild erkennen, und diese Erkennung kann genutzt werden, um entsprechende Regionen im Tiefenbild zu lokalisieren. Dies ermöglicht die Extraktion präziser Tiefeninformationen für erkannte Objekte, was die Fähigkeit des Roboters verbessert, seine Umgebung in drei Dimensionen zu verstehen.

RGB-D-Kameras

Bei der Arbeit mit Tiefenbildern ist es wichtig sicherzustellen, dass die RGB- und Tiefenbilder korrekt aufeinander abgestimmt sind. RGB-D-Kameras, wie die Intel RealSense-Serie, liefern synchronisierte RGB- und Tiefenbilder, was die Kombination von Informationen aus beiden Quellen erleichtert. Wenn separate RGB- und Tiefenkameras verwendet werden, ist es entscheidend, diese zu kalibrieren, um eine genaue Ausrichtung sicherzustellen.

Schritt-für-Schritt-Anleitung zur Tiefennutzung#

In diesem Beispiel verwenden wir YOLO, um ein Bild zu segmentieren, und wenden die extrahierte Maske an, um das Objekt im Tiefenbild zu segmentieren. Dies ermöglicht uns, den Abstand jedes Pixels des interessierenden Objekts vom Fokuszentrum der Kamera zu bestimmen. Durch den Erhalt dieser Abstandsdaten können wir den Abstand zwischen der Kamera und dem spezifischen Objekt in der Szene berechnen. Beginne mit dem Importieren der notwendigen Bibliotheken, dem Erstellen eines ROS-Nodes und der Instanziierung eines Segmentierungsmodells sowie eines ROS-Topics.

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 Tiefenbild- und RGB-Bildnachrichten, 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 die durchschnittliche Entfernung des Objekts zur Kamera mithilfe des Tiefenbildes. Die meisten Sensoren haben eine maximale Entfernung, die als Clip-Entfernung bezeichnet wird und ab der Werte als inf (np.inf) dargestellt werden. Vor der Verarbeitung ist es wichtig, diese Nullwerte herauszufiltern und ihnen einen Wert von 0 zuzuweisen. Schließlich veröffentlicht sie 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()

Verwende Ultralytics mit ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

Der Nachrichtentyp sensor_msgs/PointCloud2 ist eine Datenstruktur, die in ROS verwendet wird, um 3D-Punktwolken-Daten darzustellen. Dieser Nachrichtentyp ist für Roboteranwendungen unverzichtbar und ermöglicht Aufgaben wie 3D-Mapping, Objekterkennung und Lokalisierung.

Eine Punktwolke ist eine Sammlung von Datenpunkten, die in einem dreidimensionalen Koordinatensystem definiert sind. Diese Datenpunkte repräsentieren die äußere Oberfläche eines Objekts oder einer Szene, die über 3D-Scantechnologien erfasst wurde. Jeder Punkt in der Wolke verfügt über X-, Y- und Z-Koordinaten, die seiner Position im Raum entsprechen, und kann zusätzlich Informationen wie Farbe und Intensität enthalten.

Referenzrahmen

Bei der Arbeit mit sensor_msgs/PointCloud2 ist es wichtig, den Referenzrahmen des Sensors zu berücksichtigen, von dem die Punktwolken-Daten erfasst wurden. Die Punktwolke wird zunächst im Referenzrahmen des Sensors erfasst. Du kannst diesen Referenzrahmen ermitteln, indem du auf das Topic /tf_static lauschst. Abhängig von deinen spezifischen Anwendungsanforderungen musst du die Punktwolke jedoch möglicherweise in einen anderen Referenzrahmen konvertieren. Diese Transformation kann mit dem Paket tf2_ros erreicht werden, das Werkzeuge zur Verwaltung von Koordinatenrahmen und zum Transformieren von Daten zwischen ihnen bereitstellt.

Erhalten von Punktwolken

Punktwolken können mit verschiedenen Sensoren gewonnen werden:

  1. LIDAR (Light Detection and Ranging): Verwendet Laserpulse, um Entfernungen zu Objekten zu messen und hoch-präzise 3D-Karten zu erstellen.
  2. Tiefenkameras: Erfassen Tiefeninformationen für jeden Pixel, was eine 3D-Rekonstruktion der Szene ermöglicht.
  3. Stereokameras: Nutzen zwei oder mehr Kameras, um Tiefeninformationen durch Triangulation zu erhalten.
  4. Strukturlichtscanner: Projizieren ein bekanntes Muster auf eine Oberfläche und messen die Verformung, um die Tiefe zu berechnen.

Verwendung von YOLO mit Punktwolken#

Um YOLO mit Nachrichten des Typs sensor_msgs/PointCloud2 zu integrieren, können wir eine Methode anwenden, die der für Tiefenkarten verwendeten ähnelt. Durch die Nutzung der in der Punktwolke eingebetteten Farbinformationen können wir ein 2D-Bild extrahieren, eine Segmentierung dieses Bildes mit YOLO durchführen und dann die resultierende Maske auf die dreidimensionalen Punkte anwenden, um das 3D-Objekt von Interesse zu isolieren.

Für die Handhabung von Punktwolken empfehlen wir die Verwendung von Open3D (pip install open3d), einer benutzerfreundlichen Python-Bibliothek. Open3D bietet robuste Tools zur Verwaltung von Punktwolkendatenstrukturen, zu deren Visualisierung und zur nahtlosen Ausführung komplexer Operationen. Diese Bibliothek kann den Prozess erheblich vereinfachen und unsere Fähigkeit verbessern, Punktwolken in Verbindung mit der YOLO-basierten Segmentierung zu manipulieren und zu analysieren.

Schritt-für-Schritt-Anleitung zur Punktwolken-Nutzung#

Importiere die notwendigen 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 sensor_msgs/PointCloud2-Nachricht in zwei NumPy-Arrays umwandelt. Die sensor_msgs/PointCloud2-Nachrichten enthalten n-Punkte basierend auf der width und height des erfassten Bildes. Beispielsweise hat ein 480 x 640-Bild 307,200-Punkte. Jeder Punkt enthält drei räumliche Koordinaten (xyz) und die entsprechende Farbe im Format RGB. Diese können als zwei getrennte Informationskanäle betrachtet werden.

Die Funktion gibt die xyz-Koordinaten und RGB-Werte im Format der ursprünglichen Kameraauflösung (width x height) zurück. Die meisten Sensoren haben eine maximale Entfernung, die als Clip-Entfernung bezeichnet wird und ab der Werte als inf (np.inf) dargestellt werden. Vor der Verarbeitung ist es wichtig, diese Nullwerte herauszufiltern und ihnen einen Wert von 0 zuzuweisen.

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 konvertiere die sensor_msgs/PointCloud2-Nachricht in NumPy-Arrays, die die XYZ-Koordinaten und RGB-Werte enthalten (unter Verwendung der Funktion pointcloud2_to_array). 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.

Das Verarbeiten der Maske ist unkompliziert, da sie aus binären Werten besteht, wobei 1 das Vorhandensein des Objekts und 0 das Fehlen anzeigt. Um die Maske anzuwenden, multipliziere einfach die ursprünglichen Kanäle mit der Maske. Dieser Vorgang isoliert effektiv das Objekt von Interesse innerhalb des Bildes. Erstelle abschließend ein Open3D-Punktwolkenobjekt und visualisiere das segmentierte Objekt im 3D-Raum mit zugehörigen Farben.

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

Point Cloud Segmentation with Ultralytics

Fazit#

Mit der in ROS integrierten Ultralytics YOLO-Lösung kann dein Roboter Objekterkennung und -segmentierung über RGB-Bilder, Tiefenbilder und Punktwolken hinweg ausführen und rohe Sensorströme in umsetzbare Wahrnehmung umwandeln. Erkunde von hier aus den Vorhersagemodus für weitere Inferenzoptionen oder befolge die Schritte eines Computer-Vision-Projekts, um deine Roboteranwendung vom Prototyp zur Produktion zu führen.

FAQ#

  • Das Robot Operating System (ROS) ist ein Open-Source-Framework, das in der Robotik häufig verwendet wird, um Entwicklern bei der Erstellung robustes Roboteranwendungen zu helfen. Es stellt eine Sammlung von Bibliotheken und Tools zum Erstellen von und Schnittstellen zu Robotersystemen bereit, was eine einfachere Entwicklung komplexer Anwendungen ermöglicht. ROS unterstützt die Kommunikation zwischen Nodes mithilfe von Nachrichten über Topics oder Services.

  • Die Integration von Ultralytics YOLO in ROS umfasst das Einrichten einer ROS-Umgebung und die Verwendung von YOLO zur Verarbeitung von Sensordaten. Beginne mit der Installation der erforderlichen Abhängigkeiten wie ros_numpy und Ultralytics YOLO:

    pip install ros_numpy ultralytics

    Erstelle als Nächstes einen ROS-Node und abonniere ein Bild-Topic, 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 erleichtern die Kommunikation zwischen Nodes in einem ROS-Netzwerk mithilfe eines Publish-Subscribe-Modells. Ein Topic ist ein benannter Kanal, den Nodes verwenden, um Nachrichten asynchron zu senden und zu empfangen. Im Kontext von Ultralytics YOLO kannst du einen Node ein Bild-Topic abonnieren lassen, die Bilder mit YOLO für Aufgaben wie Erkennung oder Segmentierung verarbeiten und Ergebnisse in neuen Topics veröffentlichen.

    Abonniere zum Beispiel ein Kamera-Topic und verarbeite das eingehende Bild für die Erkennung:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • Tiefenbilder in ROS, dargestellt durch sensor_msgs/Image, liefern die Entfernung von Objekten zur Kamera, was für Aufgaben wie Hindernisvermeidung, 3D-Mapping und Lokalisierung von entscheidender Bedeutung ist. 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 diese Masken auf Tiefenbilder anwenden, um präzise 3D-Objektinformationen zu erhalten, was die Fähigkeit des Roboters verbessert, zu navigieren und mit seiner Umgebung zu interagieren.

  • Zur Visualisierung von 3D-Punktwolken in ROS mit YOLO:

    1. Konvertiere sensor_msgs/PointCloud2-Nachrichten in NumPy-Arrays.
    2. Verwende YOLO, um RGB-Bilder zu segmentieren.
    3. 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 bietet eine 3D-Visualisierung von segmentierten Objekten, die für Aufgaben wie Navigation und Manipulation in Roboteranwendungen nützlich ist.

Kommentare