Niveau
Débutant
Env.
Local (Windows), Local (Linux), Google Colab
Code
Python
Libs
skimage, scipy, matplotlib
Sources

Traitement d’images (partie 6: Filtres & Convolution)

Dans le précédent chapitre nous avons fait sans le savoir un premier pas vers les filtres. Ce principe de la petite fenêtre glissante nous allons le généraliser mais surtout aller plus loin en y ajoutant des opérations sur les valeurs de pixel. Nous allons donc aborder dans cet article une famille de filtre très utilisée par tous les logiciels de retouches (comme Photoshop ou Gimp). En fait et pour aller plus loin (sans non plus pour autant « sploiler » les articles suivants) ce principe de convolution va aussi être très utilisés par les réseaux de neurones (Deep Learning) … mais nous verrons cela plus tard. Focalisons nous tout d’abord sur le principe de filtre et plus précisément de convolution.

Principe de convolution

Comme je l’ai précisé en introduction nous allons garder le principe de la fenêtre glissante qui va donc parcourir toute l’image de haut en bas et de gauche à droite (même si en réalité l’ordre et le sens n’ont pas d’importance, mais c’est plus simple à visualiser pour la compréhension). Cette fenêtre glissante s’appelle le kernel (on voit bien ici la racine mathématique du concept). On trouve aussi les terminologies de noyau de convolution ou même de masque de convolution.

Etape N°1

Le principe est donc très simple. On superpose le kernel sur le coin gauche de la matrice de l’image, comme ci-dessous. Pour information le kernel est la matrice avec des seules valeurs 0,5 dans l’illustration ci-dessous. Ensuite nous allons multiplier chaque nombre superposé puis additionner le tout. Le résultat prendra sa place naturellement comme cela est montré dans la figure.

Notre premier pixel est donc recalculé, on peut passer au suivant. Pour ce faire on déplace juste le kernel d’un rang. Puis on refait exactement la même opération.

Etapes suivantes

On effectue cela bien sur pour tous les pixels de l’image à filtrer. On doit obtenir par ce procédé simple une nouvelle image (matrice) filtrée. Ce procédé est vraiment efficace d’une part et de plus très peu couteux pour la machine car seules des additions & multiplications sont effectuées. Nous venons de faire ce que l’on appelle un produit de convolution. Cette opération est de plus une application bilinéaire, associative et commutative.

Une petit détail demeure quand même! Avez-vous remarqué qu’il était impossible de calculer les pixels du bord ?

Heureusement les bords ne comprennent que rarement des pixels importants, et puis ils sont peu nombreux par rapport au reste de l’image. Il existe plusieurs stratégies donc pour remplir ces bords. On peut simplement les laisser à zéro, répliquer les valeurs à coté, calculer une moyenne des pixels alentours, etc.

Si vous avez compris le principe, vous allez maintenant en voir l’intérêt. En fait la clé du filtrage se trouve dans la création du noyau. Heureusement les mathématiciens ont déjà travaillé pour nous en fournissant des noyaux tout fait pour effectuer bon nombre d’opérations de filtrage. Nous allons en passer quelques uns en revue.

Pour mieux se familiariser avec ce concept (si quelqu’un à encore un doute), allez sur ce site https://setosa.io/ev/image-kernels/ vous pourrez jouer avec les filtres et voir directement les résultats.

Convolution avec Python

Nous n’allons pas utiliser de librairies toutes faites comme il en existe. Afin d’illustrer le principe que nous voyons de voir nous allons directement jouer avec les matrices/pixels. Nous utiliserons donc la librairie SciPy pour les opérations matricielles de convolution.

Commençons par importer quelques librairies et ajoutons une petite fonction de visualisation:

Python
import numpy as np
from skimage import data
import matplotlib as plt
from scipy import signal
from matplotlib.pyplot import imshow, get_cmap
import matplotlib.pyplot as plt
def displayTwoBaWImages(img1, img2):
  _, axes = plt.subplots(ncols=2)
  axes[0].imshow(img1, cmap=plt.get_cmap('gray'))
  axes[1].imshow(img2, cmap=plt.get_cmap('gray'))

Créons maintenant une image toute simple en noir et blanc:

Python
image_test = np.array([[0,0,0,0,0], 
                       [0,0,1,0,0], 
                       [0,1,1,1,0], 
                       [0,0,1,0,0], 
                       [0,0,0,0,0]])
imshow(image_test, 
       cmap=get_cmap('gray'))

