Tuto Python & Scipy : traitement d'images

Table des matières

Introduction

  1. Prérequis
  2. Traitement d’images avec Scipy

2.1. Les filtres

2.2. La réduction de bruit

2.3. Détection des bords

2.4. Morphologie mathématique : érosion et dilatation d’une image

  1. Exercices

3.1. Exercice 1

3.2. Exercice 2

  1. Solution des exercices:

4.1. Exercice 1

4.2. Exercice 2

Conclusion

Introduction

Scipy est une bibliothèque scientifique de Python construite sur la base de Numpy et facile à utiliser. Elle contient plusieurs fonctions mathématiques et scientifiques très performantes. Dans ce tutoriel, nous allons vous présenter le module ndimage de scipy spécialisé dans le traitement d’images. Vous allez découvrir comment générer des filtres, réduire le bruit, détecter les bords et implémenter des opérateurs de morphologie mathématique en utilisant le module ndimage. Ce tutoriel contient des scripts qui vont vous apparaitre un peu complexes au premier regard, mais accrochez-vous bien et suivez le tutoriel jusqu’au bout.

1. Prérequis

Commençons par importer les bibliothèques avec lesquelles nous allons travailler. Importez scipymatplotlib.pyplot (pour afficher les images), la bibliothèque numpy (pour manipuler les images) et skimage pour importer notre image de test et la transformer en nuances de gris. Nous vous conseillons d’aller voir le tutoriel « Les bases de traitement d'images en Python : Bibliothèque NumPy » avant de commencer celui-là.

Syntaxe:

import scipy
import skimage
import numpy as np
import matplotlib.pyplot as plt

Dans ce tutoriel, nous utiliserons une image téléchargée à partir de pixabay. Le script suivant importe notre image de test et stocke le résultat dans la variable image puis génère une image en nuances de gris (variablegrey_image) :

Syntaxe:

from skimage import io, color
image = io.imread('/path/image.jpg')
grey_image = color.rgb2grey(image)

Nous allons utiliser ces deux images tout au long du tutoriel. À titre de précision, l’image en nuances de gris générée à partir de la fonction rgb2grey() contient des valeurs de pixels normalisées (entre 0 et 1). Il faut multiplier par 255 pour avoir des valeurs entre 0 et 255.

2. Traitement d’images avec Scipy 

Comme nous l’avons précisé auparavant, nous allons utiliser l’image de test suivante :

Syntaxe:

plt.imshow(image)
plt.show()

Résultat d’exécution:

Importons le module ndimage de la bibliothèque scipy pour le traitement d’image :

Syntaxe:

from scipy import ndimage 

2.1. Les filtres

Le filtrage constitue un volet très important en traitement d'images (il permet d'appliquer divers effets sur les images). Dans cette partie, nous allons générer des filtres en utilisant le module ndimage, créer des images filtrées et comparer les histogrammes des pixels des deux images (originale et filtrée).

Pour générer une image avec le filtre gaussien, utilisez la fonction gaussian_filter() du module ndimage qui met en œuvre un filtre gaussien multidimensionnel. Cette fonction prend l’image originale en paramètres et génère une image filtrée. Le script suivant applique le filtre gaussien à notre image de test, affiche l’image filtrée et compare les histogrammes des deux images:

Syntaxe:

### filtre gaussian
gaussian_image = ndimage.gaussian_filter( image , sigma = 7)
plt.imshow( gaussian_image )
plt.show()
### histogramme des pixels
# image originale
plt.hist(image.flatten(), bins=20, density=True , alpha=.5 , edgecolor='black', color='red' , label='Image originale')
# image filtrée
plt.hist(gaussian_image.flatten(),bins=20, density=True ,alpha=.3 ,edgecolor='black', color='blue', label='filtre gaussian')
plt.legend()
plt.show()

Résultat d’exécution:

Pour générer une image avec le filtre médian, utilisez la fonction median_filter() du module ndimage:

Syntaxe:

### filtre median
median_image = ndimage.median_filter( image , size = 8)
plt.imshow( median_image )
plt.show()
### histogramme des pixels
# image originale
plt.hist(image.flatten(), bins=20, density=True , alpha=.5 , edgecolor='black', color='red' , label='Image originale')
# image filtrée
plt.hist(median_image.flatten(), bins=20, density=True , alpha=.3 , edgecolor='black', color='blue', label='filtre median')
plt.legend()
plt.show()

Résultat d’exécution:

Pour générer une image avec le filtre minimum, utilisez la fonction minimum_filter() du module ndimage:

Syntaxe:

### filtre minimum
minimum_image = ndimage.minimum_filter( image , size = 5)
plt.imshow( minimum_image )
plt.show()
### histogramme des pixels
# image originale
plt.hist(image.flatten(), bins=20, density=True , alpha=.5 , edgecolor='black', color='red' , label='Image originale')
# image filtrée
plt.hist(minimum_image.flatten(),bins=20, density=True , alpha=.3 , edgecolor='black', color='blue', label='filtre minimum')
plt.legend()
plt.show()

