Python : détection d’un objet sur une image en utilisant le template-matching

Temps de lecture : 5 minutes

Suivre un intrus qui se balade sur des images, c’est notre mission dans cet article ! Sur une image, ce que nous allons faire ensemble est le suivant :

Détecter un objet, ici le disque en noir sur l’image en entrée en utilisant une technique populaire dans le traitement d’images : le template-matching.

[Code complet (Jupyter Notebook) et ressources sur Github]

Contexte pratique

Ceux qui ont lu mes autres articles sont probablement déjà familiers avec cette image en entrée. C’est une image type tirée de mes expériences de doctorat sur le mélange de matériaux granulaires. Deux populations de particules de forme circulaire sont mélangées sous l’effet du déplacement d’un intrus : le disque en noir qui fait une trajectoire spécifique. J’ai déjà écrit des articles sur le prétraitement de l’image, la détection des particules, le suivi des particules… etc. Mais ce qui nous intéresse aujourd’hui, c’est la détection de l’intrus (disque en noir). Ne perdons pas de temps, allons-y !

Position de la tige (intrus) à différents instants
Template-matching : qu’est ce que c’est ?

Oui, encore des mots anglais 🙃, mais pas de crainte, je t’explique ! Littéralement, « template » veut dire modèle ou patron et « matching » veut dire … concorder, correspondre (ça tu dois déjà le savoir si tu as Tinder installé sur ton téléphone 😉). Donc très simplement, la technique du template-matching consiste à promener un modèle dans toute notre image et à trouver les endroits de l’image où nous avons la meilleure correspondance avec le modèle ! Plutôt simple, non ? Mathématiquement, pour déterminer cette correspondance, on utilisera la corrélation croisée – ça c’est juste pour ta culture générale, je pourrais écrire un article spécifiquement là-dessus mais on ne fera pas des maths dans ce billet, tout est déjà implémenté dans Python !

Le template-matching est intéressant quand tu as un objet isolé dans l’image dont l’aspect varie très peu dans le temps. Cela te garantit une correspondance avec ton modèle à n’importe quel moment. C’est exactement le cas ici : notre disque noir mélangeur change de position dans le temps pour mélanger le milieu mais son aspect ne change pas et surtout aucune autre partie de l’image n’a le même aspect !

Implémentation du template-matching

Python propose la fonction match_template() dans la librairie skimage.feature. Dans son usage le plus simple, elle prend en entrée l’image et le modèle. Pas plus pour faire ce qu’on a envie de faire ici. Notre modèle c’est le disque noir qui se promène dans le milieu. Si on prend une image quelconque et qu’on zoome dessus, ça ressemble à ceci :

Modèle

On peut utiliser cette petite image de 31 x 31 comme modèle. Tu peux bien sûr choisir une autre partie, c’est comme tu veux, tant que c’est suffisamment représentatif de l’objet à détecter, ça devrait marcher.  

# Lecture de l'image
image = io.imread('./image.png', as_gray = True)

# Lecture du modèle
modele = np.load('./modele.npy')

# Template matching
result = match_template(image, modele)

N.B. : la taille de la matrice result est par défaut inférieure à celle de image. C’est normal, c’est une conséquence de ce qui se passe aux bords de l’image : quand tu parcours l’image avec le modèle, pour que le modèle ne sorte pas du cadre de l’image, tu dois en quelque sorte oublier quelques pixels sur les bords. tu peux changer ce comportement en modifiant l’option pad_input de match_template() … Mais bref, ceci reste un détail pour nous ici, je voulais juste attirer ton attention sur ce comportement.

Si nous revenons à nos moutons (oui, j’aime les vieilles expressions 😛), la matrice result contient nos coefficients de corrélation allant de 0 à 1. Dans un monde parfait, on devrait retrouver 1 à l’endroit où se trouve le disque… mais tu sais comment est le monde ! On n’obtient pas 1 mais on obtient quelque chose qui est proche de 1. Dans tous les cas, ce sera la valeur maximale de la matrice result. Tu peux faire max(result) pour l’afficher et surtout tu peux faire numpy.argmax(result) pour trouver la position de ce maximum dans la matrice… Le problème avec numpy.argmax(result) c’est qu’il va convertir notre matrice en vecteur ligne avant de nous donner la position du max, donc au lieu d’avoir une localisation (i,j) on aura une localisation avec un scalaire (une seule valeur)! Si tu ne me crois pas, fais le test !

Pour convertir la position du max en (i,j), on utilisera la fonction numpy.unravel_index(). Je ne vais pas trop expliquer son fonctionnement de A à Z ici, c’est ma façon de t’encourager à lire les docs (je sais, je suis sadique 😎).

# Recherche du point de correspondance maximale
ij = np.unravel_index(np.argmax(result), result.shape)

Donc désormais, nous avons le i et le j du maximum de correspondance i.e. le centre du disque. On peut désormais l’afficher sur notre image. Mais rappelle-toi (i,j) sont exprimées matriciellement (i = ligne, j = colonne). Si on veut les tracer sur un axe (x,y), x = j et y = i (c’est très facile de se faire avoir sur ça, je parle par expérience). On n’oubliera donc pas de faire la petite inversion :

x, y = ij[::-1] # conversion de ligne, colonne à coordonnées x,y

On y est, on peut tracer le point (x,y) et pour faire un truc sympa, on peut faire un petite rectangle ayant les dimensions de modele autour du point (x,y).

# Limites de la région de correspondance maximale
longueur, largeur = modele.shape
a = x + largeur/2
b = y + longueur/2

# Affichage d'un rectangle autour de la région
plt.imshow(image, cmap=plt.cm.gray)
rect = plt.Rectangle((x, y), largeur, longueur, edgecolor='r', facecolor='none')
plt.plot(a,b, 'rx')
plt.gca().add_patch(rect)
plt.axis('off')

Et voilà, c’est comme ça qu’on détecte la position de notre disque en utilisant le template-matching. Là, nous avons fait la démo sur une image mais bien sûr, l’intérêt final c’est de pouvoir utiliser le même modèle sur toutes les images de notre pile, donc si tu le fais sur 3 autres images par exemple, ça marche plutôt bien.

Moi j’avais près de 1000 images, et en utilisant cette technique avec le même modèle, je pouvais retrouver toute la trajectoire du disque par exemple :

Trajectoire complète du disque reconstituée par template-matching

Trajectoire sympa non ?

Partager

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *