Vous êtes sur la page 1sur 11

MP-ISI2 FSM-Monastir A.U.

2023-24

Chapitre 1
Introduction aux traitements d'images
I. Lire une image avec Python
Indépendament du traitement à appliquer à une image, il est souvent nécessaire de lire et charger
l’image sur l’espace de travail, afin de pouvoir travailler dessus. C’est pourquoi, nous allons
apprendre, avec un exemple de code et une base d’images, comment lire une image avec python à
l’aide de la bibliothèque 𝑃𝑖𝑙𝑙𝑜𝑤.

La bibliothèque Pillow
𝑃𝑖𝑙𝑙𝑜𝑤 est le successeur de 𝑃𝐼𝐿 (Python Imaging Library) et a pour principale vocation le
traitement d’images avec python.

Installation de Pillow
L’installation est assez simple et pratiquement la même dans les différents systèmes
d’exploitations: '𝑝𝑖𝑝 𝑖𝑛𝑠𝑡𝑎𝑙𝑙 𝑝𝑖𝑙𝑙𝑜𝑤'. Pour plus de détails, nous vous invitons à consulter la page
officielle ( https://pillow.readthedocs.io/en/stable/installation.html).

Lire une image


Pour lire une image avec Python et la bibliothèque 𝑝𝑖𝑙𝑙𝑜𝑤, il faut:
1. Importer le module ‘𝐼𝑚𝑎𝑔𝑒’ de la librairie ‘𝑃𝑖𝑙𝑙𝑜𝑤’ : '𝑓𝑟𝑜𝑚 𝑃𝐼𝐿 𝑖𝑚𝑝𝑜𝑟𝑡 𝐼𝑚𝑎𝑔𝑒'
2. Ouvrir l’image avec la fonction ‘𝑜𝑝𝑒𝑛’ : '𝐼𝑚𝑎𝑔𝑒. 𝑜𝑝𝑒𝑛(′𝑛𝑜𝑚_𝑖𝑚𝑎𝑔𝑒. 𝑓𝑜𝑟𝑚𝑎𝑡′)'1
3. Afficher l’image avec la fonction ‘𝑠ℎ𝑜𝑤’
En code source propre, ça donne:
#importer le package Image de la bibliothèque Pillow
from PIL import Image
# lire l'image de votre choix2
# Copier l’image dans votre espace de travail
imageLue = Image.open('feuilles.jpg')
#Afficher l'image
imageLue

Sélection d’image avec une boite de dialogue

1
Plus de 30 formats d’images peuvent être lues, selon la documentation officielle de Pillow
(https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).
2 Image à prendre de la base de données https://www.kaggle.com/nitishabharathi/scene-classification , dont le but est la

classification de 6 scènes naturelles (ci-dessous un aperçu de son contenu)


MP-ISI2 FSM-Monastir A.U. 2023-24

Le code précèdent nous a permis de lire une image dans un emplacement précis, c’est à dire que
le chemin de l’image est bien précisé. Maintenant comment faire pour lire une image à partir de
n’importe quel emplacement?

Pour notre cas, nous proposons la boite de dialogue de la bibliothèque d’interfaces graphiques
𝑇𝐾𝑖𝑛𝑡𝑒𝑟 (https://docs.python.org/fr/3/library/tkinter.html), qui a la particularité d’être livré avec
l’installation de python, et donc aucune installation de plus ! Néanmoins, si ce n’est pas le cas un
simple 𝑝𝑖𝑝 𝑖𝑛𝑠𝑡𝑎𝑙𝑙 𝑡𝑘𝑖𝑛𝑡𝑒𝑟 fait l’affaire généralement !

Passons maintenant au code, là où il faut faire appel au module d’ouverture de boite de dialogue,
en l’important via 𝑡𝑒𝑟. 𝑓𝑖𝑙𝑒𝑑𝑖𝑎𝑙𝑜𝑔 𝑖𝑚𝑝𝑜𝑟𝑡 𝑎𝑠𝑘𝑜𝑝𝑒𝑛𝑓𝑖𝑙𝑒𝑛𝑎𝑚𝑒, le code python complet devient
alors :

#importer le package Image de la bibliothèque Pillow


from PIL import Image
#importer la librairie pour la boite de dialogue
from tkinter.filedialog import askopenfilename
#récupérer le chemin de l'image via la boite de dialogue
chemin=askopenfilename(file='Selectionner une image', filetypes=[('JPEG
files','.jpg'),('all files','.*')])
#lire l'image
imageLue = Image.open(chemin)
#Afficher l'image
imageLue.show()
• Ce code tel qu’il est peut ouvrir seulement les images JPEG, mais avec des ajouts, il peut ouvrir
d’autres formats

• Cette fiche est dans un but pédagogique, et en python il y a beaucoup de partisans de minimum
de lignes la lecture de l’image avec la boite de dialogue peut se faire directement:

𝑖𝑚𝑎𝑔𝑒𝐿𝑢𝑒 = 𝐼𝑚𝑎𝑔𝑒. 𝑜𝑝𝑒𝑛(𝑎𝑠𝑘𝑜𝑝𝑒𝑛𝑓𝑖𝑙𝑒𝑛𝑎𝑚𝑒(𝑓𝑖𝑙𝑒 = ’𝑆𝑒𝑙𝑒𝑐𝑡𝑖𝑜𝑛𝑛𝑒𝑟 𝑢𝑛𝑒 𝑖𝑚𝑎𝑔𝑒’, 𝑓𝑖𝑙𝑒𝑡𝑦𝑝𝑒𝑠


= [(‘𝐽𝑃𝐸𝐺 𝑓𝑖𝑙𝑒𝑠’, ’. 𝑗𝑝𝑔’), (‘𝑎𝑙𝑙 𝑓𝑖𝑙𝑒𝑠’, ’.∗ ’)]))

II. Sauvegarder une image avec python


Souvent, après des traitements d’images : des transformations (conversion en niveau de gris,
passer à un autre espace couleur, redimensionner l’image…) ou des améliorations (rehaussement de
contraste, enlever le bruit, …), il faut penser à enregistrer les résultats obtenus en forme d’images.
Avec la bibliothèque 𝑃𝑖𝑙𝑙𝑜𝑤, il est possible d’enregistrer des images en utilisant la fonction ‘𝑠𝑎𝑣𝑒’. Cet
enregistrement se fait dans plusieurs formats : png, bitmap, jpeg,…

Formats d’images
𝑃𝑖𝑙𝑙𝑜𝑤 supporte plusieurs formats d’images : https://pillow.readthedocs.io/en/stable/handbook/image-file-
formats.html. Quand nous voulons enregistrer une image, il faut faire appel à la fonction 𝑠𝑎𝑣𝑒 de
l’image à enregistrer et spécifier le format voulu :
𝑖𝑚𝑎𝑔𝑒_𝑎_𝑒𝑛𝑟𝑒𝑔𝑖𝑠𝑡𝑟𝑒𝑟. 𝑠𝑎𝑣𝑒(′𝑛𝑜𝑚_𝑖𝑚𝑎𝑔𝑒. 𝑓𝑜𝑟𝑚𝑎𝑡′, ′𝑓𝑜𝑟𝑚𝑎𝑡′)
NB: si le format n’est pas spécifié, alors il va être déterminé du fichier de l’image, quand cela est
possible.

Code python pour la sauvegarde d’image


Nous reprenons le premier exemple de lecture d’image, à qui nous ajoutons la sauvegarde de
l’image JPEG, en image PNG et en image Bitmap.
MP-ISI2 FSM-Monastir A.U. 2023-24

from PIL import Image


#lire l'image 4.jpg
#n'oubliez pas de copier le dossier train dans votre espace #de
travail et changer le chemin si nécessaire
imageLue = Image.open("formes.png")
#sauvegarder l'image en format bitmap
imageLue.save('bmp_version.bmp', 'bmp')
Code python pour sauvegarder une image au format bitmap
NB : L’image utilisée est issue de la base https://www.kaggle.com/nitishabharathi/scene-classification

III. Convertir une image au niveau de gris


Une image au niveau de gris est composée de valeurs allant du blanc au noir. Le nombre de ces
valeurs est dépendant du codage en bits de l’appareil : de 4 bits à 16 bits, ce qui offre des valeurs au
niveau de gris de 16 à 65536 (24 à 216). Le codage le plus fréquent est celui de 8 bits, ce qui donne
256 valeurs. Les 256 couleurs possibles prennent des valeurs de 0 à 255, où le 0 représente le noir et
le 255 représente le balnc.
Convertir une image couleur à une image au niveau de gris est souvent nécessaire pour des
traitements en vision par ordinateur, tel que la détection de visage et bien d’autres opérations. Cette
conversion avec python à l’aide de la bibliothèque 𝑃𝑖𝑙𝑙𝑜𝑤 est possible avec la fonction 𝑐𝑜𝑛𝑣𝑒𝑟𝑡 du
module 𝐼𝑚𝑎𝑔𝑒. Avant de voir le code, il est important de signaler que la conversion au niveau de gris
est une opération mathématique, où il faut, généralement, suivre les recommandations de la
Commission Internationale de l’Éclairage.

Conversion au niveau de gris avec 𝑷𝒊𝒍𝒍𝒐𝒘


La librairie 𝑃𝑖𝑙𝑙𝑜𝑤 (https://pillow.readthedocs.io/en/stable/reference/Image.html) se base sur la
recommandation 601 de la Commission Internationale de l’Éclairage, qui a pour formule de
conversion de l’espace couleur RGB (Rouge Vert et Bleu) vers le niveau de gris par :
𝐿 = 𝑅 ∗ 0.299 + 𝐺 ∗ 0.587 + 𝐵 ∗ 0.114
L : niveau de gris obtenu
Pour le code python, nous appelons la fonction 𝑐𝑜𝑛𝑣𝑒𝑟𝑡 avec l’argument 𝑚𝑜𝑑𝑒 en ‘𝐿‘, et ça
donne :
#importer le package Image de la bibliothèque Pillow
from PIL import Image
#copier le dossier de la plateforme Kaggle dans votre espace de travail
# https://www.kaggle.com/nitishabharathi/scene-classification
imageLue = Image.open("camera.png")
#Convertir l'image au niveau de gris
imageGris = imageLue.convert("L")
#Afficher l'image au niveau de gris
imageGris
Nous vous recommandons maintenant de:
 Changer l’image pour voir les résultats que ça donne
 Essayer des images au niveau de gris, et voyez par vous-même que rien ne va se passer

IV. Compresser les images avec Python


MP-ISI2 FSM-Monastir A.U. 2023-24

Une image lourde c’est une image qui prend beaucoup de places en mémoire, et ça a pour
conséquences un ralentissement dans les traitements, dans le chargement de site/blog et bien
d’autres … La solution est de réduire sa taille.
Réduire la taille de l’image avec python est parmi les opérations de bases de toutes les librairies
de traitement d’images, tel que : 𝑜𝑝𝑒𝑛𝑐𝑣 − 𝑝𝑦𝑡ℎ𝑜𝑛, 𝑠𝑖𝑐𝑘𝑖𝑡 − 𝑖𝑚𝑎𝑔𝑒, 𝑝𝑖𝑙𝑙𝑜𝑤 et bien d’autres. Dans
la suite, nous allons continuer avec 𝑃𝑖𝑙𝑙𝑜𝑤, pour montrer comment, avec le langage python,
compresser une image.
Nous voyons la réduction de la taille de l’image de deux manières:
1. Passer un format connu pour son pouvoir de compression et par conséquent conserver les
dimensions initiales de l’image
2. Ou réduire les dimensions de l’image: diviser l’image en 2, en 3 …
Utiliser un format de compression
Le JPEG est un format d’images, qui par définition cible la compression d’images. Réduire la taille
en sauvegardant l’image sous forme JPEG peut se relever efficace. Il faudra appeler la fonction 𝑠𝑎𝑣𝑒
et spécifier le format 𝐽𝑃𝐸𝐺, le code python sera alors:
#importer le package Image de la bibliothèque Pillow
from PIL import Image
#lire l'image
imageLue = Image.open("motif.bmp")
#sauvegarder l'image au format JPEG
imageLue.save("motif.jpg")
Après l’exécution du code, nous obtenons une nouvelle image JPEG de
37 ko de taille. C’est 62 fois plus inférieur à la taille de l’image originale
et la qualité à vue d’œil est restée la même.
JPEG et alpha
Sur certains formats d’images, nous trouvons le composant alpha (la transparence). Cette
dernière ne peut être transformée directement par le format JPEG et il faut absolument convertir au
système classique RGB, avant de sauvegarder au format JPEG. Le code suivant est la mise à jour du
précèdent, en s’assurant une conversion au format RGB avant la sauvegarde au JPEG.
#importer le package Image de la bibliothèque Pillow
from PIL import Image
#lire l'image
imageLue = Image.open("motif.bmp")
#convertir au format RGB
imageRGB = imageLue.convert("RGB")
#sauvegarder l'image au format JPEG
imageRGB.save("motif.jpg")
Réduire les dimensions de l’image
La fonction 𝑟𝑒𝑠𝑖𝑧𝑒 du module Image de la librairie 𝑃𝑖𝑙𝑙𝑜𝑤, va nous permettre de réduire la
dimension de l’image. Notez toutes fois que 𝑟𝑒𝑠𝑖𝑧𝑒 sert à redimensionner l’image dans les deux sens
: réduire ou augmenter selon la dimension choisit par rapport à la dimension d’origine. La fonction
𝑟𝑒𝑠𝑖𝑧𝑒 attend comme argument un tuple contenant la taille souhaitée (largeur x hauteur). Ici nous
voulons diviser la taille de l’image en 2, donc, nous allons commencer par récupérer la dimension
d’origine et la diviser en 2 dans la dimension compressée, et cela nous donne le code suivant:
#importer le package Image de la bibliothèque Pillow
from PIL import Image
imageLue = Image.open("motif.bmp")
#taille de l'image
dim=imageLue.size
#redimensionner
imageComp=imageLue.resize((int(dim[0]/2.),int(dim[1]/2.)))
#sauvegarder l'image réduite
MP-ISI2 FSM-Monastir A.U. 2023-24

imageComp.save("motif2.bmp")
#récupérer la dimension de l'image compressée
dimComp=imageComp.size
#Affichage des dimensions
print("Dimension d'origine: ",dim,"\nDimension compressée:",dimComp)

V. Manipulations d'histogrammes
Nous abordons ici les techniques de traitement d'images basées sur la modification
d'histogrammes. Ces méthodes font partie de la classe des traitements dits ponctuels : la valeur de
chaque pixel est corrigée, et ce indépendamment des autres pixels.
Qu'est-ce qu'un histogramme dans le domaine de l'imagerie numérique ?
L'histogramme d'une image numérique est une courbe statistique représentant la répartition de
ses pixels selon leur intensité. Pour une image en noir et blanc, il indique en abscisse le niveau de gris
(entier entre 0 et 255) et en ordonnée, le nombre de pixels ayant cette valeur.
Lorsque l'histogramme est normalisé, il indique en ordonnée la probabilité pi de trouver un pixel
de niveau de gris i dans l'image :
𝑛𝑜𝑚𝑏𝑟𝑒 𝑑𝑒 𝑝𝑖𝑥𝑒𝑙𝑠 𝑑′ 𝑖𝑛𝑡𝑒𝑛𝑠𝑖𝑡é 𝑖
∀𝑖 ∈ {0, ⋯ ,255}, 𝑝𝑖 =
𝑛𝑜𝑚𝑏𝑟𝑒 𝑡𝑜𝑡𝑎𝑙 𝑑𝑒 𝑝𝑖𝑥𝑒𝑙𝑠
L'intensité d'un pixel est alors vue comme une variable aléatoire discrète.
Un histogramme cumulé normalisé calcule le pourcentage de pixels ayant une valeur inférieure à
un niveau de gris donné :
𝑖

∀𝑖 ∈ {0, ⋯ ,255}, 𝑃𝑖 = ∑ 𝑝𝑘
𝑘=0
L'histogramme normalisé peut être interprété comme une densité de probabilité, et l'histogramme
cumulé normalisé comme la fonction de répartition.
La génération d'histogrammes se fait aisément en Python avec la fonction ℎ𝑖𝑠𝑡 de
𝑚𝑎𝑡𝑝𝑙𝑜𝑡𝑙𝑖𝑏. 𝑝𝑦𝑝𝑙𝑜𝑡 :
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image

# Charger l'image comme matrice de pixels


im1 = np.array(Image.open(' feuilles.png'))

# Générer et afficher l'histogramme


# Pour le normaliser : argument density=True dans plt.hist
# Pour avoir l'histogramme cumulé : argument cumulative=True
n, bins, patches = plt.hist(im1.flatten(), bins=range(256))
plt.show()

Nombre de pixels

Niveau de gris
Figure 1 : Image test et son histogramme
MP-ISI2 FSM-Monastir A.U. 2023-24

Il s'agit d'un outil très important en traitement d'images, car sa modification permet d'ajuster la
dynamique des niveaux de gris ou des couleurs dans une image afin de la rendre plus agréable
visuellement. Grossièrement, à gauche se situent les pixels noirs, à droite les pixels blancs, et au milieu,
toutes les nuances de gris.
Nous pouvons constater que l'image de la Figure 1 est trop sombre, ou sous-exposée, la majorité
des pixels se situent dans la partie gauche de l'histogramme, vers les valeurs de niveaux de gris faibles.
Inversement, les pixels de l'image trop claire, ou sur-exposée, se concentrent dans la partie droite
de l'histogramme.
Étirement d’histogrammes
Une première application consiste à corriger la luminosité, ou exposition, de l'image. Analysons la
forme des histogrammes pour des images dont l'exposition est mauvaise. Ainsi, pour corriger les
défauts liés à l’exposition d'une image, il suffit simplement d'étirer son histogramme : l'objectif est
d'étendre les valeurs des niveaux de gris de l'image mal exposée, majoritairement répartis dans un
sous intervalle [Imin,Imax]⊂[0,255] , à tout l'intervalle disponible. Cette transformation se fait
simplement à l’aide de la règle de trois : la valeur de chaque pixel est remplacée par le résultat de la
formule ci-dessous.
255 × (𝐼(𝑥, 𝑦) − 𝐼𝑚𝑖𝑛 )
𝐼 ′ (𝑥, 𝑦) =
𝐼𝑚𝑎𝑥 − 𝐼𝑚𝑖𝑛
où 𝐼(𝑥, 𝑦) et 𝐼 ′ (𝑥, 𝑦) désignent les intensités du pixel de coordonnées (x,y) respectivement dans
l'image mal exposée et la nouvelle image.
Concrètement, cette formule mathématique signifie qu'on déplace les niveaux de gris de
[𝐼𝑚𝑖𝑛 , 𝐼𝑚𝑎𝑥 ] vers [0, 𝐼𝑚𝑎𝑥 − 𝐼𝑚𝑖𝑛 ], avant de les répartir dans [0 , 255].

En pratique, 𝐼𝑚𝑖𝑛 et 𝐼𝑚𝑎𝑥 sont souvent déterminées comme les niveaux de gris minimum et
maximum de l'image après avoir éliminé 1% des valeurs extrêmes.
L'étirement d'histogramme se fait donc avec la fonction 𝒂𝒖𝒕𝒐𝒄𝒐𝒏𝒕𝒓𝒂𝒔𝒕 du module 𝑰𝒎𝒂𝒈𝒆𝑶𝒑𝒔
de 𝑷𝒊𝒍𝒍𝒐𝒘. On l'applique sur l'image sous-exposée de la Figure 1 (voir Figure 2).

# Importing Image and ImageOps module from PIL package


from PIL import ImageOps
# applying autocontrast method
im2 = ImageOps.autocontrast(im1, cutoff = 1)
img2.save("feuilles-Rehaus.jpg")
im2.show()

Figure 2 : Etirement d'histogramme de l’image test


Le résultat est satisfaisant : les pixels se répartissent bien dans tout l'intervalle [0,255] et l'image
présente une meilleure luminosité ! En guise de petit exercice, vous pouvez appliquer la
transformation sur l'image surexposée.
MP-ISI2 FSM-Monastir A.U. 2023-24

Égalisation d’histogrammes
La deuxième application courante concerne l'amélioration du contraste de l'image. Le contraste
caractérise la répartition de lumière dans une image : plus une image est contrastée, plus la différence
de luminosité entre ses zones claires et sombres est importante. En général, une image peu contrastée
est terne, tandis qu'une image trop contrastée est visuellement "agressive". Dans les deux cas, l'image
manque de clarté car certains de ses détails seront peu, voire pas du tout, visibles.
L'égalisation d'histogrammes est une technique simple permettant de réajuster le contraste d'une
image et ainsi de lui redonner du dynamisme ou de l'adoucir. Pour comprendre de manière intuitive
le fonctionnement de ce traitement, étudions l'allure de l'histogramme pour des images peu
contrastées (voir Figure 1).
Comme vous pouvez le constater, les pixels des images dont le contraste est mauvais se répartissent
dans tout l'intervalle disponible – donc un étirement d'histogramme n'améliore pas le contraste de
manière équitable (voir Figure 2).
L'objectif est donc d'harmoniser la distribution des niveaux de gris de l'image, de sorte que chaque
niveau de l'histogramme contienne idéalement le même nombre de pixels. Concrètement, on essaye
d'aplatir au maximum l'histogramme original.
Pour cela, nous calculons d'abord l'histogramme cumulé normalisé de l'image, puis nous ajustons
la valeur de chaque pixel en utilisant la formule mathématique suivante :
𝐼(𝑥,𝑦)

𝐼 ′ (𝑥, 𝑦) = 255. ∑ 𝑝𝑖
𝑖=0
où 𝑝𝑖 désigne la probabilité qu'un pixel de l'image initiale soit d'intensité 𝑖.

Pour mieux comprendre cette formule, rappelez-vous que le but est de répartir les niveaux de gris
de l'image le plus équitablement possible sur [0,255]. Autrement dit, nous voulons imposer la loi de
probabilité 𝐼′(𝑥, 𝑦) comme étant uniforme discrète sur cet intervalle. Nous exploitons alors la loi de
𝐼(𝑥,𝑦 )
𝐼(𝑥, 𝑦) : sa fonction de répartition, donnée par ∑𝑖=0 𝑝𝑖 , est multipliée par 255. Si l’on considère
𝐼(𝑥, 𝑦) et 𝐼′(𝑥, 𝑦) comme des variables aléatoires continues, respectivement 𝐼𝑐(𝑥, 𝑦) et 𝐼′𝑐(𝑥, 𝑦), il
est prouvé mathématiquement que 𝐼′𝑐(𝑥, 𝑦) est uniformément distribuée sur [0,255].
L'égalisation d'histogramme correspond à la fonction 𝑃𝐼𝐿. 𝐼𝑚𝑎𝑔𝑒𝑂𝑝𝑠. 𝑒𝑞𝑢𝑎𝑙𝑖𝑧𝑒. L'application de
ce traitement sur l'image peu contrastée nous donne :

Figure 3 : Égalisation d'histogramme de l’image test


L'image est plus contrastée et son histogramme confirme que la distribution de ses niveaux de gris
est plus uniforme.
En pratique, les histogrammes après égalisation ne sont pas parfaitement plats,
comme le montre l'exemple précédent (Figure 3). Cela est dû au fait que les
intensités des pixels sont discrètes. En effet, la preuve mathématique énoncée ci-
dessus part du principe que les intensités sont des variables aléatoires continues.
Si cette hypothèse n'est pas vérifiée, on observe une distribution des intensités plus
uniforme qu'avant égalisation, et non parfaitement uniforme.
MP-ISI2 FSM-Monastir A.U. 2023-24

Néanmoins, si l'histogramme de l'image corrigée n'est visiblement pas plat, l'histogramme cumulé
normalisé montre que la fonction de répartition semble être linéaire comme celle de la loi uniforme :

Figure 4 : Histogramme cumulé normalisé de l'image peu contrastée après égalisation

VI. Transformations géométriques


Une autre catégorie de traitement ponctuel regroupe les transformations géométriques, qui
modifient la position des pixels dans l'image. Une rotation, une translation ou un changement d'échelle
(zoom) en sont des exemples typiques.
Une transformation géométrique est caractérisée par une matrice et un vecteur réels,
respectivement notés 𝑇 et 𝑉. Les nouvelles coordonnées (𝑥′, 𝑦′) du pixel de position initiale (𝑥, 𝑦) sont
𝑥′ 𝑥
déterminées selon l'équation ci-dessous : ( ) = 𝑇 (𝑦) + 𝑉
𝑦′
Plus précisément, les rotations, translations et zooms s'expriment de la manière suivante :
𝑥′ 1 0 𝑥 𝑡
 Translation de vecteur (𝑡1 𝑡2 )⊤ : ( ) = ( ) ( ) + (𝑡1 )
𝑦′ 0 1 𝑦 2
𝑥′ 𝜆1 0 𝑥
 Zoom de coefficients 𝜆1 et 𝜆2 : ( ) = ( )( )
𝑦′ 0 𝜆2 𝑦
𝑥′ 𝑐𝑜𝑠(𝜃) −𝑠𝑖𝑛(𝜃) 𝑥
 Rotation d'angle 𝜃 : ( ′ ) = ( )( )
𝑦 𝑠𝑖𝑛(𝜃) 𝑐𝑜𝑠(𝜃) 𝑦
Certaines nouvelles coordonnées risquent de sortir des bornes de l'image. Celle-ci doit
alors être agrandie afin de pouvoir afficher tous les pixels qui se trouvent en dehors.
Rappelons qu'une image numérique est définie sur un ensemble discret : les coordonnées (𝑥, 𝑦)
d'un pixel sont entières, de sorte que son intensité se trouve à la 𝑦-ème ligne et 𝑥-ème colonne de la
matrice représentative.
Or, ces transformations, basées sur la géométrie euclidienne, sont définies dans un repère
cartésien qui utilise des coordonnées réelles. Il s'agit donc d'une application qui va de ℕ2 dansℝ2 ;
par exemple, considérez une translation de vecteur (0.5 0.5)⊤ . Afin de pouvoir représenter l'image
numérique transformée, il est nécessaire d'arrondir les nouvelles coordonnées réelles vers les entiers
les plus proches.
Cependant, ces arrondis ne garantissent pas un parcours de tous les pixels de l'image finale.
Celle-ci risque alors de présenter des "trous", dus aux valeurs manquantes de sa matrice. Pour éviter
ce problème, on utilise la transformation géométrique inverse, qui permet de passer de l'image
transformée à l'image originale :
𝑥′ 𝑥
( ) = 𝑇 −1 (𝑦) − 𝑉
𝑦′
Le pixel de coordonnées (𝑥′, 𝑦′) dans l'image transformée prend ainsi la valeur du pixel de
coordonnées (𝑥′′, 𝑦′′) dans l'image initiale.
Mais... Ne risque-t-on pas, encore une fois, d'obtenir des coordonnées réelles en sortie ?
MP-ISI2 FSM-Monastir A.U. 2023-24

Bien vu ! La transformation inverse nous assure une exploration de tous les pixels de l'image
transformée, mais il s'agit à nouveau d'une application qui va de ℕ2 dans ℝ2 . Le problème n'est donc
pas tout à fait résolu, puisque les coordonnées (𝑥′′, 𝑦′′) ne sont pas nécessairement entières !
Dans ce cas, comment déterminer l'intensité associée à un point de
coordonnées réelles dans l'image originale ?
En fait, il faut coupler la transformation inverse avec une méthode d'interpolation, qui va associer
à chaque couple de coordonnées dans l'image originale une valeur déterminée à partir des intensités
des pixels voisins. Il existe deux modes d'interpolation principaux :
 L'interpolation au plus proche voisin : la nouvelle valeur est déterminée comme
l'intensité du pixel le plus proche. Il s'agit de la technique d'interpolation la plus simple et
la plus rapide, mais en contrepartie, elle offre une moins bonne qualité visuelle
 L'interpolation bilinéaire : la nouvelle valeur est calculée à partir des intensités des
quatre pixels voisins
Le module 𝑃𝐼𝐿. 𝐼𝑚𝑎𝑔𝑒 contient les méthodes permettant d'appliquer les
transformations géométriques présentées. Leur paramètre d'entrée 𝑟𝑒𝑠𝑎𝑚𝑝𝑙𝑒
donne la possibilité de choisir le mode d'interpolation, et 𝑒𝑥𝑝𝑎𝑛𝑑 d'agrandir l'image
au cas où des pixels se trouvent en dehors des bornes après transformation.
Comparons les effets des interpolations au plus proche voisin et bilinéaire avec une rotation
(𝑃𝐼𝐿. 𝐼𝑚𝑎𝑔𝑒. 𝑟𝑜𝑡𝑎𝑡𝑒 , voir Figure 5) :

Figure 5 : Image test après rotation avec une interpolation au plus proche voisin (à
gauche) et une interpolation bilinéaire (à droite)
L'interpolation bilinéaire donne une image de bien meilleure qualité que l'interpolation au plus
proche voisin.

# Importing Image and ImageOps module from PIL package


import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageOps
# applying autocontrast method
im1 = Image.open('feuilles-Rehaus.jpg')
#im2 = ImageOps.autocontrast(im1, cutoff = 1)
im2=im1.rotate(45, resample=Image.NEAREST)
im3=im1.rotate(45, resample=Image.BILINEAR)
im3
im4 = im1.rotate(45, fillcolor=(255, 128, 0), expand=True)
im5 = im1.rotate(45, translate=(20, 15))
im6 = im.rotate(45, translate=(20, 15), expand=True)
MP-ISI2 FSM-Monastir A.U. 2023-24

VII. Élimination du bruit


Enfin, la qualité d'une photo peut également être dégradée par du bruit numérique, c'est-à-dire
par l'apparition aléatoire de "grains" superflus. Il s'agit d'un phénomène courant en photographie
numérique, dû à un mauvais réglage de la sensibilité des capteurs de l'appareil photo, ou à une
limitation de leurs capacités.
Le bruit peut être vu comme une image constituée de pixels dont les intensités ont été
déterminées de manière aléatoire.
Une image étant définie soit comme une fonction soit comme une matrice, nous pouvons
appliquer des opérations mathématiques usuelles, comme l'addition. Ainsi, on parle de bruit additif
lorsque l'image bruitée est la somme de l'image originale et du bruit.
L'intensité du pixel de coordonnées (𝑥, 𝑦) dans l'image bruitée est alors donnée par la relation :
𝐼′(𝑥, 𝑦) = 𝐼(𝑥, 𝑦) + 𝜂(𝑥, 𝑦)
où 𝐼′(𝑥, 𝑦), 𝐼(𝑥, 𝑦) et 𝜂(𝑥, 𝑦) désignent les intensités du pixel (𝑥, 𝑦) respectivement dans l'image
bruitée, l'image originale et le bruit.
Un exemple très classique de bruit additif est le bruit gaussien, pour lequel les intensités sont
choisies aléatoirement selon une loi normale : 𝜂(𝑥, 𝑦) suit la loi 𝒩(0, 𝜎 2 ). Plus la variance est élevée,
plus le bruit est visible dans l'image.
Il existe d'autres types de bruits, comme le bruit impulsionnel : un certain pourcentage
de pixels dans l'image est remplacé par une valeur tirée uniformément sur [0,255], ou
1
bien par 0 ou 255 avec la même probabilité (bruit impulsionnel dit "poivre et sel").
2
Nous pouvons facilement générer du bruit gaussien dans une image. En Python, cela se fait avec
la fonction 𝑛𝑢𝑚𝑝𝑦. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙, qui simule une variable aléatoire gaussienne :

# Charger l'image sous forme d'une matrice de pixels


img = np.array(Image.open('feuilles-Rehaus.jpg'))
# Générer le bruit gaussien de moyenne nulle et d'écart-type 7 (variance 49)
noise = np.random.normal(0, 7, img.shape)
# Créer l'image bruitée et l'afficher
noisy_img = Image.fromarray(img + noise).convert('L')
noisy_img

Figure 6 : Image test (à gauche) et Image altérée par un bruit gaussien (à droite)
Pour se débarrasse du bruit, plusieurs techniques de débruitage (ou lissage) ont été développées
afin d'atténuer le bruit dans une image. Le lissage par moyennage désigne la solution la plus intuitive.
Dans une image de bonne qualité, un pixel a généralement une intensité relativement similaire à
celle de ses voisins. Le lissage par moyennage consiste alors à remplacer la valeur de chaque pixel par
l'intensité moyenne de son voisinage. Le voisinage est défini par une fenêtre carrée de taille impaire,
centrée en le pixel à corriger. Le principe de cette méthode est schématisé ci-dessous :
MP-ISI2 FSM-Monastir A.U. 2023-24

Figure 7 : Fonctionnement du lissage par moyennage


Comme la valeur de chaque pixel est modifiée en fonction des intensités de ses pixels voisins, il
s'agit d'un traitement local.
Ne confondez pas traitements locaux et traitements ponctuels : ces derniers
corrigent la valeur d'un pixel sans prendre en compte son voisinage !
En 𝑃𝑖𝑙𝑙𝑜𝑤, le lissage par moyennage est implémenté dans la classe 𝑃𝐼𝐿. 𝐼𝑚𝑎𝑔𝑒𝐹𝑖𝑙𝑡𝑒𝑟. 𝐵𝑜𝑥𝐵𝑙𝑢𝑟
et il s'applique avec la méthode 𝑃𝐼𝐿. 𝐼𝑚𝑎𝑔𝑒. 𝑓𝑖𝑙𝑡𝑒𝑟 :

from PIL import ImageFilter


# Appliquer le lissage par moyennage (fenêtre de taille 9) et afficher
le résultat
noisy_img.filter(ImageFilter.BoxBlur(1)).show()

Figure 8 : Image test bruité (à gauche) et image lissée (à droite)


Le bruit est bien atténué, mais en contrepartie, l'image est devenue floue – d'où l'appellation
𝐵𝑜𝑥𝐵𝑙𝑢𝑟 (blur = flou en anglais) dans 𝑃𝑖𝑙𝑙𝑜𝑤 !
Mais pourquoi le lissage s'applique avec une méthode nommée filter ? En réalité, le lissage par
moyennage n'est qu'un exemple de filtre. Le chapitre suivant est consacré à cet outil fondamental en
traitement d'images.

Vous aimerez peut-être aussi