Détecter les contours avec opencv findcontours - cv2 findcontours - opencv contours

Détecter-contours-opencv-findcontours

S'il y a une chose importante en analyse et traitement d'image, c'est bien d'être capable de

détecter les contours des objets qui s'y trouvent.

Car en effet, si tu es capable de faire cela, non seulement, tu seras capable d'obtenir la position des objets, de les déplacer dans l'image, mais aussi les extraire de l'image et de les mesurer.

Et il faut bien l'avouer être capable d'extraire des objets d'une image et de les mesurer c'est bien utile pour les tâches de reconnaissance d'image non? 

Sinon comment serais-tu capable d'obtenir l'aire, le périmètre, aspect ratio  des objets d'intérêt présents dans l'image. 

Mieux encore comment seras-tu capable d'envoyer cette image en entrée d'un réseau de neurones convolutif? 

    Sans cela impossible de faire de la reconnaissance, mais également de générer quoi que ce soit ! 

Car si tu es fan de machine learning et de deep learning comme moi je le suis, tu dois d'hors et déjà t'intéresser au GAN (Generative Adversarial Network) qui font fureur en ce moment même dans le domaine du traitement d'image et du deep learning !

Quoi qu'il en soit, tu l'as compris, dans ce tutoriel je vais te montrer comment détecter les contours d'un objet dans une image et donc comment utiliser la fonction

findcontours

d'openCV.

Dans cet article, tu trouveras donc :

Comment créer une image binaire et pourquoi il est nécessaire d'en faire une pour obtenir les contours des objets ?

Comment utiliser la fonction findcontours ?



GAN qui transforme la phrase "petit chien blanc mignon" en une vraie image de petit chien


Pourquoi et comment créer une image binaire 

Comme, je te l'ai dit dans la petite intro, si tu veux obtenir les contours de ton image, il faut préalablement

transformer celle-ci en image binaire.

Car la fonction findcontours prend en paramètre une image binaire. Pour ce faire, il y a énormément de technique.

Cependant, la plus simple reste encore le seuillage (automatique de préférence).

Car oui, il est possible de seuiller manuellement, mais en terme pratique ce n’est vraiment pas top.

Pour ce faire il va falloir utiliser la fonction

threshold


d'OpenCV.

Si tu veux j'ai fait tout un tuto sur cette dernière auquel tu peux accéder via ce lien.

Au pire, si le seuillage ne te dit pas, il te reste encore la possibilité de choisir une autre manière de segmenter ton image. Pour en savoir plus, tu peux regarder ce tuto sur la segmentation d'image.

Pour ce faire, nous allons utiliser le seuillage d'Otsu qui s'utilise comme je l'ai dit en appelant la fonction thresold.

Code :

ret,th=cv2.threshold(img, seuil,couleur, option)


Le premier paramètre est l'image à traiter.

Le second normalement devrait être la valeur du seuil mais étant donné que Otsu décide du seuil automatiquement, cet valeur correspondra à la valeur minimale de seuil que l'on autorise la fonction à retourner. 

Le troisième argument est la couleur que prendront les objets de l'image après avoir été seuillé. Le dernier paramètre "option" correspond à la méthode de seuillage que l'on souhaite employer. 

Dans notre cas comme j'ai dit que nous allions utiliser la méthode d'Otsu, ce paramètre aura pour valeur,

cv2.THRESH_BINARY+cv2.THRESH_OTSU.

exemple :

ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)


Exemple : Application de la méthode d’Otsu sur chaque canal de l’image HSV

