TD K1 : Traitement de l'image avec numpy et matplotlib¶
Mise en place de l'espace de travail¶
Pour récupérer, traiter et afficher une image, on va dans ce TD, utiliser les modules matplotlib et numpy.
# les bibliothèques que l'on va utiliser
import matplotlib.pyplot as plt
import matplotlib.image as iplt
import numpy as np
Pour charger l'image dans un tableau 2D
color_datas = iplt.imread('Hibiscus_Couleur.jpg')
Pour voir le type et les dimensions de l'image :
print(type(color_datas))
print(np.shape(color_datas))
print(color_datas[0,0])
# constater le "shape" de color_datas qui un un tableau 3D
<class 'numpy.ndarray'> (262, 420, 3) [ 67 128 0]
Pour éviter de travailler sur les trois composantes, on va convertir cette image en niveaux de gris :
# conversion en niveau de gris en faisant la moyenne des 3 composantes colorées
conversion = [0.2989, 0.5870, 0.1140] # les coefficients de la moyenne
datas = np.dot(color_datas,conversion) # broadcasting : produit de chasue pixel par conversion
datas = datas.astype(np.int32) # on convertit les types (flottants) en entiers
print("type :",type(datas))
print("shape :", np.shape(datas))
print("datas[0,0] = ",datas[0,0])
# constater le "shape" de la variable 'datas' qui est maintenant un tableau 2D
type : <class 'numpy.ndarray'> shape : (262, 420) datas[0,0] = 95
Pour afficher un tableau 2D sous forme d'image :
# Important : utiliser cette fonction pour afficher les images afin de ne pas avoir de mauvaise surprise !
def display(tab, fig = 1):
""" Reçoit un tableau 2D de nombres entiers entre 0 et 255, un numéro de figure
et affiche l'image correspondante en lui associant le numéro de figure"""
plt.figure(fig)
plt.clf()
plt.imshow(tab,cmap='gray',vmin=0,vmax=255) # on précise le niveau du noir (0) et du blanc (255)
plt.show() # on affiche la figure à l'écran.
# On affiche l'image :
display(datas,1)
# Remarquer comme l'image est en fait un tableau 2D
print(datas)
[[ 95 95 95 ... 96 96 96] [ 96 96 96 ... 96 96 96] [ 96 96 96 ... 96 96 96] ... [ 96 96 96 ... 96 97 98] [ 96 96 96 ... 100 102 103] [ 96 96 96 ... 104 106 107]]
I. Premiers tests : affichage d'une image fond uni¶
Q1. On donne une instruction permettant d'afficher un image de 256x256 noire. Compléter les suivantes pour obtenir une image blanche, puis une image grise (niveau 128 par exemple), puis une dégradée horizontal de la gauche vers la droite en allant du noir vers le blanc.
tab1 = np.zeros((256,256))
display(tab1,1)
tab2= # à compléter
display(tab2,2)
tab3 = # à compléter
display(tab3,3)
tab4 = np.zeros((256,256))
# à compléter en modifiant tab4
display(tab4,4)
II. Division par 2¶
Q2. Ecrire une fonction qui reçoit un tableau 2D représentant une image en niveaux de gris et qui renvoie un tableau de cette même image dont les dimensions ont été divisées par 2 (on ne gardera que les lignes et les colonnes paires par exemple).
On pourra envisager 2 méthodes, l'une avec une double boucle, l'autre avec des slices.
def div2(tab0):
"""reçoit un tableau 2D représentant une image en niveaux de gris
et qui renvoie un tableau de cette même image dont les dimensions
ont été divisées par 2 """
# à compléter
return tab1
datas_div2 = div2(datas)
display(datas_div2,2)
# remarquer la qualité légèrement dégradée et surtout l'échelle
# sur les axes pour bien voir la réduction de l'image
III. Rotation de 90°¶
Q3. Ecrire une fonction python qui reçoit un tableau représentant une image en niveaux de gris et qui renvoie un tableau de cette même image mais tournée de 90° vers la droite. Envisager une solution avec dune double boucle ou un solution avec des slices en écriture sur les tableaux numpy...
def rot90(tab0) :
""" reçoit un tableau 2D représentant une image en niveaux de gris
et renvoie un tableau de cette même image mais tournée de 90°"""
lig,col = np.shape(tab0)
tab1 = np.zeros((col,lig)) # col lignes et lig colonnes
# à compléter
return tab1
datas90 = rot90(datas)
display(datas90,3)
IV. Multiplication par 2¶
Q4. Ecrire une fonction qui reçoit un tableau 2D représentant une image en niveaux de gris et qui renvoie un tableau de cette même image dont les dimensions ont été multipliées par 2 (on recopiera chaque ligne et chaque colonne)
def mult2(tab0):
""" reçoit un tableau 2D représentant une image en niveaux de gris
et qui renvoie un tableau de cette même image dont les dimensions
ont été multipliées par 2"""
lig, col = np.shape(tab0)
tab1 = np.zeros((2*lig,2*col))
# à compléter
return tab1
datas_m2 = mult2(datas)
display(datas_m2,4)
V. Effet seuil : noir & blanc¶
Q5. Ecrire une fonction qui reçoit un tableau 2D représentant une image en niveaux de gris et qui renvoie un tableau de cette même image dont les valeurs seront 0 et 255 (0 si valeur initiale < 128, 255 sinon)
def seuil(tab0, seuil=128):
""" reçoit un tableau 2D représentant une image en niveaux de gris et
qui renvoie un tableau de cette même image dont les valeurs seront 0 et 255
(0 si valeur initiale < 128, 255 sinon)"""
# à compléter
return tab1
datas_seuil= seuil(datas,96)
display(datas_seuil,5)
VI. Dessiner l'histogramme de la répartition des "couleurs"¶
On souhaite dans cette partie savoir pour chacun des 256 niveaux de gris possibles, combien de pixels ont cette valeur, puis de dessiner ces données sur un diagramme en barres.
Q6a. Ecrire une fonction qui reçoit un tableau 2D représentant une image en niveaux de gris et qui renvoie la liste dont chaque élément est égal au nombre de pixels ayant comme niveau de gris son index
def niveau(tab0):
niv = np.zeros(256)
# à compléter
return niv
#print(niveau(datas))
Q6b. Dessiner le diagramme en barre de cette liste
plt.figure(6)
plt.clf()
# à compléter
plt.show()
VII. Appliquer un filtre pour flouter l'image¶
Une solution pour flouter une image est de rempalcer le niveau de chaque pixel par la moyenne des niveaux des pixels qui l'entourent. Reste à savoir combien prendre de pixels autour. On peut commencer par ne considérer que la matrice 3x3 centrée sur le pixel à modifier, puis une 5x5 etc..
Remarque : il existe dans numpy une méthode qui calcule la moyenne des éléments d'un tableau (qu'il soit 1D ou 2D), c'est .mean(). Par exemple :
tab = np.arange(10)
print(tab.mean())
4.5
Q7. Ecrire une fonction flou(tab)qui reçoit tab un tableau 2D représentant une image en niveaux de gris et une taille t de 'masque'. Elle renvoie un tableau de cette même image floutée, dont chaque valeur aura été remplacée par la moyenne des valeurs adjacentes. La variable t contient le nombre de colonnes de la matrice englobant le pixel (ce doit être un nombre impair)
def flou(tab0, t):
assert t%2 == 1 # t doit être impair
lig, col = np.shape(tab0)
tab1 = tab0.copy()
d = t//2
# à completer
return tab1
datas_flou = flou(datas,9)
display(datas_flou,7)
VIII. Détection des contours d'une image¶
Le principe de l'algorithme (extrait du livre "informatique, programmation et calcul scientifique en python et Scilab" de Thierry Audibert et Amar Oussalah):
On souhiate déterminer les lignes de contour dans une image. Une première approche, naïve, consiste à comparer le gradient d'intensité entre un pixel et chacun de ses voisins immédiats. Si ce gradient dépasse un certzin seuil, le pixel est noirci dans une copie de l'image, sinon il est laissé en blanc.
Voici les grandes lignes de l'algorithme écrites "en langage naturel" si l'on cherche le gradient dans les direction est et sud :
txt
Créer un tableau noir
pour chaque ligne (sauf la dernière) :
pour chaque colonne (sauf la dernière) :
pix = intensité du pixel courant
east = intensité du pixel "Est"
south = intensité du pixel "Sud"
si les écarts (east-pix) et (south-pix) sont tous deux inférieurs au seuil alors :
mettre dans la copie du tableau un pixel à blanc.
Q8. Ecrire une fonction qui reçoit un tableau 2D représentant une image en niveaux de gris. Elle renvoie un tableau contenant les contours de l'image initiale.
# fonction contour :
def contour(tab0, seuil = 20):
""" renvoie le contour de l'image """
# tester diverse valeurs de seuil
lig, col = np.shape(tab0)
tab1 = np.zeros((lig,col))
# à compléter
return tab1
datas_cont = contour(datas,20)
display(datas_cont,8)
IX. Effet photomaton¶
Q9a. Essayer de comprendre ce que fait la fonction ci-dessous
def photomaton(tab0, repet=1):
lig,col = np.shape(tab0)
lig = 2**int(np.log2(lig))
col = 2**int(np.log2(col))
#assert int(np.log2(lig))==np.log2(lig)
#assert int(np.log2(col))==np.log2(col)
tab0 = tab0[:lig,:col]
tab1 = np.zeros((lig,col))
for i in range(repet):
tab1[:lig//2,:col//2] = tab0[::2,::2]
tab1[lig//2:,:col//2] = tab0[1::2,::2]
tab1[:lig//2,col//2:] = tab0[::2,1::2]
tab1[lig//2:,col//2:] = tab0[1::2,1::2]
tab0 = tab1.copy()
return tab1
Q9b. Exécuter les commandes suivantes avec N=2, puis 3 ou 4 pour confirmer (ou pas !) votre hypothèse.
N = 2
display(photomaton(datas,N),9)
Q9c. Essayer progressivement pour N de 2 à 8 et constater un effet pour le moins surprenant !
X. Tracer des objets simples¶
Ceux qui sont en avance peuvent essayer de répondre à ces problèmes apparement simples...
... mais il ne faut pas toujours se fier aux apparences !
Q10.a Le code ci-dessous produit une image noire de 512x512 pixels. On souhaite écrire une fonction cercle(img, x_center, y_center, radius) qui dessine sur l'image img un cercle dont on connait les coordonnées du centre ainsi que le rayon en pixels.
On rappelle si besoin qu'une équation paramétrique d'un cercle de centre $A(x_A,y_A)$ et de rayon $r$ est : $\begin{cases} x = x_A + r\times \cos(t) \\ y = y_A + r\times\sin(t) \end{cases} \quad t\in[0;2\pi]$
Q10.b Pour les plus courageux, le défi est maintenant d'écrire une fonction line(img,xA,yA,xB,yB) qui trace une ligne entre les points A(xA,yA) et B(xB,yB).