CPGE-Al cachy Traitement d’Image MP/MP*
Traitement d’images en Python
1. Introduction
1.1 Représentation d’une image par une valeur Python
Un niveau de gris est représenté par un entier dans l’intervalle [0,255], 0 pour noir et 255 pour blanc.
En Python, le module numpy fournit le type numpy.uint8, codé sur 1 octet, qui représente précisément
cet intervalle d’entiers.
Une image en niveaux de gris est constituée de h×ℓ pixels où h et ℓ sont respectivement la hauteur et la largeur
de l’image. Chaque pixel est défini par son niveau de gris. En Python on représente une telle image par un
tableau (matrice) m de dimensions (h,ℓ). Pour i ∈ [0,h−1] et j ∈ [0, ℓ−1], m [ i, j ]∈ [0,255] est le niveau du pixel
situé à l’intersection de la iième ligne et de la jième colonne; les h lignes sont numérotées de 0 à h−1 à partir du
haut et les ℓ colonnes de 0 à ℓ−1 à partir de la gauche. Par exemple, t[0,0] est le niveau de gris du pixel situé en
haut et à gauche. On représente une couleur par un triplet d’entiers (r,g,b) avec 0 ≤ r, g, b ≤ 255; r, g et b
définissent respectivement le niveau de rouge, de vert et de bleu de la couleur.
Une image en couleurs de dimensions (h,ℓ) est représentée en Python par un tableau (matrice) m de dimensions
(h,ℓ,3) de sorte que, pour tout pixel (i,j), m[i,j] est le tableau de trois entiers définissant la couleur du pixel.
1.2 Lecture et écriture de fichiers images
Outre les modules numpy et scipy, on suppose que notre installation Python contient l’un des
modules matplotlib de manipulation d’images. Le module matplotlib contient un sous-
module pyplot qui fait appel aux fonctions de traitement ‘image. On déclare
>>>import numpy as np
>>>import [Link] as plt
>>>[Link](image)
>>> [Link]()
1.3 Exemple
On suppose avoir un fichier image représentant La Joconde enregistré sous le nom [Link] dans un
sous répertoire nommé images du répertoire courant.
Lecture du fichier et stockage dans un tableau numpy: >>>im = [Link]("images/[Link]")
Obtention d’informations sur l’image: >>>print("type = ",type(im)) donne <class '[Link]'>
>>>print("dimensions = ",[Link]) donne dimensions = (240, 161, 3)
>>>print("data type = ",[Link]) donne data type = uint8
>>>print("taille = ",[Link])
taille = 115920
Visualisation:
>>>[Link](im)
Conversion de l’image en niveaux de gris:
>>>def imageenNG (image):
h, l, d= [Link] # dimensions de image
imageNG = [Link]((h,l),dtype = np.uint8) # image vide de memes dimensions
for i in range(h):
for j in range(l):
imageNG[i,j] = image[i,j].mean()
# moyenne des 3 niveaux
[Link] 1/8
CPGE-Al
Al cachy Traitement d’Image MP/MP*
return imageNG
>>>imNG = imageenNG(im)
Vérification:
>>>[Link](imNG)
Sauvegarde de l’image sur disque:
>>>[Link]("images/[Link]"
"images/[Link]", imNG)
Dans toute la suite, pour simplifier, on ne travaillera que sur des images en niveaux de gris (en abrégé
NG). L’image imNG sera appelée l’image
l’ originale et servira d’image test. Tous les algorithmes
présentés se généralisent à des images en couleurs: il suffit de les appliquer à chacune des trois
composantes R, G et B.
2 Transformations pixel par pixel
Pour s’entrainer à la manipulation d’image on peut écrire des fonctions Python opérant par exemple
des transformations géométriques (symétrie, rotation), qui modifient la luminosité, etc. On donne ici
deux exemples simples.
2.1 Négatif d’une image
Question 1
Écrire une fonction def negatif(image)
negatif(image qui prend une image en paramètre et retourne une image en
négatif (noir devient blanc, etc.)
On vérifiera:
>>>[Link](negatif(imNG))
2.2 Noir et blanc
Partant d’une image sur 255 niveaux de gris, on désire
construire une image ne comportant que deux niveaux: noir (0) et blanc (255). Pour cela on choisit un
u
seuil s∈]0,255[;
]0,255[; si le niveau d’un pixel est ≥ à s,, on le remplace par 255, sinon par 0.
Question 2
Écrire une fonction def noirBlanc(image, seuil = 128) qui prend en paramètre
une image en niveau de gris et un seuil et retourne une image en noir et blanc.
On vérifiera:
>>>[Link](noirBlanc(imNG))
3 Tramage
L’algorithme de la fonction noirBlanc fournit une image très dégradée. En remplaçant le niveau d’un
pixel par 0 ou 255 on introduit une erreur importante. L’idée du tramage par diffusion d’erreur est de
distribuer partiellement cette erreur sur les pixels voisins non encore traités. L’image obtenue
ressemble à une image en niveaux de gris alors que chaque pixel est soit blanc soit noir. L’algorithme
est le suivant à partir d’une image NG:
Traitement du premier pixel ((0,0) en haut à gauche): Suivant que le niveau k du pixel est ≥ ou < au
seuil on remplace, comme dans l’algorithme précédent, k par k′=255 ′=255 ou 0. Ce faisant on commet une
erreur de δ=k−k′. On ajoute alors δ/2 au niveau
niveau du pixel situé immédiatement à droite et on ajoute δ/4
au pixel situé immédiatement dessous et aussi à celui en dessous à droite.
On traite le pixel suivant (0,1) de la même manière en tenant compte des modifications déjà déj
effectuées.
Et ainsi de suite pour tous les pixels. Il est entendu que si le pixel traité se trouve sur la dernière ligne
[Link] 2/8
CPGE-Al cachy Traitement d’Image MP/MP*
et/ou sur la dernière colonne, le pixel à droite et/ou dessous n’existe pas et il ne faut donc pas en tenir
compte.
Question 3
Écrire une fonction def tramage(image,seuil = 128) qui implémente cet algorithme. On vérifiera:
>>>[Link](tramage(imNG))
4 Filtrage
Soit I une image NG de dimensions h×ℓ. Le niveau d’un pixel (i,j) ∈ [0,h−1]×[0,ℓ−1] est noté Ii,j.
1 2 3
On considère une matrice réelle 3×3 = 1 2 3 appelée le filtre et deux réels d>0 et
1 2 3
et δ appelés le diviseur et le décalage. Le filtrage de l’image I par les données F, d et δ est l’image I′
définie par I′i,j=pi,j /d + δ où pi,j est la somme, pondérée par les éléments de F, des niveaux des 9 pixels
voisins de (i,j) dans I. Plus précisément,
pi,j=a1 Ii−1,j−1 + a2 Ii−1,j + a3 Ii−1,j+1+ b1Ii,j−1 + b2 Ii,j + b3Ii,j+1+ c1 Ii+1,j−1 + c2 Ii+1,j + c3 Ii+1,j+1.
On convient que I−1,j = I0,j, Ih,j = Ih−1,j, Ii,−1 = Ii,0 et Ii,ℓ = Ii,ℓ−1 pour que cette formule ait un sens même si le
pixel (i,j) est au bord de l’image (i = 0 ou i = h−1 ou j = 0 ou j = ℓ−1).
En fait, I′i,j n’est pas toujours dans l’intervalle [0,255]. Pour se ramener à cet intervalle, deux stratégies
sont possibles:
L’écrêtage qui consiste à remplacer I′i,j par 255 (resp. 0) si I′i,j est > 255 (resp. < 0).
La normalisation. On calcule le minimum m et le maximum M des I′i,j et on applique à chaque I′i,j
l’application affine qui transforme l’intervalle [m,M] en [0,255].
Bien entendu, dans les deux cas, les calculs sont effectués avec des nombres réels (type [Link]) et on
doit remplacer le résultat par sa partie entière pour obtenir un résultat de type np.uint8.
Ce qui précède se généralise facilement au cas où la matrice F est de dimensions p × q avec p
et q impairs.
Question 4
Écrire une fonction def filtre(matrice,image,diviseur = 1, decalage = 0, normal = False)
Arguments: matrice : ndarray la matrice F, image : ndarray une image NG, diviseur : d, decalage : δ
et normal : bool
Valeur renvoyée: l’image filtrée; si normal est False (resp. True) la méthode employée est l’écrêtage
(resp. la normalisation).
On vérifiera que les commandes
>>>floutage = [Link]([[1,1,1],
[1,1,1],
[1,1,1]], dtype = [Link])
[Link](filtre(floutage, imNG, diviseur = 9, normal = True))
>>>contour = [Link]([[-1,-1,-1],
[-1,12,-1],
[-1,-1,-1]],dtype = [Link])
[Link](filtre(contour,imNG, decalage = 128))
>>>contraste = [Link]([[ 0,-1, 0],
[Link] 3/8
CPGE-Al
Al cachy Traitement d’Image MP/MP*
[-1, 5, -1],
1],
[ 0,-1, 0]],dtype = [Link])
[Link](filtre(contraste,
.imshow(filtre(contraste, imNG, decalage = 128))
>>>relief = [Link]([[-2,-1, 0],
[-1, 0, 1],
[ 0, 1, 2]], dtype = [Link])
[Link](filtre(relief,imNG,decalage
(filtre(relief,imNG,decalage = 128,normal = True)) affichent:
5 Égalisation de l’histogramme
Soit I une image en NG. Pour k ∈ [0,255] on note X(k) le nombre de pixels de I de niveau k.
L’histogramme de I est le graphe de la fonction X : [0,255] → N.. On le représente dans R2 en traçant les
))] pour tout k ∈ [0,255].
segments verticaux [(k,0),(k,X(k))]
Voici par exemple l’histogramme de l’image originale:
Question 5 : Écrire une fonction distribution(image)
distribution( qui prend une image en niveau de gris et renvoie
une matrice dee taille 256 défini par m[k] = X(k)
Pour atténuer le fait que certaines valeurs de k annulent X et pour donner une vision différente de la
distribution des niveaux d’une image on peut lisser l’histogramme en choisissant un petit entier δ > 0 et
en remplaçant X par la fonction Y définie par Y(k) = la moyenne des X(i) pour i variant de k − δ à k + δ
(supprimer les i > à 255 ou < à 0).
L’histogramme lissé avec δ = 10 de l’image originale est:
Question 6 : Écrire une fonction def distributionLisse (image) analogue à la fonction distribution mais
portant sur la fonction Y;; on prendra δ = 10.
On définit une nouvelle image I′′ par ′ , = ∑ ,
où N = h × ℓ est le nombre de pixels de I.
Si I est une image théorique définie non pas par N pixels mais par une fonction continue de [0,h]
[0, × [0,ℓ]
dans R, on définit X et I′′ par des intégrales au lieu de sommes et on peut montrer que, dans ce cas, la
distribution X′ des niveaux de I′′ est constante (histogramme plat). I′′ a donc en un sens un contraste
idéal.
Dans le cas qui nous occupe (I est pixelisée), X′′ n’est pas constante mais, dans les régions où
o X′ prend
[Link] 4/8
CPGE-Al
Al cachy Traitement d’Image MP/MP*
de grandes valeurs, il y a plus de valeurs de k qui annulent X′. Par exemple, si I est l’image originale, X′
et I′ sont donnés par:
On remarque que I′′ est bien contrastée et on vérifie que la fonction Y′′ est pratiquement constante en
dessinant l’histogramme lissé de I′:
I
Question 7 :
Écrire une fonction def egalisationHistogramme(image)
egalisationHistogramme( implémentant le calcul de I′ à partir de I.
6 Pixelisation et dépixelisation
6.1 Pixelisation
Soient I une image NG de dimensions h × ℓ et p ≥ 2 un entier. La p-pixelisation de I consiste à diviser
par p ses dimensions. Précisément, il s’agit de l’image I′ de dimensions h1 × ℓ1, où h1 = ⌊h / p⌋ et ℓ1 = ⌋ℓ
/ p⌋,, définie comme suit: On partage I en h1 × ℓ1 carrés de p × p pixels (si h et/ou ℓ n’est pas multiple
de p,, les carrés situés en bas et/ou à droite de l’image sont coupés, ce qui en fait des rectangles). Á
chacun de ces carrés correspond un pixel de I′′ auquel on donne pour niveau la moyenne des niveaux
des p2 (ou moins) pixels de I correspondant au carré. I′′ est une version dégradée de I.
Question 8
Écrire une fonction pixelise(image
image,p) implémentant le calcul de I′ à partir de I et p.
On vérifiera:
>>>[Link](pixelise(imNG,4))
(avec un zoom de 400% pour l’affichage)
6.2 Dépixelisation
On se propose d’agrandir une image I, h1 × ℓ1, en une image I′, h × ℓ, où h = ph1 et ℓ = pℓ1. La méthode
la plus simple consiste à remplir chacun des h1ℓ1carrés p × p de I′′ avec le niveau du pixel correspondant
dans I,, mais cela produit une image dans laquelle ces carrés sont trop visibles. On va adopter une
méthode encore simple et d’efficacité moyenne (effet de flou). On détermine le niveau d’un pixel (i,j) (
de I′ correspondant à un pixel (i1,j1) de I comme le barycentre des niveaux des 4 pixels (i
( 1,j1), (i1+1,j1),
(i1,j1+1) et (i1+1,j1+1) de I affectés de coefficients
coefficients qui dépendent naturellement de la position de (i,j)
(
dans le carré p × p correspondant.
Question 9
Écrire une fonction depixelise(image
image,p) implémentant le calcul de I′ à partir de I et p.
On vérifiera: >>>[Link](depixelise(pixelise(imNG,4),4))
.imshow(depixelise(pixelise(imNG,4),4))
%Fin de l’énoncé%
[Link] 5/8
CPGE-Al cachy Traitement d’Image MP/MP*
Traitement d’images, corrigé
Question 1 : Il suffit de remplacer le niveau k de chaque pixel par son négatif 255−k.
1 def negatif(image):
2 h,l = [Link]
3 imageN = [Link]((h,l),dtype = np.uint8)
4 for i in range(h):
5 for j in range(l):
6 imageN[i,j] = 255 - image[i,j]
7 return imageN
Question 2
1 def noirBlanc(image,seuil = 128):
2 h,l = [Link]
3 imageNB = [Link]((h,l),dtype = np.uint8)
4 for i in range(h):
5 for j in range(l):
6 if image[i,j] >= seuil:
7 imageNB[i,j] = 255
8 else:
9 imageNB[i,j] = 0
10 return imageNB
Question 3
1 def tramage(image, seuil = 128):
2 h,l = [Link]
3 imageT = [Link](image,dtype = [Link])
4 for i in range(h):
5 for j in range(l):
6 if imageT[i,j] >= seuil:
7 err = imageT[i,j] - 255
8 imageT[i,j] = 255
9 else:
10 err = imageT[i,j]
11 imageT[i,j] = 0
12 if j != l - 1:
13 imageT[i,j+1] += err // 2
14 if i != h - 1:
15 imageT[i+1,j] += err // 4
16 if j != l - 1:
17 imageT[i+1,j+1] += err // 4
18 return [Link](imageT,dtype = np.uint8)
Question 4
1 def filtre(matrice, image, diviseur = 1 , normal = False):
2 # matrice : le filtre ; image : l'image a filtrer , diviseur = 1 ; d decalage = 0, # delta
3 # normal = False : ecretage ou normalisation
4 h,l = [Link]
7 def get(i,j): # image[i,j] generalise
[Link] 6/8
CPGE-Al cachy Traitement d’Image MP/MP*
8 return image[max(0,min(i,h-1)),max(0,min(j,l-1))]
9 imageAux = [Link]((h,l),dtype = [Link])
10 p,q = [Link] # p et q sont impairs
11 mini = 1e10 # min des imageAux[i,j]
12 maxi = -1e10 # max des imageAux[i,j]
13 for i in range(h):
14 for j in range(l):
15 r, i0, j0 = 0.0, i - p//2, j - q//2
16 for u in range(p):
17 for v in range(q):
18 r += matrice[u,v] * get(i0 + u, j0 + v)
19 r = r / diviseur + decalage
20 if r < mini: mini = r
21 if r > maxi: maxi = r
22 imageAux[i,j] = r
23 imageF = [Link]((h,l),dtype = np.uint8)
24 if normal: # normalisation
25 a = 255.0 / (maxi - mini)
26 for i in range(h):
27 for j in range(l):
28 imageF[i,j] = [Link](a * (imageAux[i,j] - mini))
29 else: # ecretage
30 for i in range(h):
31 for j in range(l):
32 imageF[i,j] = max(0,min(255,[Link](imageAux[i,j])))
33 return imageF
Question 5
1 def distribution(image):
2 h,l = [Link]
3 t = [Link](256,dtype = [Link])
4 for i in range(h):
5 for j in range(l):
6 t[image[i,j]] += 1
7 return t
Question 6
1 def distributionLisse(image,delta = 10):
2 t = distribution(image)
3 u = [Link](256,dtype = [Link])
4 for k in range(256):
5 u[k] = [Link]([t[i] for i in range(k-delta,k+delta+1) if i >= 0 and i <= 255]).mean()
6 return u
Question 7
1 def egalisationHistogramme(image):
2 h,l = [Link]
3 r = 255.0 / h / l
4 t = distribution(image) # t[k] = X(k)
5 for k in range(1,256):
6 t[k] += t[k-1] # t[k] = X(0)+...+X(k)
[Link] 7/8
CPGE-Al cachy Traitement d’Image MP/MP*
7 for k in range(256):
8 t[k] = int(r * t[k])
9 imageEH = [Link]((h,l),dtype = np.uint8)
10 for i in range(h):
11 for j in range(l):
12 imageEH[i,j] = t[image[i,j]]
13 return imageEH
Question 8
1 def pixelise(image,p):
2 h,l = [Link]
3 h1,l1 = h //p, l // p
4 imageP = [Link]((h1,l1),dtype = np.uint8)
5 for i in range(h1):
6 for j in range(l1):
7 imageP[i,j] = image[p * i : p*i + p, p * j : p*j + p].mean()
8 return imageP
Question 9
1 def depixelise(image,p):
2 h1,l1 = [Link]
3 def get(i,j):
4 return image[min(i,h1-1),min(j,l1-1)]
5 h,l = h1 * p, l1 * p
6 imageDP = [Link]((h,l),dtype = np.uint8)
7 for i1 in range(h1):
8 for i in range(p*i1,p*(i1 + 1)):
9 di = i/p - i1
10 for j1 in range(l1):
11 for j in range(p*j1,p*(j1 + 1)):
12 dj = j/p - j1
13 k1 = (1 - di)*get(i1,j1) + di*get(i1+1,j1)
14 k2 = (1 - di)*get(i1,j1+1) + di*get(i1+1,j1+1)
15 imageDP[i,j] = (1 - dj)*k1 + dj*k2
16 return imageDP
[Link] 8/8