Niveau
Débutant
Env.
Google Colab
Code
Python
Libs
cv2, darknet
Sources

YOLO (Partie 2) Utilisez YOLO avec OpenCV

Dans l’article La détection d’objets avec YOLO v4 nous avons vu l’implémentation de YOLO avec Darknet. L’idée de cet article est de nous simplifier les choses en utilisant l’implémentation de YOLO qui est fournie dans l’une des plus célèbre bibliothèque de vision par ordinateur: à savoir OpenCV. Et oui, au lieu de télécharger darknet comme nous l’avons fait, ou alors pourquoi pas implémenter YOLO « from scratch » il est possible d’utliser cette librairie pour directement utiliser YOLO … super n’est-ce pas ? alors allons-y …

Pré-requis

Dans cet article j’utiliserai Python dans Google Colab.

Vous devrez avoir néanmoins télécharger les 3 fichiers indispensables à YOLO:

  • La configuration (fichier cfg)
  • Les poids (weights)
  • La liste des labels (coco.names ici)

Je vous invite à vous référer à mon article La détection d’objets avec YOLO v4 où je vous explique comment récupérer et copier ces fichiers dans Google colab.

Une fois le notebook créé dans colab, importez les librairies comme suit:

import numpy as np
import cv2
from google.colab.patches import cv2_imshow

Remarquez qu’on va devoir utiliser la fonction cv2_imshow à la place de cv2.imshow() car cette dernière n’est pas supportée dans colab.

Préparations

Nous allons maintenant définir les variables globales (permettant notament le paramétrage):

ROOT_COLAB = '/content/drive/MyDrive/Colab Notebooks'
YOLO_CONFIG = ROOT_COLAB + '/YOLO/oc_data/'
COCO_LABELS_FILE = YOLO_CONFIG + 'coco.names'
YOLO_CONFIG_FILE = YOLO_CONFIG + 'yolov3.cfg'
YOLO_WEIGHTS_FILE = YOLO_CONFIG + 'yolov3.weights'
IMAGE_FILE = 'yoloimg.jpg'
IMAGE = cv2.imread(ROOT_COLAB + '/' + IMAGE_FILE)
CONFIDENCE_MIN = 0.5

La variable ROOT_COLAB est la racine de mes répertoires colab dans Google Drive, il vous faudra certainement l’adapter à votre environnement. J’ai bien sur téléchargé les fichiers YOLO et je les aie placé dans le répertoire /YOLO/oc_data/.

Afin d’afficher correctement – sans prendre tout votre écran – en retaillant et conservant les proportions de votre image, voici une petite fonction pratique :

# Little function to resize in keeping the format ratio
# Cf. https://stackoverflow.com/questions/35180764/opencv-python-image-too-big-to-display
def ResizeWithAspectRatio(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    image = image.copy()
    (h, w) = image.shape[:2]
    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))
    return cv2.resize(image, dim, interpolation=inter)

cv2_imshow(ResizeWithAspectRatio(IMAGE, width=700)) 

Nous allons maintenant lire la liste des libellés (classes) du fichier coco.names :

with open(COCO_LABELS_FILE, 'rt') as f:
    labels = f.read().rstrip('\n').split('\n')

Puis nous allons créer un jeu de couleur aléatoire pour chaque classe/libellé :

np.random.seed(45)
BOX_COLORS = np.random.randint(0, 255, size=(len(labels), 3), dtype="uint8")

Cela nous permettra, pour chaque classe d’avoir un cadre de couleur différente (pratique si nous avons beaucoup de détections dans la même image).

Initialisation de YOLO via OpenCV

Nous y voilà, nous allons maintenant initialiser le réseau YOLO (implémenté dans OpenCV) via la fonction readNetFromDarknet() :

yolo = cv2.dnn.readNetFromDarknet(YOLO_CONFIG_FILE, YOLO_WEIGHTS_FILE)

On récupère ensuite la dernière couche (output) de sortie :

yololayers = [yolo.getLayerNames()[i[0] - 1] for i in yolo.getUnconnectedOutLayers()]

