Reconnaissance de formes

Avant de partir dans des calculs de caractéristiques à rallonge afin de labelliser correctement un objet dans une image, il peut être intéressant de détecter simplement ses formes. Par exemple trouver un carré pour représenter un dé ou bien un rond permettant de détecter une balle.

Vous l’aurez compris nous allons nous attaquer aujourd’hui à la reconnaissance de formes. Les images que nous allons utiliser aujourd’hui sont des images simple où le fond est noir. Pour une application réelle, il sera nécessaire d’effectuer un prétraitement comme une soustraction de fond ou un seuillage.

Comment allons-nous procéder pour détecter des formes ?

Voici les 3 étapes que nous allons effectuer :

  1. L’extraction des contours dans l’image, qui nous permettra d’extraire un à un les objets.
  2. L’approximation de chaque contour grâce à la fonction approxPolyDP. Cette fonction, reposant sur l’algorithme de Douglas-Peucker, sert à diminuer le nombre de noeuds/points sur une polyligne / contour. En d’autres termes, nous allons essayer de décrire les contours par un ensemble de segments.
  3. La classification des formes en fonction de nombre de lignes formant leur contour.

Reconnaissance de formes

Commençons par déclarer les importations dans le programme detect_formes.py et à charger une image.

import numpy as np
import cv2
image = cv2.imread('image.bmp')
import numpy as np import cv2 image = cv2.imread('image.bmp')

import numpy as np 
import cv2 
image = cv2.imread('image.bmp')

Voici donc l’image que nous souhaitons traiter (disponible ici) :

Étape 1: Détection des contours

La première étape que nous avons soulignée précédemment est de détecter les contours de l’image. Pour ce faire, nous allons convertir l’image en niveau de gris (l. 6) avant de réaliser un seuillage (l. 7) pour améliorer la détection de contours (findContours() – (l. 9)).

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,250,255,cv2.THRESH_BINARY_INV)
img,contours,h=cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(gray,250,255,cv2.THRESH_BINARY_INV) img,contours,h=cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,250,255,cv2.THRESH_BINARY_INV)

contours,h = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

Voilà, nos contours sont détectés. Passons maintenant à l’étape suivante : l’approximation de nos contours.

Étape 2: Approximation des contours

Pour chaque contour, nous commençons par calculer le périmètre du contour (l. 12). Afin de pouvoir calculer l’approximation du contour ligne 13. La fonction approxPolyDP prend en entrée les points du contour, ainsi qu’un pourcentage (1% dans notre cas) du périmètre du contour. Plus ce pourcentage est grand, moins l’approximation sera précise. Enfin le dernier paramètre spécifie que la courbe que nous cherchons est fermée.

Nous en profitons pour calculer les moments du contour (l. 15) afin de pouvoir calculer simplement le centre de chaque contour (l. 16-17).

Enfin, nous traçons chaque contour sur l’image d’origine (l. 18) avant de passer à l’étape suivante.

for cnt in contours:
perimetre=cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,0.01*perimetre,True)
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.drawContours(image,[cnt],-1,(0,255,0),2)
for cnt in contours: perimetre=cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,0.01*perimetre,True) M = cv2.moments(cnt) cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) cv2.drawContours(image,[cnt],-1,(0,255,0),2)

for cnt in contours:
    perimetre=cv2.arcLength(cnt,True)
    approx = cv2.approxPolyDP(cnt,0.01*perimetre,True)
    
    M = cv2.moments(cnt)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    cv2.drawContours(image,[cnt],-1,(0,255,0),2)

Étape 3: classification des formes

Cette étape va être relativement simple à comprendre, il suffira juste de se rappeler combien de sommets a chaque forme.

if len(approx)==3:
 shape = "triangle"
elif len(approx)==4:
 (x, y, w, h) = cv2.boundingRect(approx)
 ratio = w / float(h)

 if ratio >= 0.95 and ratio <= 1.05:
  shape = "carre"
 else:
  shape = "rectangle"
elif len(approx)==5:
 shape = "pentagone"
elif len(approx)==6:
 shape = "hexagone"
else:
 shape= "circle"

cv2.putText(image, shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX,0.5, (255, 255, 255), 2)

Si la forme à trois sommets, il s’agit d’un triangle (l. 19-20). La démarche est identique pour le pentagone (l. 28-29) ou l’hexagone (l. 30-31). Pour 4 sommets, c’est légèrement différent. En effet, sur notre image, nous avons un carré, mais également un rectangle.

Nous savons que le carré à ses côtés de mêmes longueurs. Pour vérifier cela, nous utilisons la fonction boundingRect (l. 22) qui nous permet d’obtenir le rectangle encadrant de notre quadrilatère. Ce qui nous intéresse particulièrement, c’est que cette fonction nous renvoie la hauteur (h) et la largeur (w) de la forme. Ainsi le ratio largeur/hauteur devrait être égale à 1 pour un carré (l. 23) et différent de 1 pour un rectangle. Comme il est possible qu’il y ait des erreurs dans les calculs d’approximations ou lors du seuillage, nous acceptons une erreur de 5% autour de 1 afin de déterminer s’il s’agit bien d’un carré (l. 24).

Enfin, si le nombre de sommets est supérieur à 6, nous déterminons qu’il s’agit d’un cercle (l. 32-33). Bien évidemment, il serait possible de modifier notre code pour prendre en compte les heptagones, octogones et j’en passe 🙂

Enfin, nous rajoutons sur l’image, à proximité du centre de chaque contour, le type de forme dont il s’agit (l. 34).

Il nous reste plus qu’à afficher le résultat pour admirer notre travail.

cv2.imshow('image',image)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imshow('image',image) cv2.waitKey(0) cv2.destroyAllWindows()

cv2.imshow('image',image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Maintenant que nous arrivons à détecter les contours, il peut être intéressant d’en étudier la couleur.

Florent

View Comments

Recent Posts

Conduite autonome : comment détecter les lignes d’une route

Détecter les voies de circulation est une étape incontournable pour la réalisation d’un système de…

2 jours ago

Capturer une image avec la caméra du Raspberry

Dans cet article, nous allons voir comment capturer simplement une image grâce au module raspicam…

4 mois ago

Détecter les repères faciaux (facial landmarks) avec Dlib

Aujourd’hui nous allons utiliser Dlib et OpenCV pour détecter les repères faciaux (facial landmarks) dans…

2 ans ago

Reconnaissance d’objets en temps réel avec MobileNet

Nous avons vu dans un précédent article comment utiliser l’algorithme MobileNetSSD pour la reconnaissance d’objet…

2 ans ago

Reconnaissance d’objet avec MobileNet et OpenCV

Dans cet article, nous nous intéressons à l’utilisation des réseaux de neurones convolutifs dans le…

2 ans ago

Où est Charlie ?

Derrière ce titre un peu trompeur, nous n’allons pas trouver Charlie dans une image, mais…

2 ans ago