Zum Inhalt springen

Isolierung von Segmentierungsobjekten

Nach der Durchführung der Segmentaufgabe ist es manchmal wünschenswert, die isolierten Objekte aus den Schlussfolgerungsergebnissen zu extrahieren. In diesem Leitfaden findest du ein allgemeines Rezept, wie du dies mit dem Ultralytics Predict Mode erreichen kannst.

Beispiel für die Segmentierung isolierter Objekte

Rezeptdurchlauf

  1. Beginne mit den notwendigen Importen

    from pathlib import Path
    
    import cv2
    import numpy as np
    from ultralytics import YOLO
    
    Ultralytics Installiere

    Im Abschnitt Ultralytics Schnellstart-Installation findest du eine kurze Anleitung für die Installation der benötigten Bibliotheken.


  2. Ein Modell laden und ausführen predict() Methode auf eine Quelle.

    from ultralytics import YOLO
    
    # Load a model
    model = YOLO('yolov8n-seg.pt')
    
    # Run inference
    results = model.predict()
    

    Keine Vorhersage-Argumente?

    Wenn du keine Quelle angibst, werden die Beispielbilder aus der Bibliothek verwendet:

    'ultralytics/assets/bus.jpg'
    'ultralytics/assets/zidane.jpg'
    

    Dies ist hilfreich für schnelle Tests mit dem predict() Methode.

    Weitere Informationen über Segmentierungsmodelle findest du auf der Segment Aufgabe Seite. Um mehr zu erfahren über predict() Methode, siehe Modus vorhersagen Abschnitt der Dokumentation.


  3. Iteriere nun über die Ergebnisse und die Konturen. Für Workflows, die ein Bild in einer Datei speichern wollen, muss das Quellbild base-name und die Erkennung class-label zur späteren Verwendung abgerufen werden (optional).

    # (2) Iterate detection results (helpful for multiple images)
    for r in res:
        img = np.copy(r.orig_img)
        img_name = Path(r.path).stem # source image base-name
    
        # Iterate each object contour (multiple detections)
        for ci,c in enumerate(r):
            # (1) Get detection class name
            label = c.names[c.boxes.cls.tolist().pop()]
    
    1. Mehr über die Arbeit mit den Erkennungsergebnissen erfährst du im Abschnitt Boxen für den Vorhersagemodus.
    2. Um mehr zu erfahren über predict() Ergebnisse siehe Arbeiten mit Ergebnissen für den Vorhersagemodus
    For-Schleife

    Bei einem einzelnen Bild wird die erste Schleife nur einmal durchlaufen. Bei einem einzelnen Bild mit nur einer Erkennung wird jede Schleife nur einmal durchlaufen.


  4. Beginne mit der Erstellung einer binären Maske aus dem Quellbild und zeichne dann eine gefüllte Kontur auf die Maske. So kann das Objekt von den anderen Teilen des Bildes isoliert werden. Ein Beispiel aus bus.jpg für eine der erkannten person Klassenobjekte ist rechts abgebildet.

    Binäres Maskenbild

    # Create binary mask
    b_mask = np.zeros(img.shape[:2], np.uint8)
    
    # (1) Extract contour result
    contour = c.masks.xy.pop()
    # (2) Changing the type
    contour = contour.astype(np.int32)
    # (3) Reshaping
    contour = contour.reshape(-1, 1, 2)
    
    
    # Draw contour onto mask
    _ = cv2.drawContours(b_mask,
                        [contour],
                        -1,
                        (255, 255, 255),
                        cv2.FILLED)
    
    1. Für weitere Informationen über c.masks.xy siehe Masken Abschnitt aus dem Vorhersagemodus.

    2. Hier werden die Werte in np.int32 für die Kompatibilität mit drawContours() Funktion von OpenCV.

    3. Die OpenCV drawContours() Funktion erwartet, dass die Konturen eine Form von [N, 1, 2] Abschnitt unten für weitere Details erweitern.

    Erweitern Sie, um zu verstehen, was passiert, wenn Sie die contour variabel.

    • c.masks.xy :: Liefert die Koordinaten der Maskenkonturpunkte im Format (x, y). Weitere Einzelheiten findest du in der Masken Abschnitt aus dem Vorhersagemodus.

    • .pop() :: As masks.xy eine Liste ist, die ein einzelnes Element enthält, wird dieses Element mit der Methode pop() Methode.

    • .astype(np.int32) :: Verwendung von masks.xy wird mit einem Datentyp von float32aber das ist nicht kompatibel mit der OpenCV drawContours() Funktion, also wird der Datentyp in int32 für Kompatibilität.

    • .reshape(-1, 1, 2) :: Formatiert die Daten in die gewünschte Form von [N, 1, 2] wo N ist die Anzahl der Konturpunkte, wobei jeder Punkt durch einen einzelnen Eintrag repräsentiert wird 1und der Eintrag besteht aus 2 Werte. Die -1 bedeutet, dass die Anzahl der Werte entlang dieser Dimension flexibel ist.

    Erweitern Sie für eine Erklärung der drawContours() Konfiguration.

    • Die Verkapselung der contour Variable in eckigen Klammern, [contour]wurde bei den Tests festgestellt, dass sie die gewünschte Konturenmaske effektiv erzeugt.

    • Der Wert -1 angegeben für die drawContours() Parameter weist die Funktion an, alle im Bild vorhandenen Konturen zu zeichnen.

    • Die tuple (255, 255, 255) steht für die Farbe Weiß, die die gewünschte Farbe zum Zeichnen der Kontur in dieser binären Maske ist.

    • Der Zusatz von cv2.FILLED färbt alle von der Konturbegrenzung eingeschlossenen Pixel gleich, in diesem Fall sind alle eingeschlossenen Pixel weiß.

    • Siehe OpenCV Dokumentation auf drawContours() für weitere Informationen.


  5. Als Nächstes gibt es 2 Optionen, wie du mit dem Bild von diesem Punkt aus weitermachen kannst, und eine weitere Option für jede.

    Optionen zur Objektisolierung

    # Create 3-channel mask
    mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR)
    
    # Isolate object with binary mask
    isolated = cv2.bitwise_and(mask3ch, img)
    
    Wie funktioniert das?
    • Zunächst wird die binäre Maske von einem einkanaligen Bild in ein dreikanaliges Bild umgewandelt. Diese Umwandlung ist für den nachfolgenden Schritt notwendig, bei dem die Maske und das Originalbild kombiniert werden. Beide Bilder müssen die gleiche Anzahl von Kanälen haben, damit sie mit dem Überblendvorgang kompatibel sind.

    • Das Originalbild und die Dreikanal-Binärmaske werden mit der OpenCV-Funktion zusammengeführt bitwise_and(). Bei diesem Vorgang bleiben die nur Pixelwerte, die größer als Null sind (> 0) aus beiden Bildern. Da die Pixel der Maske größer als Null sind (> 0) nur Innerhalb der Konturregion bleiben die Pixel des Originalbildes übrig, die sich mit der Kontur überschneiden.

    Mit schwarzen Pixeln isolieren: Unteroptionen

    Bild in voller Größe

    Es sind keine weiteren Schritte erforderlich, wenn du das Bild in voller Größe behältst.

    Beispiel in voller Größe Isoliertes Objektbild Schwarzer Hintergrund
    Beispiel für die Ausgabe in voller Größe

    Ausgeschnittenes Objekt Bild

    Zusätzliche Schritte sind erforderlich, um das Bild so zuzuschneiden, dass es nur die Objektregion enthält.

    Beispiel Bild mit isoliertem Objekt beschneiden Schwarzer Hintergrund

    # (1) Bounding box coordinates
    x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32)
    # Crop image to object region
    iso_crop = isolated[y1:y2, x1:x2]
    

    1. Weitere Informationen zu Bounding-Box-Ergebnissen findest du unter Boxen-Abschnitt im Vorhersagemodus
    Was bewirkt dieser Code?
    • Die c.boxes.xyxy.cpu().numpy() Aufruf ruft die Bounding Boxes als NumPy-Array in der xyxy Format, wobei xmin, ymin, xmax, und ymax stehen für die Koordinaten des Begrenzungsrahmen-Rechtecks. Siehe Kästchen Abschnitt aus dem Vorhersagemodus für weitere Details.

    • Die squeeze() Operation entfernt alle unnötigen Dimensionen aus dem NumPy-Array und stellt sicher, dass es die erwartete Form hat.

    • Umrechnung der Koordinatenwerte mit .astype(np.int32) ändert den Datentyp der Feldkoordinaten von float32 zu int32Dadurch sind sie für das Zuschneiden von Bildern mit Indexscheiben geeignet.

    • Schließlich wird die Bounding-Box-Region mithilfe von Index-Slicing aus dem Bild ausgeschnitten. Die Begrenzungen werden durch den [ymin:ymax, xmin:xmax] Koordinaten des Begrenzungsrahmens der Erkennung.

    # Isolate object with transparent background (when saved as PNG)
    isolated = np.dstack([img, b_mask])
    
    Wie funktioniert das?
    • Die Verwendung der NumPy dstack() (Array-Stapelung entlang der Tiefenachse) in Verbindung mit der erzeugten binären Maske ein Bild mit vier Kanälen. Dadurch werden alle Pixel außerhalb der Objektkontur transparent, wenn das Bild als PNG Datei.

    Mit transparenten Pixeln isolieren: Unteroptionen

    Bild in voller Größe

    Es sind keine weiteren Schritte erforderlich, wenn du das Bild in voller Größe behältst.

    Beispiel in voller Größe Isoliertes Objektbild ohne Hintergrund
    Beispiel Ausgabe in voller Größe + transparenter Hintergrund

    Ausgeschnittenes Objekt Bild

    Zusätzliche Schritte sind erforderlich, um das Bild so zuzuschneiden, dass es nur die Objektregion enthält.

    Beispiel Zuschneiden eines isolierten Objekts Bild ohne Hintergrund

    # (1) Bounding box coordinates
    x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32)
    # Crop image to object region
    iso_crop = isolated[y1:y2, x1:x2]
    

    1. Weitere Informationen zu Bounding-Box-Ergebnissen findest du unter Boxen-Abschnitt im Vorhersagemodus
    Was bewirkt dieser Code?
    • Bei der Verwendung von c.boxes.xyxy.cpu().numpy()werden die Bounding Boxes als NumPy-Array zurückgegeben, indem die xyxy Box-Koordinatenformat, die den Punkten entsprechen xmin, ymin, xmax, ymax für den Begrenzungsrahmen (Rechteck), siehe Kästchen Abschnitt aus dem Vorhersagemodus für weitere Informationen.

    • Hinzufügen von squeeze() sorgt dafür, dass alle überflüssigen Dimensionen aus dem NumPy-Array entfernt werden.

    • Umrechnung der Koordinatenwerte mit .astype(np.int32) ändert den Datentyp der Feldkoordinaten von float32 zu int32 die beim Zuschneiden des Bildes mit Hilfe von Index-Slices kompatibel sein werden.

    • Schließlich wird die Bildregion für die Bounding Box mithilfe von Index-Slicing zugeschnitten, wobei die Grenzen mit der Funktion [ymin:ymax, xmin:xmax] Koordinaten des Begrenzungsrahmens der Erkennung.

    Was ist, wenn ich das ausgeschnittene Objekt einschließlich des Hintergrunds haben möchte?

    Dies ist eine eingebaute Funktion der Bibliothek Ultralytics . Siehe die save_crop Argument für Predict Mode Inference Argumente für Details.


  6. Was als Nächstes zu tun ist, bleibt ganz dir als Entwickler überlassen. Ein einfaches Beispiel für einen möglichen nächsten Schritt (Speichern des Bildes in einer Datei zur späteren Verwendung) wird hier gezeigt.

    • HINWEIS: Dieser Schritt ist optional und kann übersprungen werden, wenn er für deinen speziellen Anwendungsfall nicht erforderlich ist.
    Beispiel Letzter Schritt
    # Save isolated object to file
    _ = cv2.imwrite(f'{img_name}_{label}-{ci}.png', iso_crop)
    
    • In diesem Beispiel wird die img_name ist der Basisname der Quellbilddatei, label ist der erkannte Klassenname, und ci ist der Index der Objekterkennung (im Falle mehrerer Instanzen mit demselben Klassennamen).

