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

Installer OpenCV 5 sur MacOS

Vous avez commencé un projet de vision par ordinateur ou de traitement d'images ? Alors,…

2 semaines ago

[Étape 3] Création d’un Photobooth – Finalisation

Nous avons vu dans les précédents articles de quel matériel nous avions besoin pour créer…

3 semaines ago

[Étape 2] Création d’un photobooth – Logiciel

Nous avons préparé la semaine dernière toute la partie matérielle du photobooth. Maintenant nous allons…

4 semaines ago

[Étape 1] Création d’un photobooth – Hardware

J'avais il y a quelques années réalisé un photobooth, afin de permettre aux convives de…

1 mois ago

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 mois ago

Installez OpenCV 4 sur macOs

Vous avez commencé un projet de vision par ordinateur ou de traitement d'images ? Alors,…

4 mois ago