Résultat d’exécution:

Pour générer une image avec le filtre maximum, utilisez la fonction maximum_filter() du module ndimage:

Syntaxe:

### filtre maximum
maximum_image = ndimage.maximum_filter( image , size = 5)
plt.imshow( maximum_image )
plt.show()
### histogramme des pixels
# image originale
plt.hist(image.flatten(), bins=20, density=True , alpha=.5 , edgecolor='black', color='red' , label='Image originale')
# image filtrée
plt.hist(maximum_image.flatten(), bins=20, density=True , alpha=.3 , edgecolor='black', color='blue', label='filtre maximum')
plt.legend()
plt.show()

Résultat d’exécution:

2.2. La réduction de bruit

La réduction de bruit est une étape essentielle lors du traitement d’images visant à améliorer les résultats de traitements futurs (détection des bords par exemple). Les filtres gaussien et médian sont largement utilisés en traitement d'images numérique car ils permettent sous certaines conditions de réduire le bruit. Dans cette partie, nous allons générer des images bruitées à partir de notre image de test en nuance de gris (voir la section prérequis), appliquer les filtres et comparer les résultats.

- Bruit aléatoire :

Dans le script suivant, nous allons générer une image avec un bruit aléatoire à partir de notre image de test, afficher les deux images (originale et bruitée) et générer leurs histogrammes de pixels :

Syntaxe:

fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(15,5))
# image en nuance de gris
axs[0].imshow( grey_image, cmap = 'Greys_r')
axs[0].set_title( 'image originalle' )
axs[0].set_axis_off()
# image avec bruit aléatoire
noise_image = grey_image + 5 * grey_image.std() * np.random.random(grey_image.shape)
noise_image = np.uint8( ( noise_image / noise_image.max() ) * 255 )
axs[1].imshow( noise_image, cmap = 'Greys_r')
axs[1].set_title( 'image avec bruit aléatoire' )
axs[1].set_axis_off()
# histogramme des pixels
axs[2].hist(image.flatten(), bins = 20, density = True , alpha = .5 , edgecolor = 'black', color = 'red' , label = 'Image originale')
axs[2].hist( noise_image.flatten(), bins = 20, density = True , alpha = .3 , edgecolor = 'black', color = 'blue', label = 'Image avec bruit aléatoire')
axs[2].legend()
axs[2].set_title( 'Histogramme des pixels' )

Résultat d’exécution:

Nous pouvons maintenant appliquer le filtre gaussien à l’image avec le bruit aléatoire pour voir comment le filtre réduit le bruit (vous pouvez aussi appliquer le filtre médian et comparer les résultats) :

Syntaxe:

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(15,5))
# image avec bruit aléatoire
axs[0].imshow( noise_image, cmap = 'Greys_r')
axs[0].set_title( 'image avec bruit aléatoire' )
axs[0].set_axis_off()
# image bruitée avec filtre gaussien
gaussian_image = ndimage.gaussian_filter( noise_image , 3)
axs[1].imshow( gaussian_image, cmap = 'Greys_r')
axs[1].set_title( 'filtre gaussien' )
axs[1].set_axis_off()

Résultat d’exécution:

Vous remarquez bien qu’il y a une amélioration, mais peut-être vous vous dites que ce n’est pas assez important en terme de traitement. Nous vous conseillons de faire le premier exercice quand vous finissez le tutoriel pour mieux comprendre l’utilité de la réduction de bruit.

- Bruit poivre et sel :

Le bruit poivre et sel est une modification que subit une image numérique en faisant passer l'intensité de certains pixels à la valeur minimale 0 (couleur noire) ou maximale 255 (couleur blanche). Dans le script suivant, nous allons générer une image avec un bruit poivre et sel à partir de notre image de test, afficher les deux images (originale et bruitée) et générer leurs histogrammes de pixels :

Syntaxe:

fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(15,5))
# image en nuance de gris
axs[0].imshow( grey_image, cmap = 'Greys_r')
axs[0].set_title( 'image originalle' )
axs[0].set_axis_off()
# image avec bruit poivre et sel
x = grey_image.shape[0]
y = grey_image.shape[1]
noise = np.random.randint(10 , size = (x, y))
noise_image = np.where(noise == 0, 0, grey_image*255)
noise_image = np.where(noise == 9, 255, noise_image)
noise_image = np.uint8(noise_image)
axs[1].imshow( noise_image, cmap = 'Greys_r')
axs[1].set_title( 'image avec bruit poivre et sel' )
axs[1].set_axis_off()
# histogramme des pixels
axs[2].hist(image.flatten(), bins = 20, density = True , alpha = .5 , edgecolor = 'black', color = 'red' , label = 'Image originale')
axs[2].hist( noise_image.flatten(), bins = 20, density = True , alpha = .3 , edgecolor = 'black', color = 'blue', label = 'Image avec bruit poivre et sel')
axs[2].legend()
axs[2].set_title( 'Histogramme des pixels' )