Vollständiger Beispielcode

Hier werden alle Schritte aus dem vorherigen Abschnitt in einem einzigen Codeblock zusammengefasst. Bei wiederholter Verwendung wäre es optimal, eine Funktion zu definieren, die einige oder alle Befehle aus dem for-Schleifen, aber das ist eine Übung, die dem Leser überlassen bleibt.

from pathlib import Path

import cv2
import numpy as np
from ultralytics import YOLO

m = YOLO('yolov8n-seg.pt')#(4)!
res = m.predict()#(3)!

# iterate detection results (5)
for r in res:
    img = np.copy(r.orig_img)
    img_name = Path(r.path).stem

    # iterate each object contour (6)
    for ci,c in enumerate(r):
        label = c.names[c.boxes.cls.tolist().pop()]

        b_mask = np.zeros(img.shape[:2], np.uint8)

        # Create contour mask (1)
        contour = c.masks.xy.pop().astype(np.int32).reshape(-1, 1, 2)
        _ = cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED)

        # Choose one:

        # OPTION-1: Isolate object with black background
        mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR)
        isolated = cv2.bitwise_and(mask3ch, img)

        # OPTION-2: Isolate object with transparent background (when saved as PNG)
        isolated = np.dstack([img, b_mask])

        # OPTIONAL: detection crop (from either OPT1 or OPT2)
        x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32)
        iso_crop = isolated[y1:y2, x1:x2]

        # TODO your actions go here (2)
  1. Die Zeile, die contour wird hier in einer einzigen Zeile zusammengefasst, wo sie oben auf mehrere Zeilen aufgeteilt war.
  2. Was hier hingehört, entscheidest du!
  3. Weitere Informationen findest du unter Vorhersagemodus.
  4. Siehe Segmentaufgabe für weitere Informationen.
  5. Erfahre mehr über das Arbeiten mit Ergebnissen
  6. Erfahre mehr über die Ergebnisse der Segmentierungsmaske


Erstellt am 2023-11-27, Aktualisiert am 2024-04-27
Autoren: glenn-jocher (6), RizwanMunawar (1), Burhan-Q (1)

Kommentare