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)