Nous allons maintenant donner notre image au réseau de neurones, mais pour cela il nous faut la convertir en blob:

blobimage = cv2.dnn.blobFromImage(IMAGE, 1 / 255.0, (416, 416),	swapRB=True, crop=False)
yolo.setInput(blobimage)

Détection des objets

Pour lancer le traitement, rien de plus simple:

layerOutputs = yolo.forward(yololayers)

Et voilà le réseau de neurones a fait son travail: voyons comment nous allons récupérer/traiter ses résultats. Pour celà nous allons avoir besoin de 3 listes :

  • Une pour les cadres qui ont détecté des objets
  • Une pour les scores de confiance de ces objets/détection
  • Une pour le type d’objet (classe/libellé) détecté

YOLO nous renvoit en effet tout cela en vrac et nous allons devoir choisir ce que nous allons faire de toutes ses informations, par exemple un objet détecté sur un confiance de moins de 10% est-il intéressant ?

Nous allons devoir parcourir tous ces objets détectés et filtrer/retravailler les informations selon notre besoin:

boxes_detected = []
confidences_scores = []
labels_detected = []
# loop over each of the layer outputs
for output in layerOutputs:
  # loop over each of the detections
  for detection in output:
    # extract the class ID and confidence (i.e., probability) of the current object detection
    scores = detection[5:]
    classID = np.argmax(scores)
    confidence = scores[classID]

    # Take only predictions with confidence more than CONFIDENCE_MIN thresold
    if confidence > CONFIDENCE_MIN:
      # Bounding box
      box = detection[0:4] * np.array([W, H, W, H])
      (centerX, centerY, width, height) = box.astype("int")

      # Use the center (x, y)-coordinates to derive the top and left corner of the bounding box
      x = int(centerX - (width / 2))
      y = int(centerY - (height / 2))

      # update our result list (detection)
      boxes_detected.append([x, y, int(width), int(height)])
      confidences_scores.append(float(confidence))
      labels_detected.append(classID)

Ci-dessus nous avons par exemple filtré sur le score de confiance minimum de CONFIDENCE_MIN. Nous avons aussi retravaillé les coordonnées de cadres détectés afin de pouvoir les afficher ultérieurement avec la fonction cv2.Rectangle d’OpenCV.

Attention aussi car les classes (libellés) renvoyés par YOLO sont les ID et non les noms usuels. il faudra utiliser le tableau initial pour les afficher:

label_names = [labels[i] for i in labels_detected]
label_names
['laptop', 'cell phone']

Affichage du résultat

Aller j’arrête la le suspense ! nous allons afficher le résultat dans l’image en créan les cadres colorés (selon les classes) directement dans l’image source. On y rajoutera bien sur le taux de confiance de la détection :

image = IMAGE.copy()
if nb_results > 0:
  for i in range(nb_results):
    # extract the bounding box coordinates
    (x, y) = (boxes_detected[i][0], boxes_detected[i][1])
    (w, h) = (boxes_detected[i][2], boxes_detected[i][3])
    # draw a bounding box rectangle and label on the image
    color = [int(c) for c in BOX_COLORS[labels_detected[i]]]
    cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
    score = str(round(float(confidences_scores[i]) * 100, 1)) + "%"
    text = "{}: {}".format(labels[labels_detected[i]], score)
    cv2.putText(image, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
  
cv2_imshow(ResizeWithAspectRatio(image, width=700))
Rapide et efficace n’est-ce pas ? n’hésitez pas à jouer aussi avec le paramètre de seuil de confiance CONFIDENCE_MIN. Vous vous rendrez d’ailleurs compte que sur des scores assez faible on peut avoir quelques surprises …

Les sources (Jupyter notebook) de cet article ici.

Lire la suite

Dans l’article suivant nous verrons comment utiliser la technique de NMS pour supprimer les détections en double.

Partager cet article

6 Replies to “YOLO (Partie 2) Utilisez YOLO avec OpenCV”

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.