Nous allons voir dans ce tutoriel comment implémenter un algorithme de détection de contour avec Python et OpenCV. La détection de contour est un exercice primordiale pour la vision assistée par ordinateur et la reconnaissance d’objet.
Installation et configuration de Python
Si ce n’est pas déjà fait, installer Python et configurer vos variables d’environnement
Installer la librairie opencv
python -m pip install opencv-python
N.B.: Il peut être intéressant de créer un environnement virtuel car les algorithmes n’utilisent pas les mêmes librairies d’OpenCV. L’important est de n’en choisir qu’une seule. opencv-python, opencv-python-contrib, opencv-python-headless et opencv-python-headless-contrib.
La première officielle contient les modules principaux, la deuxième a le plus de fonctionnalités, les deux autres sont des versions sans interface graphique.
d’autres modules sont très utiles comme numpy et matplotlib
python -m pip install numpy matplotlib
Pour importer ces modules dans un script Python
import cv2 import numpy as np import matplotlib.pyplot as plt
Dans un précédent tutoriel, nous avons vu les fonctions de bases du traitement d’images avec OpenCV. Nous allons voir dans ce tutoriel, comment mettre en place les étapes d’une détection de contour à partir d’une image et, surtout, détecter les contours de l’objet désiré dans l’image.
Ouvrir et afficher l’image
Dans notre programme principal, nous ouvrons, dimensionnons et affichons l’image. Nous placerons l’algorithme de détection de contour dans la fonction contourDetection().
def contourDetection(): #detection algorithm pass def main(args): ## Open and resize image base_img = cv2.imread( fileDir+fileName , cv2.IMREAD_UNCHANGED) h,w,c = base_img.shape width = 512 # specify new width scale_f = width/w height = h*scale_f base_img = cv2.resize(base_img, (int(width),int(height))) img = base_img.copy() cv2.imshow('orig', img) ## Process image contourDetection() ## wait ESC or q key = cv2.waitKey(0) & 0x0FF # wait indefinitely and get input if key == 27 or key == ord('q'): #key is ESC or q cv2.destroyAllWindows() return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]))
Algorithme simple de détection
Dans cet exemple, nous utilisons l’algorithme de base
- passage en niveau de gris
- floutage de l’image pour éliminer le bruit
- détection des bords dans l’image Canny()
- détection des contours findContours()
def contourDetection(img): # Conversion to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Blurr blur = cv2.GaussianBlur(gray, (5,5), 0) # Find Canny edges edged = cv2.Canny(blur, 30, 200) cv2.imshow('Canny', edged) # Finding Contours contours, hierarchy = cv2.findContours(edged, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE #cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE print("Number of Contours found = " + str(len(contours))) # Draw all contours (i.e -1) cv2.drawContours(img, contours, -1, (0, 255, 0), 3) cv2.namedWindow('Contours', cv2.WINDOW_NORMAL) cv2.imshow('Contours', img)
L’algorithme Canny est défini par quatre paramètres:
- un seuil1 pour le premier seuil d’hystérésis
- le seuil2 pour le deuxième
- la taille d’ouverture pour le filtre de Sobel
- l’utilisation d’une plus grande précision sur le gradient
L’algorithme de récupération des contours permet juste de hiérarchiser et filtrer les rebords:
Méthode de récupération du contour est basé sur la hiérarchie des contours. A savoir si un contour est enfant ou parent par rapport à un autre (enfant à l’intérieur du parent)
- cv2.RETR_LIST pas de hiérarchie
- cv2.RETR_EXTERNAL avec cette méthode les contours enfants ne sont pas conservé
- cv2.RETR_CCOMP propose deux niveaux de hierarchie, les limites extérieures des objets et les trous dans les objets
- cv2.RETR_TREE retourne une arborescence pour chaque contour
Méthode d’approximation du contour
- cv2.CHAIN_APPROX_NONE pas d’approximation. Enregistre tous les points du contour
- cv2.CHAIN_APPROX_SIMPLE supprime les points proches (ex: un rectangle aura un contour de 4 points)
Ce code fonctionne correctement sur des images simples ou avec un contraste bien défini. Pour des images plus complexes comme des photos, tous les bords seront tracés, il faut ajouter des fonctionnalités à l’algorithme afin de filtrer ce qui est indésirable.
Paramétrer la fonction de détection de contour
Une bonne pratique est de tester les paramètres de la fonction et pour cela rien de mieux qu’une visualisation en temps réel. Nous mettons en place des curseurs afin de faire varier les paramètres et tracer le résultat.
# Conversion to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Blurr blurr_size = 5 blur = cv2.GaussianBlur(gray, (blurr_size,blurr_size), 0) # Find Canny edges #cv2.Canny( image,threshold1, threshold2, apertureSize = 3, L2gradient = false ) low_thresh = 30 ratio =3.0 edged = cv2.Canny(blur, low_thresh, low_thresh*ratio ) cv2.imshow('Canny', edged) def change_blur(value): global blurr_size, low_thresh, ratio if value%2 == 1: blurr_size = value else: blurr_size = value+1 blur = cv2.GaussianBlur(gray, (blurr_size,blurr_size), 0) edged = cv2.Canny(blur, low_thresh, low_thresh*ratio ) cv2.imshow('Canny', edged) def change_ratio(value): global blurr_size, low_thresh, ratio ratio = (3.0-2.0)*value/100+2.0 # lower to upper ratio [2 ; 3] blur = cv2.GaussianBlur(gray, (blurr_size,blurr_size), 0) edged = cv2.Canny(blur, low_thresh, low_thresh*ratio ) cv2.imshow('Canny', edged) def change_thresh(value): global blurr_size, low_thresh, ratio low_thresh = value# blur = cv2.GaussianBlur(gray, (blurr_size,blurr_size), 0) edged = cv2.Canny(blur, low_thresh, low_thresh*ratio ) cv2.imshow('Canny', edged) cv2.createTrackbar('blurr', 'Canny', 5, 30, change_blur) cv2.createTrackbar('ratio', 'Canny', 100, 100, change_ratio) cv2.createTrackbar('thresh', 'Canny', 30, 200, change_thresh)
Fonction de seuillage
La fonction de seuillage cv2.threshold permet de sélectionner les pixels dont le niveau de gris est à un certain seuil. Cela permet de filtrer les zones plus ou moins claires d’une image. La fonction prend comme paramètres:
- l’image img
- le seuil considérer thresh [0-255]
- la valeur maximale à donner au pixel au dessus du seuil maxval [0-255]
- la méthode du threshold
- cv2.THRESH_BINARY en dessous du seuil, 0; au dessus, maxval
- cv2.THRESH_BINARY_INV en dessous du seuil, maxval; au dessus, 0
- cv2.THRESH_TRUNC en dessous du seuil, srcl; au dessus, thresh
- cv2.THRESH_TOZERO en dessous du seuil, 0; au dessus, src
- cv2.THRESH_TOZERO_INV en dessous du seuil, src; au dessus, 0
# Thresh thresh_min = 180 thresh_max = 255 ret, binary = cv2.threshold(blur, thresh, maxval, cv2.THRESH_TOZERO)
Filtrage
Il existe d’autres filtres dans OpenCV très utiliser qui vous permettront d’améliorer les résultats de la détection de contours. Vous pouvez les utiliser et visualiser leurs effets à l’aide de curseur afin de choisir un réglage optimal.
## bilateral blurr bilateral = cv2.bilateralFilter(img, 15, 75, 75) blur = bilateral ## dilate dil_size = 1 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dil_size,dil_size)) dilated = cv2.dilate(blur, kernel)
Recadrer l’image
Si vous avez du mal à trouver le contour d’un objet en particulier
Vous pouvez sélectionner une partie de l’image
y=100 x=100 h=50 w=50 sub_img = img[y:y+h, x:x+w]
Supprimer l’arrière plan
Vous pouvez également utiliser un algorithme pour retirer l’arrière plan
pip install rembg
from rembg import remove import cv2 base_img = cv2.imread( "input.png" ) img_bg = rembg.remove(img,alpha_matting=False, alpha_matting_erode_size=5) #cv2.imwrite("img_bg.png", img_bg) cv2.imshow('rembg', img_bg)