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 :
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) :
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.
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) 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.
Vous avez commencé un projet de vision par ordinateur ou de traitement d'images ? Alors,…
Nous avons vu dans les précédents articles de quel matériel nous avions besoin pour créer…
Nous avons préparé la semaine dernière toute la partie matérielle du photobooth. Maintenant nous allons…
J'avais il y a quelques années réalisé un photobooth, afin de permettre aux convives de…
Détecter les voies de circulation est une étape incontournable pour la réalisation d’un système de…
Vous avez commencé un projet de vision par ordinateur ou de traitement d'images ? Alors,…
View Comments