import cv2 as cv
import numpy as np
img=cv.imread ("fleur.png");
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
h,s,v= cv.split(hsv)
ret_h, th_h = cv.threshold(h,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
ret_s, th_s = cv.threshold(s,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
ret_v, th_v = cv.threshold(s,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
cv.imwrite("th_h.png",th_h)
cv.imwrite("th_s.png",th_s)
cv.imwrite("th_v.png",th_v)

Résultat :

Seuillage d'Otsu sur le canal Saturation


Les seuils optimaux trouvés par Otsu pour les canaux teinte, saturation et luminosité sont respectivement 75, 168 et 114.

Remarquons que, la segmentation obtenue sur le canal luminosité n’a rien donné d’intéressant.

Celle du canal hue a permis d’extraire les fleurs blanches.

Enfin celle de la saturation a extrait toutes les fleurs sauf les blanches.

Afin d’obtenir une segmentation parfaite qui sépare toutes les fleurs du fond, il nous est nécessaire de fusionner le résultat obtenu en hue et en saturation.

Cette opération se fera par l’intermédiaire d’opérateurs binaires d’image.

Découpage des différents objets de l’image

Maintenant que nous avons vu comment retirer le fond d’une image, il serait bien qu’on apprenne à isoler les objets présents dans celle-ci. Cela se fait grâce aux fonctions

findContours, drawContours et boundingRect d’OpenCV. 


La fonction findcontours d'OpenCV


La fonction findContours nous

retourne les contours des objets présents dans une image binaire,


elle prend en paramètre une image binaire, le mode, qui indique à la fonction les contours dont nous souhaitons obtenir cela peut être les contours extérieurs, intérieurs ou les deux.

Enfin, l’argument method indique comment nous souhaitons que les contours soient représentés dans notre cas, ils seront représentés par une suite de points connectés.

contours, hierarchy = cv2.findContours(thresh,mode,method) 

DrawContours va permettre de

dessiner un à un  chacun des contours extraits


précédemment avec find contours sur des images vierges .

Elle prend en paramètre,  une image sur laquelle la fonction va dessiner les contours , l’indice du contour que l’on souhaite dessiner, la couleur que l’on souhaite donner à ce contour et enfin l’épaisseur que l’on souhaite, -1 si l’on souhaite que le contour soit rempli.

cv2.drawContours(image,contours,contourIdx,couleur, thickness) 

Pour finir boundingrect est une fonction qui

retourne les coordonnées de la boundingbox d’un contour

c’est-à-dire les coordonnées du carré de taille minimum contenant le contour. Cette fonction prend en paramètre le contour et retourne les coordonnées de sa bounding box.

x,y,w,h = cv.boundingRect(contours[i]) 

Voici le code qui grâce à ces trois fonctions nous permet de découper les objets présents dans l’image.
Code :

import cv2
import numpy as np
img=cv2.imread ("fleur.png");
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v= cv2.split(hsv)
ret_h, th_h = cv2.threshold(h,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
ret_s, th_s = cv2.threshold(s,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#Fusion th_h et th_s
th=cv2.bitwise_or(th_h,th_s)
#Ajouts de bord à l'image
bordersize=10
th=cv2.copyMakeBorder(th, top=bordersize, bottom=bordersize, left=bordersize, right=bordersize, borderType= cv2.BORDER_CONSTANT, value=[0,0,0] )
#Remplissage des contours
im_floodfill = th.copy()
h, w = th.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
cv2.floodFill(im_floodfill, mask, (0,0), 255)
im_floodfill_inv = cv2.bitwise_not(im_floodfill)
th = th | im_floodfill_inv
#Enlèvement des bord de l'image
th=th[bordersize: len(th)-bordersize,bordersize: len(th[0])-bordersize]
resultat=cv2.bitwise_and(img,img,mask=th)
cv2.imwrite("im_floodfill.png",im_floodfill)
cv2.imwrite("th.png",th)
cv2.imwrite("resultat.png",resultat)
contours, hierarchy = cv2.findContours(th,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for i in range (0, len(contours)) :
    mask_BB_i = np.zeros((len(th),len(th[0])), np.uint8)
    x,y,w,= cv2.boundingRect(contours[i])
    cv2.drawContours(mask_BB_i, contours, i, (255,255,255), -1)
    BB_i=cv2.bitwise_and(img,img,mask=mask_BB_i)
    if h >15 and w>15 :
        BB_i=BB_i[y:y+h,x:x+w]
        cv2.imwrite("BB_"+str(i)+".png",BB_i)


 Au début, c’est le même code que précédemment,  jusqu’à la ligne 26. À la ligne 26, on extrait de l’image binaire th les contours des fleurs, Ret_tree signifie que l’on veut tout les contours et chain_ approx_simple que les contours doivent être représentés par une suite de point. À la ligne 27,  il y a une boucle qui va itérer sur tous les contours, à la ligne 28 une image vierge est créée , la bounding box du contour courant est extraite a la ligne  29, la zone englobée par le contour courant est dessiné sur l’image vierge qui a été créée au début de la boucle ligne 30. Ligne 31, le masque nouvellement créé est appliqué sur  l’image fleur.png et l’imagette contenant uniquement la fleur  courante est créée grâce au coordonné de la bounding box à la ligne 33  enfin, l’imagette  est enregistrée ligne 34. Vous remarquerez que j’ai mis une condition à la ligne 32 afin d’éviter que trop d’images qui ne sont pas des fleurs soient extraites.   

Résultat :

Nous remarquons que toutes les fleurs ont été extraites  de manières individuelles, mais pas que beaucoup d’objets gênants ont aussi été extraits. Nous verrons prochainement comment automatiquement retirer ses objets.





 

 

.