Créons un noyau de convolution très simple

Python
kernel = np.ones((3,3), np.float32)/2

Demandons à Scipy de faire le produit de convolution

Python
imgconvol = signal.convolve2d(image_test, 
                              kernel, 
                              mode='same',
                              boundary='fill', 
                              fillvalue=0)
displayTwoBaWImages(image_test, imgconvol)

Si on regarde l’image (sa matrice) :

array([[0. , 0.5, 0.5, 0.5, 0. ],
       [0.5, 1.5, 2. , 1.5, 0.5],
       [0.5, 2. , 2.5, 2. , 0.5],
       [0.5, 1.5, 2. , 1.5, 0.5],
       [0. , 0.5, 0.5, 0.5, 0. ]])

Ce filtre a en quelque sorte créé un flou sur l’image de base comme on peut le voir.

Voyons d’autres filtres maintenant.

Détection de contours

Le noyau de convolution qui permet de détecter les contours est une matrice 3×3 toute simple :

Python
kernel_contour = np.array([[0,1,0], 
                       [1,-4,1], 
                       [0,1,0]])

Appliquons le filtre de convolution comme précédemment :

Python
imgconvol = signal.convolve2d(image, 
                              kernel_contour, 
                              boundary='symm', 
                              mode='same')
displayTwoBaWImages(image, imgconvol)
imshow(imgconvol, cmap=get_cmap('gray'))

Résultant plutôt bluffant n’est-ce pas ?

Augmentation de contraste

Le noyau de convolution est maintenant une matrice 5×5:

Python
kernel_inccontrast = np.array([[0,0,0,0,0], 
                               [0,0,-1,0,0], 
                               [0,-1,5,-1,0], 
                               [0,0,-1,0,0], 
                               [0,0,0,0,0]])
array([[ 0,  0,  0,  0,  0],
       [ 0,  0, -1,  0,  0],
       [ 0, -1,  5, -1,  0],
       [ 0,  0, -1,  0,  0],
       [ 0,  0,  0,  0,  0]])
Python
imgcontrast = signal.convolve2d(data.camera(), 
                              kernel_inccontrast, 
                              boundary='symm', 
                              mode='same')
displayTwoBaWImages(data.camera(), imgcontrast)

Flouttage

Python
kernel = np.array([[0,0,0,0,0], 
                    [0,1,1,1,0], 
                    [0,1,1,1,0], 
                    [0,1,1,1,0], 
                    [0,0,0,0,0]])
img = signal.convolve2d(data.checkerboard(), 
                        kernel, 
                        boundary='symm', 
                        mode='same')
displayTwoBaWImages(data.checkerboard(), img)

Renforcement de bords

Python
kernel = np.array([[0,0,0], 
                   [-1,1,0,], 
                   [0,0,0,]])
img = signal.convolve2d(data.camera(), 
                        kernel, 
                        boundary='symm', 
                        mode='same')
displayTwoBaWImages(data.camera(), img)

Conclusion

Il existe bon nombre de noyaux de convolution déjà fournis et qui permettent comme nous venons de le voir d’effectuer des opérations sur les images. Nous verrons dans un prochain article comment les réseaux de neurones à convolution vont trouver et combiner des filtres de convolution pour détecter des formes plus complexes.

Partager cet article

7 Replies to “Traitement d’images (partie 6: Filtres & Convolution)”

  1. Bonjour,
    Un grand merci pour vos articles passionnants.
    J’aimerais comprendre comment sont faits les filtres pour transformer une image colorée en nuances de gris?

    Ce qui m’intéresse est la valeur (graphique et non pas informatique) de la couleur :
    – en créant une image avec plusieurs couleurs différentes mais une seule valeur (par exemple la valeur visuelle claire : bleu très très clair et jaune plus fort)
    – puis en appliquant le filtre « vieillir – noir-blanc » de photofiltre, on obtient une image d’une seule valeur donc invisible

    Cependant en appliquant des couleurs de valeurs visiblement différentes à l’oeil nu (par exemple des verts plus ou moins lumineux et/ou foncés), le filtre donne aussi une image en gris d’une seule valeur et ne le devrait pas.

    Je me doute que le fait qu’il y ait dimension 3 aux couleurs et dimension 2 au noir-blanc entre en jeu et que sans doute la perception visuelle qui n’est pas mathématique ni linéraire ne peut être traduite de manière informatique….

    Merci d’avance pour votre réponse

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.