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 :
- L’extraction des contours dans l’image, qui nous permettra d’extraire un à un les objets.
- 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.
- 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.
2 commentaires
Réaliser un seuillage sur une image en niveau de gris – Pymotion · 11 décembre 2024 à 14h24
[…] Les artefacts que l’on peut distinguer viennent du fait que les bordures des objets ont une « valeur de transition » entre la couleur de l’objet et le fond. Cette valeur peut alors se retrouver en dessous du seuil sélectionné, rendant les contours moins nets. Certains lecteurs ont été confronté à cette problématique lors de la reconnaissance de formes. […]
Reconnaissance de couleurs – Pymotion · 11 décembre 2024 à 16h12
[…] avons vu la semaine dernière comment détecter une forme dans une image. L’image que nous avons traitée comportait différentes formes de différentes […]