Skip to content

Isoler les objets de segmentation

Après avoir effectué la tâche de segmentation, il est parfois souhaitable d'extraire les objets isolés des résultats de l'inférence. Ce guide fournit une recette générique sur la façon d'accomplir cette tâche en utilisant lemode prédictifde Ultralytics .

Exemple de segmentation d'objets isolés

Visite guidée de la recette

  1. Commence par les importations nécessaires

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

    Voir la section Ultralytics Quickstart Installation pour une démonstration rapide de l'installation des bibliothèques requises.


  2. Charge un modèle et exécute-le predict() sur une source.

    from ultralytics import YOLO
    
    # Load a model
    model = YOLO('yolov8n-seg.pt')
    
    # Run inference
    result = model.predict()
    
    Pas d'arguments de prédiction ?

    Sans spécifier de source, les images d'exemple de la bibliothèque seront utilisées :

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

    Ceci est utile pour effectuer des tests rapides avec le predict() méthode.

    Pour plus d'informations sur les modèles de segmentation, consulte le site Tâche du segment page. Pour en savoir plus sur predict() méthode, voir Mode prédiction de la documentation.


  3. Itère maintenant sur les résultats et les contours. Pour les flux de travail qui veulent enregistrer une image dans un fichier, l'image source base-name et la détection class-label sont récupérés pour une utilisation ultérieure (facultatif).

    # (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. Pour en savoir plus sur la façon de travailler avec les résultats de la détection, voir la section des boîtes pour le mode prédiction.
    2. Pour en savoir plus predict() voir les résultats Travailler avec les résultats du mode prédictif
    Pour la boucle

    Une image unique n'itère la première boucle qu'une seule fois. Une image unique ne comportant qu'une seule détection n'effectuera qu' une seule fois l'itération de chaque boucle.


  4. Commence par générer un masque binaire à partir de l'image source, puis dessine un contour rempli sur le masque. Cela permettra d'isoler l'objet des autres parties de l'image. Un exemple de bus.jpg pour l'une des personnes détectées person Les objets de la classe sont illustrés à droite.

    Image de masque binaire

    # 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. Pour plus d'informations sur c.masks.xy voir Section des masques du mode prédictif.

    2. Ici, les valeurs sont coulées dans np.int32 pour la compatibilité avec drawContours() d'OpenCV.

    3. L'OpenCV drawContours() La fonction s'attend à ce que les contours aient une forme de [N, 1, 2] élargis la section ci-dessous pour plus de détails.

    Étends-toi pour comprendre ce qui se passe lors de la définition du contour variable.

    • c.masks.xy : : Fournit les coordonnĂ©es des points du contour du masque au format. (x, y). Pour plus de dĂ©tails, reporte-toi Ă  la rubrique Section des masques du mode prĂ©dictif.

    • .pop() : : Comme masks.xy est une liste contenant un seul Ă©lĂ©ment, cet Ă©lĂ©ment est extrait Ă  l'aide de la fonction pop() mĂ©thode.

    • .astype(np.int32) : : Utilisation masks.xy sera retournĂ© avec un type de donnĂ©es de float32mais cela ne sera pas compatible avec le logiciel OpenCV drawContours() et cela changera le type de donnĂ©es en int32 pour la compatibilitĂ©.

    • .reshape(-1, 1, 2) : : Reformate les donnĂ©es dans la forme requise de [N, 1, 2] oĂą N est le nombre de points de contour, chaque point Ă©tant reprĂ©sentĂ© par une seule entrĂ©e. 1et l'entrĂ©e est composĂ©e de 2 valeurs. Les -1 indique que le nombre de valeurs sur cette dimension est flexible.

    DĂ©veloppe pour une explication de la drawContours() configuration.

    • Encapsuler le contour entre crochets, [contour]a permis de gĂ©nĂ©rer efficacement le masque de contour souhaitĂ© au cours des tests.

    • La valeur -1 spĂ©cifiĂ©e pour le drawContours() Le paramètre indique Ă  la fonction de dessiner tous les contours prĂ©sents dans l'image.

    • Le tuple (255, 255, 255) reprĂ©sente la couleur blanche, qui est la couleur souhaitĂ©e pour dessiner le contour dans ce masque binaire.

    • L'ajout de cv.FILLED colorera de la mĂŞme façon tous les pixels entourĂ©s par la limite du contour, dans ce cas, tous les pixels entourĂ©s seront blancs.

    • Voir Documentation OpenCV sur drawContours() pour plus d'informations.


  5. Ensuite, il y a deux options pour faire avancer l'image à partir de ce point et une option ultérieure pour chacune d'entre elles.

    Options d'isolation des objets

    # Create 3-channel mask
    mask3ch = cv.cvtColor(b_mask, cv.COLOR_GRAY2BGR)
    
    # Isolate object with binary mask
    isolated = cv.bitwise_and(mask3ch, img)
    
    Comment cela fonctionne-t-il ?
    • Tout d'abord, le masque binaire est converti d'une image Ă  un seul canal en une image Ă  trois canaux. Cette conversion est nĂ©cessaire pour l'Ă©tape suivante oĂą le masque et l'image originale sont combinĂ©s. Les deux images doivent avoir le mĂŞme nombre de canaux pour ĂŞtre compatibles avec l'opĂ©ration de mĂ©lange.

    • L'image originale et le masque binaire Ă  trois canaux sont fusionnĂ©s Ă  l'aide de la fonction OpenCV bitwise_and(). Cette opĂ©ration permet de conserver seulement les valeurs des pixels qui sont supĂ©rieures Ă  zĂ©ro (> 0) des deux images. Puisque les pixels du masque sont supĂ©rieurs Ă  zĂ©ro (> 0) seulement dans la rĂ©gion du contour, les pixels restants de l'image originale sont ceux qui se superposent au contour.

    Isoler avec des pixels noirs : Sous-options

    Image en taille réelle

    Il n'y a pas d'étapes supplémentaires à suivre pour conserver l'image en taille réelle.

    Exemple Taille réelle Image d'un objet isolé Fond noir
    Exemple de sortie en taille réelle

    Image de l'objet recadré

    Des étapes supplémentaires sont nécessaires pour recadrer l'image afin de n'inclure que la région de l'objet.

    Exemple de recadrage d'une image d'objet isolé Fond noir

    # (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. Pour plus d'informations sur les résultats des boîtes englobantes, voir la section Boîtes du mode Prédire.
    Que fait ce code ?
    • Le c.boxes.xyxy.cpu().numpy() rĂ©cupère les boĂ®tes de dĂ©limitation sous la forme d'un tableau NumPy dans le fichier xyxy oĂą xmin, ymin, xmaxet ymax reprĂ©sentent les coordonnĂ©es du rectangle de la boĂ®te de dĂ©limitation. Voir Section des boĂ®tes du mode prĂ©dictif pour plus de dĂ©tails.

    • Le squeeze() supprime toutes les dimensions inutiles du tableau NumPy, en veillant Ă  ce qu'il ait la forme attendue.

    • Conversion des valeurs de coordonnĂ©es Ă  l'aide de .astype(np.int32) change le type de donnĂ©es des coordonnĂ©es de la boĂ®te de float32 Ă  int32Ils sont donc compatibles avec le recadrage d'images Ă  l'aide de tranches d'index.

    • Enfin, la rĂ©gion de la boĂ®te englobante est dĂ©coupĂ©e dans l'image Ă  l'aide d'un dĂ©coupage par index. Les limites sont dĂ©finies par le [ymin:ymax, xmin:xmax] coordonnĂ©es de la boĂ®te de dĂ©limitation de la dĂ©tection.

    # Isolate object with transparent background (when saved as PNG)
    isolated = np.dstack([img, b_mask])
    
    Comment cela fonctionne-t-il ?
    • Utilisation du logiciel NumPy dstack() (empilement de tableaux le long de l'axe de profondeur) en conjonction avec le masque binaire gĂ©nĂ©rĂ©, crĂ©era une image Ă  quatre canaux. Cela permet Ă  tous les pixels situĂ©s en dehors du contour de l'objet d'ĂŞtre transparents lors de l'enregistrement sous forme d'image. PNG fichier.

    Isoler avec des pixels transparents : Sous-options

    Image en taille réelle

    Il n'y a pas d'étapes supplémentaires à suivre pour conserver l'image en taille réelle.

    Exemple Taille réelle Image d'objet isolé Sans arrière-plan
    Exemple de sortie en taille réelle + arrière-plan transparent

    Image de l'objet recadré

    Des étapes supplémentaires sont nécessaires pour recadrer l'image afin de n'inclure que la région de l'objet.

    Exemple de recadrage d'une image d'objet isolé sans arrière-plan

    # (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. Pour plus d'informations sur les résultats des boîtes englobantes, voir la section Boîtes du mode Prédire.
    Que fait ce code ?
    • Lors de l'utilisation de c.boxes.xyxy.cpu().numpy()les boĂ®tes de dĂ©limitation sont renvoyĂ©es sous la forme d'un tableau NumPy, Ă  l'aide de la fonction xyxy format des coordonnĂ©es de la boĂ®te, qui correspondent aux points xmin, ymin, xmax, ymax pour la boĂ®te de dĂ©limitation (rectangle), voir Section des boĂ®tes du mode prĂ©dictif pour plus d'informations.

    • Ajouter squeeze() s'assure que toutes les dimensions superflues sont supprimĂ©es du tableau NumPy.

    • Conversion des valeurs de coordonnĂ©es Ă  l'aide de .astype(np.int32) change le type de donnĂ©es des coordonnĂ©es de la boĂ®te de float32 Ă  int32 qui sera compatible lors du recadrage de l'image Ă  l'aide des tranches d'index.

    • Enfin, la rĂ©gion de l'image pour la boĂ®te de dĂ©limitation est dĂ©coupĂ©e Ă  l'aide d'un dĂ©coupage par index, oĂą les limites sont dĂ©finies Ă  l'aide de la fonction [ymin:ymax, xmin:xmax] coordonnĂ©es de la boĂ®te de dĂ©limitation de la dĂ©tection.

    Et si je veux que l'objet recadré comprenne l' arrière-plan ?

    Il s'agit d'une fonctionnalité intégrée à la bibliothèque Ultralytics . Voir l'article save_crop argument pour Arguments d'inférence du mode prédictif pour plus de détails.


  6. Ce qu'il faut faire ensuite est entièrement laissé à ton appréciation en tant que développeur. Un exemple basique d'une étape suivante possible (enregistrer l'image dans un fichier pour une utilisation ultérieure) est présenté.

    • NOTE : cette Ă©tape est facultative et peut ĂŞtre ignorĂ©e si elle n'est pas nĂ©cessaire pour ton cas d'utilisation spĂ©cifique.
    Exemple Étape finale
    # Save isolated object to file
    _ = cv.imwrite(f'{img_name}_{label}-{ci}.png', iso_crop)
    
    • Dans cet exemple, le img_name est le nom de base du fichier image source, label est le nom de la classe dĂ©tectĂ©e, et ci est l'index de la dĂ©tection de l'objet (en cas d'instances multiples portant le mĂŞme nom de classe).

Exemple de code complet

Ici, toutes les étapes de la section précédente sont combinées en un seul bloc de code. Pour une utilisation répétée, il serait optimal de définir une fonction pour exécuter certaines ou toutes les commandes contenues dans la section for-Mais c'est un exercice que nous laissons au lecteur.

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 ligne qui remplit contour est combinée en une seule ligne ici, alors qu'elle était divisée en plusieurs lignes ci-dessus.
  2. C'est à toi de décider ce que tu veux mettre ici !
  3. Voir Mode prédictif pour plus d'informations.
  4. Voir Tâche de segment pour plus d'informations.
  5. En savoir plus sur Travailler avec des résultats
  6. En savoir plus sur les résultats du masque de segmentation


Créé le 2023-11-27, Mis à jour le 2024-01-14
Auteurs : glenn-jocher (4), RizwanMunawar (1), Burhan-Q (1)

Commentaires