Vai al contenuto

Isolamento degli oggetti di segmentazione

Dopo aver eseguito l'attività di segmentazione, a volte è auspicabile estrarre gli oggetti isolati dai risultati dell'inferenza. Questa guida fornisce una ricetta generica su come ottenere questo risultato utilizzando lamodalità Ultralytics Predict.

Esempio di segmentazione di oggetti isolati

Passaggio della ricetta

  1. Inizia con le importazioni necessarie

    from pathlib import Path
    
    import cv2 as cv
    import numpy as np
    from ultralytics import YOLO
    
    Ultralytics Installa

    Consulta la sezione Installazionerapida di Ultralytics per una guida rapida all'installazione delle librerie necessarie.


  2. Carica un modello ed esegui predict() su una sorgente.

    from ultralytics import YOLO
    
    # Load a model
    model = YOLO('yolov8n-seg.pt')
    
    # Run inference
    result = model.predict()
    
    Nessun argomento di previsione?

    Senza specificare una fonte, verranno utilizzate le immagini di esempio della libreria:

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

    Questo è utile per effettuare test rapidi con il predict() metodo.

    Per ulteriori informazioni sui Modelli di Segmentazione, visita la pagina Segmento Attività pagina. Per saperne di più su predict() metodo, vedi Modalità Previsione nella sezione della Documentazione.


  3. Ora itera sui risultati e sui contorni. Per i flussi di lavoro che desiderano salvare un'immagine su file, l'immagine di origine base-name e il rilevamento class-label vengono recuperati per un uso successivo (opzionale).

    # (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. Per saperne di più su come lavorare con i risultati del rilevamento, consulta la sezione Boxes per la modalità Predict.
    2. Per saperne di più predict() risultati vedi Lavorare con i risultati della modalità Predict
    Per-loop

    Una singola immagine itererà il primo ciclo una sola volta. Un'immagine singola con un solo rilevamento itererà ogni ciclo una sola volta.


  4. Inizia generando una maschera binaria dall'immagine di partenza e poi disegna un contorno pieno sulla maschera. In questo modo l'oggetto verrà isolato dalle altre parti dell'immagine. Un esempio tratto da bus.jpg per uno dei dati rilevati person è mostrato a destra.

    Immagine della maschera binaria

    # 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
    _ = cv.drawContours(b_mask,
                        [contour],
                        -1,
                        (255, 255, 255),
                        cv.FILLED)
    
    1. Per maggiori informazioni su c.masks.xy vedere Sezione Maschere dalla modalità Previsione.

    2. In questo caso, i valori vengono inseriti in np.int32 per la compatibilità con drawContours() di OpenCV.

    3. L'OpenCV drawContours() la funzione si aspetta che i contorni abbiano una forma di [N, 1, 2] espandi la sezione sottostante per maggiori dettagli.

    Espandi la comprensione di ciò che sta accadendo quando si definiscono i parametri di contour variabile.

    • c.masks.xy :: Fornisce le coordinate dei punti del contorno della maschera nel formato (x, y). Per maggiori dettagli, consulta la sezione Sezione Maschere dalla modalità Previsione.

    • .pop() :: Come masks.xy è un elenco contenente un singolo elemento, questo elemento viene estratto utilizzando il metodo pop() metodo.

    • .astype(np.int32) :: Utilizzo masks.xy restituirà un tipo di dati di float32ma questo non sarà compatibile con OpenCV drawContours() quindi cambierà il tipo di dati in int32 per la compatibilità.

    • .reshape(-1, 1, 2) :: Riformatta i dati nella forma richiesta di [N, 1, 2] dove N è il numero di punti del contorno, con ogni punto rappresentato da un singolo elemento 1e la voce è composta da 2 valori. Il -1 indica che il numero di valori lungo questa dimensione è flessibile.

    Espandi per una spiegazione del drawContours() configurazione.

    • Incapsulamento del contour tra parentesi quadre, [contour]è stato trovato in grado di generare efficacemente la maschera di contorno desiderata durante i test.

    • Il valore -1 specificato per l'opzione drawContours() indica alla funzione di disegnare tutti i contorni presenti nell'immagine.

    • Il tuple (255, 255, 255) rappresenta il colore bianco, che è il colore desiderato per disegnare il contorno in questa maschera binaria.

    • L'aggiunta di cv.FILLED colorerà tutti i pixel racchiusi dal contorno allo stesso modo; in questo caso, tutti i pixel racchiusi saranno bianchi.

    • Vedi Documentazione di OpenCV su drawContours() per maggiori informazioni.


  5. Poi ci sono due opzioni su come procedere con l'immagine a partire da questo punto e un'opzione successiva per ciascuna di esse.

    Opzioni di isolamento degli oggetti

    # Create 3-channel mask
    mask3ch = cv.cvtColor(b_mask, cv.COLOR_GRAY2BGR)
    
    # Isolate object with binary mask
    isolated = cv.bitwise_and(mask3ch, img)
    
    Come funziona?
    • Innanzitutto, la maschera binaria viene convertita da un'immagine a canale singolo a un'immagine a tre canali. Questa conversione è necessaria per la fase successiva in cui la maschera e l'immagine originale vengono combinate. Entrambe le immagini devono avere lo stesso numero di canali per essere compatibili con l'operazione di fusione.

    • L'immagine originale e la maschera binaria a tre canali vengono unite utilizzando la funzione OpenCV bitwise_and(). Questa operazione conserva solo valori dei pixel maggiori di zero (> 0) da entrambe le immagini. Poiché i pixel della maschera sono maggiori di zero (> 0) solo all'interno della regione del contorno, i pixel rimanenti dell'immagine originale sono quelli che si sovrappongono al contorno.

    Isola con pixel neri: Sotto-opzioni

    Immagine a grandezza naturale

    Non sono necessari ulteriori passaggi se si conserva l'immagine a grandezza naturale.

    Esempio di immagine di oggetto isolato a grandezza naturale con sfondo nero
    Esempio di uscita a grandezza naturale

    Immagine dell'oggetto ritagliata

    Sono necessari ulteriori passaggi per ritagliare l'immagine in modo da includere solo la regione dell'oggetto.

    Esempio di ritaglio dell'immagine di un oggetto isolato con sfondo nero

    # (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. Per maggiori informazioni sui risultati dei riquadri di delimitazione, consulta la sezione Riquadri dalla modalità Previsione.
    Cosa fa questo codice?
    • Il c.boxes.xyxy.cpu().numpy() recupera i riquadri di delimitazione come array NumPy nell'array xyxy formato, dove xmin, ymin, xmax, e ymax rappresentano le coordinate del rettangolo di delimitazione. Vedi Sezione Scatole dalla modalità Predict per maggiori dettagli.

    • Il squeeze() rimuove le dimensioni non necessarie dall'array NumPy, assicurandosi che abbia la forma prevista.

    • Convertire i valori delle coordinate utilizzando .astype(np.int32) cambia il tipo di dati delle coordinate della casella da float32 a int32, rendendoli compatibili con il ritaglio delle immagini utilizzando le fette di indice.

    • Infine, la regione del rettangolo di selezione viene ritagliata dall'immagine utilizzando l'index slicing. I limiti sono definiti dal parametro [ymin:ymax, xmin:xmax] coordinate del rettangolo di selezione del rilevamento.

    # Isolate object with transparent background (when saved as PNG)
    isolated = np.dstack([img, b_mask])
    
    Come funziona?
    • Utilizzo di NumPy dstack() (impilamento di array lungo l'asse di profondità) insieme alla maschera binaria generata, creerà un'immagine con quattro canali. In questo modo tutti i pixel al di fuori del contorno dell'oggetto saranno trasparenti quando verranno salvati come immagini PNG file.

    Isola con pixel trasparenti: Sotto-opzioni

    Immagine a grandezza naturale

    Non sono necessari ulteriori passaggi se si conserva l'immagine a grandezza naturale.

    Esempio di immagine di oggetto isolato a grandezza naturale senza sfondo
    Esempio di output a grandezza naturale + sfondo trasparente

    Immagine dell'oggetto ritagliata

    Sono necessari ulteriori passaggi per ritagliare l'immagine in modo da includere solo la regione dell'oggetto.

    Esempio di ritaglio dell'immagine di un oggetto isolato senza sfondo

    # (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. Per maggiori informazioni sui risultati dei riquadri di delimitazione, consulta la sezione Riquadri dalla modalità Previsione.
    Cosa fa questo codice?
    • Quando si utilizza c.boxes.xyxy.cpu().numpy(), i riquadri di delimitazione vengono restituiti come array NumPy, utilizzando l'opzione xyxy formato delle coordinate della scatola, che corrispondono ai punti xmin, ymin, xmax, ymax per il rettangolo di delimitazione (rettangolo), vedi Sezione Scatole dalla modalità Predict per maggiori informazioni.

    • Aggiunta di squeeze() assicura che qualsiasi dimensione estranea venga rimossa dall'array NumPy.

    • Convertire i valori delle coordinate utilizzando .astype(np.int32) cambia il tipo di dati delle coordinate della casella da float32 a int32 che sarà compatibile quando si ritaglia l'immagine utilizzando le fette di indice.

    • Infine, la regione dell'immagine per il riquadro di delimitazione viene ritagliata utilizzando l'index slicing, in cui i limiti vengono impostati utilizzando l'opzione [ymin:ymax, xmin:xmax] coordinate del rettangolo di selezione del rilevamento.

    E se volessi l'oggetto ritagliato , compreso lo sfondo?

    Si tratta di una funzione integrata nella libreria Ultralytics . Vedi la sezione save_crop argomento per Argomenti di inferenza della modalità di previsione per i dettagli.


  6. La scelta del passo successivo spetta esclusivamente a te, in quanto sviluppatore. Viene mostrato un esempio di base di un possibile passo successivo (salvare l'immagine in un file per un uso futuro).

    • NOTA: questo passaggio è facoltativo e può essere saltato se non è necessario per il tuo caso d'uso specifico.
    Esempio di passo finale
    # Save isolated object to file
    _ = cv.imwrite(f'{img_name}_{label}-{ci}.png', iso_crop)
    
    • In questo esempio, il img_name è il nome di base del file immagine di origine, label è il nome della classe rilevata e ci è l'indice del rilevamento dell'oggetto (in caso di istanze multiple con lo stesso nome di classe).

Codice di esempio completo

In questo caso, tutti i passaggi della sezione precedente vengono combinati in un unico blocco di codice. Per un uso ripetuto, sarebbe ottimale definire una funzione che esegua alcuni o tutti i comandi contenuti nel file for-Ma questo è un esercizio che lasciamo al lettore.

from pathlib import Path

import cv2 as cv
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)
        _ = cv.drawContours(b_mask, [contour], -1, (255, 255, 255), cv.FILLED)

        # Choose one:

        # OPTION-1: Isolate object with black background
        mask3ch = cv.cvtColor(b_mask, cv.COLOR_GRAY2BGR)
        isolated = cv.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. La linea che popola contour viene riunito in un'unica riga qui, mentre prima era diviso in più righe.
  2. Quello che c'è qui dipende da te!
  3. Per ulteriori informazioni, consulta la sezione Modalità Previsione.
  4. Per maggiori informazioni, consulta la sezione Attività di segmento.
  5. Per saperne di più su Lavorare con i risultati
  6. Per saperne di più sui risultati della maschera di segmentazione


Creato 2023-11-27, Aggiornato 2024-01-14
Autori: glenn-jocher (4), RizwanMunawar (1), Burhan-Q (1)

Commenti