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.
Détecter les voies de circulation est une étape incontournable pour la réalisation d’un système de…
Dans cet article, nous allons voir comment capturer simplement une image grâce au module raspicam…
Aujourd’hui nous allons utiliser Dlib et OpenCV pour détecter les repères faciaux (facial landmarks) dans…
Nous avons vu dans un précédent article comment utiliser l’algorithme MobileNetSSD pour la reconnaissance d’objet…
Dans cet article, nous nous intéressons à l’utilisation des réseaux de neurones convolutifs dans le…
Derrière ce titre un peu trompeur, nous n’allons pas trouver Charlie dans une image, mais…
View Comments