Résultat d’exécution:

Nous pouvons maintenant appliquer le filtre médian à l’image avec le bruit poivre et sel pour voir comment le filtre réduit le bruit :

Syntaxe:

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(15,5))
# image avec bruit poivre et sel
axs[0].imshow( noise_image, cmap = 'Greys_r')
axs[0].set_title( 'image avec bruit aléatoire' )
axs[0].set_axis_off()
# image bruitée avec filtre médian
median_image = ndimage.median_filter( noise_image , 3)
axs[1].imshow( median_image, cmap = 'Greys_r')
axs[1].set_title( 'filtre médian' )
axs[1].set_axis_off()

Résultat d’exécution:

Vous remarquez que le filtre médian réduit (voire élimine) le bruit poivre et sel.

2.3. Détection des bords

Pour détecter les bords dans une image, vous pouvez utiliser la fonction sobel() du module ndimage qui implémente le détecteur de bords sobel. Le script suivant applique le filtre gaussien à notre image de test en nuance de gris pour réduire le bruit, puis implémente le détecteur de bords sobel et affiche le résultat :

Syntaxe:

# filtre gaussien pour réduir le bruit
grey_image_gaussian = ndimage.gaussian_filter( grey_image*255 , 3)
# détecteur de bords sobel
edges_image = grey_image_gaussian.astype('int32')
dx = ndimage.sobel(edges_image, 0)
dy = ndimage.sobel(edges_image, 1)
edges_image = np.hypot(dx, dy)
# affichage
plt.imshow(edges_image, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

2.4. Morphologie mathématique : érosion et dilatation d’une image

La dilatation et l'érosion sont des opérateurs de base de la morphologie mathématique. Pour les mettre en œuvre, nous créons d’abord l’image binaire suivante :

Syntaxe:

array = np.zeros((10,10), dtype=np.int)
array[1:7, 2:6] = 255
array[7,7] = 255
print( array )
plt.imshow( array, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

Vous pouvez implémenter l’érosion binaire en utilisant la fonction binary_erosion() du module ndimage :

Syntaxe:

array_erosion = ndimage.binary_erosion( array )
print( array_erosion*255 )
plt.imshow( array_erosion, cmap = 'Greys_r' )
plt.show()

Résultat d’exécution:

Lors de ce traitement, vous appliquez un filtre minimum de taille 3x3. Vous pouvez changer la taille en utilisant le paramètre structure dans la fonction binary_erosion(). L'opération qui en quelque sorte tente de produire l'inverse de l’érosion est la dilatation morphologique. Vous pouvez utiliser la fonction binary_dilation() du module ndimage pour appliquer une dilatation binaire.

Syntaxe:

array_dilation = ndimage.binary_dilation( array )
print( array_dilation * 255 )
plt.imshow( array_dilation, cmap = 'Greys_r' )
plt.show()

Résultat d’exécution:

Finalement, l’opération ouverture applique une érosion + une dilatation. Vous pouvez l’appliquer en utilisant la fonction binary_opening() :

Syntaxe :

array_3 = ndimage.binary_opening( array )
print( array_3 * 255 )
plt.imshow( array_3, cmap = ‘Greys_r’ )
plt.show()

Résultat d’exécution :

Dans le script suivant, nous allons appliquer les opérations précédentes sur notre image de test après la segmentation et afficher le résultat :

Syntaxe:

# segmentation
array_seg = grey_image*255 > 50
# affichage
print('segmentation seuil = 50')
plt.imshow(array_seg, cmap= 'Greys_r')
plt.show()
####
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(15,8))
# Liste des valeurs
filtres = ['erosion', 'dilation', 'opening']
for i, filtre in enumerate(filtres):
exec('test = ndimage.binary_'+filtre+'(array_seg)')
axs[i].imshow(test, cmap = 'Greys_r')
axs[i].set_title( filtre )
axs[i].set_axis_off()

Résultat d’exécution:

3. Exercices

3.1. Exercice 1 

Dans cet exercice nous utilisons une image importée à partir du module misc de la bibliothèque scipy. Le script est le suivant :

Syntaxe :

from scipy import misc
# impoter l'image en nuance de gris
image = misc.face(gray = True)
# Affichage
plt.imshow(image, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

Question 1: Ajoutez un bruit aléatoire à l’image précédente et affichez le résultat.

Question 2: Comparez les histogrammes des pixels des deux images (originale et avec bruit).

Question 3: Appliquez le filtre gaussien à l’image avec bruit et comparez l’histogramme de l’image générée (image filtrée) avec celui de l’image originale.

Question 4: Appliquez le détecteur des bords sobel sur les deux images (avec bruit et filtrée) et comparez le résultat.

3.2. Exercice 2 

Question 1: Créez l’image suivante en utilisant NumPy (Taille : 300x300) :

Question 2: Ajoutez un bruit poivre et sel à l’image précédente et affichez le résultat (stockez le résultat dans la variable noise_image). 

Question 3: Essayez d’éliminer le bruit de noise_image en utilisant le filtre median.

Question 4: Essayez d’éliminer le bruit de noise_image en utilisant que l’érosion et la dilatation.

4. Solution des exercices:

4.1. Exercice 1

Question 1:

Syntaxe :

# import numpy as np
# import matplotlib.pyplot as plt
# from scipy import ndimage
# ajout du bruit
noise_image = image + 3 * image.std() * np.random.random(image.shape)
noise_image = np.uint8( ( noise_image / noise_image.max() ) * 255 )
# affichage
plt.imshow(noise_image, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

Question 2:

Syntaxe :

# image originale en nuance gris
plt.hist(image.flatten(), bins = 20, density = True , alpha = .5 , edgecolor = 'black', color = 'red' , label = 'Image originale')
# image avec bruit
plt.hist( noise_image.flatten(), bins = 20, density = True , alpha = .3 , edgecolor = 'black', color = 'blue', label = 'Image avec bruit')
plt.legend()
plt.show()

Résultat d’exécution:

Question 3:

Syntaxe :

### filtre gaussien
gaussian_image = ndimage.gaussian_filter( noise_image , 3)
plt.imshow(gaussian_image, cmap='Greys_r')
plt.show()
### histogramme des pixels
# image originale
plt.hist(image.flatten(), bins = 20, density = True , alpha = .5 , edgecolor = 'black', color = 'red' , label = 'Image originale')
# image avec filtre gaussien
plt.hist( gaussian_image.flatten(), bins = 20, density = True , alpha = .3 , edgecolor = 'black', color = 'blue', label = 'Image filtrée')
plt.legend()
plt.show()

Résultat d’exécution:

Question 4:

Syntaxe :

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(15,8))
# Liste des images à afficher
images = ['noise_image', 'gaussian_image']
for i, image in enumerate(images):
# détecteur des bords (sobel)
exec("img = "+image+".astype('int32')")
dx = ndimage.sobel(img, 0)
dy = ndimage.sobel(img, 1)
img = np.hypot(dx, dy)
# affichage
axs[i].imshow( img, cmap = 'Greys_r')
axs[i].set_title( image )
axs[i].set_axis_off()

Résultat d’exécution:

4.2. Exercice 2

Question 1:

Syntaxe :

# import numpy as np
# import matplotlib.pyplot as plt
# from scipy import ndimage
# création de l'image en utilisant numpy
image = np.zeros( (300, 300), dtype=np.int)
image[0 : 100, 0 : 100] = 255
image[100 : 200, 100 : 200] = 255
image[200 : 300, 200 : 300] = 255
image[200 : 300, 0 : 100] = 255
image[0 : 100, 200 : 300] = 255
# affichage
plt.imshow( image, cmap = 'Greys_r' )
plt.show()

Résultat d’exécution:

Question 2:

Syntaxe :

# hauteur
H = image.shape[0]
# largeur
L = image.shape[1]
# bruit poivre et sel
noise = np.random.randint(10 , size = (H, L))
noise_image = np.where(noise == 0, 0, image)
noise_image = np.where(noise == 9, 255, noise_image)
noise_image = np.uint8( noise_image )
# Affichage de l'image avec bruit poivre et sel
plt.imshow( noise_image, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

Question 3:

Syntaxe :

# filtre median
median_image = ndimage.median_filter( noise_image , 4)
plt.imshow(median_image, cmap = 'Greys_r')
plt.show()

Résultat d’exécution:

Question 4:

Syntaxe :

# dilatation
array = ndimage.binary_dilation( noise_image, structure = np.ones( (2, 2) ) )
# érosion
array = ndimage.binary_erosion( array , structure = np.ones( (5, 5) ) )
# dilatation
array = ndimage.binary_dilation( array )
array = ndimage.binary_dilation( array )
# affichage
plt.imshow( array, cmap = 'Greys_r' )
plt.show()

Résultat d’exécution:

Conclusion

Si vous êtes arrivez jusqu'ici, je vous félicite pour votre détermination. J’espère que ce tutoriel vous sera utile à vous initier au module ndimage de scipy. Je vous conseille de refaire tous les scripts par vous-même en essayons de découvrir de nouvelles fonctionnalités. Merci d’avoir lu et bon courage pour la suite. 

Article publié le 06 Décembre 2020par Sami Nadif