PREMIÈRE PARTIE : INTRODUCTION AUX IMAGES NUMÉRIQUES EN PYTHON
L'objectif de cette activité est d'illustrer le rôle du calcul matriciel (applications linéaires, transformations) en traitement numérique des images. Chaque exercice consiste à créer un filtre modifiant le contenu d'un fichier image.
Elle comporte trois parties, et se termine par une section facultative présentant des ouvertures possibles :
python
Les images qui seront utilisées dans les trois premières parties sont les suivantes :
Vous devez les télécharger (localement) puis les « téléverser » dans un répertoire images
que vous aurez créé.
Pour pouvez aussi effectuer cette opération grâce aux instructions suivantes, depuis votre notebook
jupyter
:
import urllib.request,os
# Creation eventuelle du répertoire
try:
os.mkdir('images')
except:
print('Le répertoire existe déjà')
# Téléchargement des images
urllib.request.urlretrieve("https://iut-info.univ-reims.fr/users/coutant/img/solo-256px.png", "images/solo-256px.png")
urllib.request.urlretrieve("https://iut-info.univ-reims.fr/users/coutant/img/logo-starwars-256px.png", "images/logo-starwars-256px.png")
Le répertoire images
devrait maintenant contenir les deux images suivantes :
Attention
Si vous travaillez avec votre distribution de python
, il vaut mieux utiliser le module requests
:
import requests,os
def download(url, destination):
response = requests.get(url, verify=True)
open(destination, 'wb').write(response.content)
# Creation eventuelle du répertoire
try:
os.mkdir('images')
except:
print('Le répertoire existe déjà')
# Téléchargement des images
download('https://iut-info.univ-reims.fr/users/coutant/img/solo-256px.png', 'images/solo-256px.png')
download('https://iut-info.univ-reims.fr/users/coutant/img/logo-starwars-256px.png', 'images/logo-starwars-256px.png')
Il est aussi nécessaire de charger les modules suivants :
python
Il existe de nombreux formats d'images numériques. On distingue deux catégories :
jpeg
, png
, bmp
, ou encore tiff
)svg
, emf
par exemple)Celles qui nous intéressent ici sont les images matricielles (ou images bitmap).
Les images matricielles sont des tableaux de points, appelés pixels. Chaque pixel est caractérisé par :
Dans une image en niveaux de gris, le vecteur qui représente le pixel n'a qu'une composante.
Dans le cas d'une image en couleurs, il y a au moins trois canaux. Généralement, il s'agit des canaux associés au Rouge, au Vert et au Bleu. En utilisant le principe de synthèse additive la superposition de ces canaux lumineux permet d'obtenir les autres couleurs (nous en reparlerons dan la partie suivante). On peut représenter le tableau de pixels comme un tableau à trois dimensions (dont la profondeur représente le nombre de canaux spectraux). Dans ce cas, un pixel p est un vecteur à trois composantes p=(r_p,g_p, b_p) où :
Ainsi par exemple :
Ainsi ces pixels sont de couleurs respectives :
La structure de données python
typiquement utilisée pour représenter ces tableaux est l'array de numpy
. Les valeurs sont généralement de type float64
.
array
(2D) de pixels.float
.array
de float
(un float
par composante chromatique).Affichage facile
Pour afficher les images, nous vous proposons d'utiliser la fonction display(img_list, size, shape)
ci-dessous, qui affiche une série d'images stockées dans une liste img_list
. La taille de l'affichage d'une image est fixée à size
pouces. Le booléen shape
permet d'afficher ou non les dimensions du tableau au dessus de l'image.
def display(imglist,size=5, shape=True):
cols = len(imglist)
fig = plt.figure(figsize=(size*cols,size*cols))
for i in range(0,cols):
a = fig.add_subplot(1, cols, i+1)
if len(imglist[i].shape) > 2 :
subfig = plt.imshow(imglist[i], vmin=0.0, vmax=1.0)
else :
subfig = plt.imshow(imglist[i],cmap="gray",vmin=0.0, vmax=1.0)
subfig.axes.get_xaxis().set_visible(False)
subfig.axes.get_yaxis().set_visible(False)
if shape == True:
a.set_title(str(imglist[i].shape))
plt.show()
Nous allons maintenant créer une image de 4 pixels (2 \times 2) avec les niveaux de gris suivants :
puis une image de 4 pixels (2 \times 2) avec les couleurs suivantes :
img = np.array([[[0.98, 0.06, 0.18], [0.78, 0.29, 0.91]],
[[0.49, 0.51, 0.37], [0.53, 0.81, 0.55]]])
display([img])
Pour accéder, en lecture, comme en écriture, à un pixel, il suffit d'utiliser les [ ]
.
Exemple
matplotlib
Le module matplotlib
dispose de deux fonctions imread
et imsave
(dans le sous-module pyplot
) qui permettent respectivement :
Il existe aussi une fonction imshow
, qui permet d'afficher une image contenue dans un tableau :
...mais on lui préférera notre fonction display
qui en simplifie l'usage.
Pour accéder au pixel situé à la i-ème ligne et à la j-ème colonne de l'image on utilise donc simplement img[i,j]
. Par exemple, le pixel situé sur la ligne 78 et la colonne 95 de l'image précédente correspond au vecteur :
Les composantes R, G et B de ce pixel p
sont :
Rouge : 0.7411765
Vert : 0.6627451
Bleu : 0.5803922
Remarque
On peut naturellement accéder directement à une composante d'un pixel en spécifiant son indice :
Vérification avec Gimp
Vous pouvez vérifier -ici avec Gimp, mais tout autre logiciel d'imagerie fera l'affaire- que ces valeurs numériques sont cohérentes.
colonne
,ligne
) contrairement à notre représentation.
Écrire une fonction inverse(img)
qui crée une nouvelle image dont les couleurs des pixels sont obtenues par complémentaire à 1 des couleurs de l'image img
. En d'autres termes, pour chaque pixel p^{\prime}_{i,j} = (r^{\prime}_{i,j},g^{\prime}_{i,j},b^{\prime}_{i,j}) de la nouvelle image, on a :
\left\{\begin{array}{ccc} r^{\prime}_{i,j} & = & 1 - r_{i,j}\\ g^{\prime}_{i,j} & = & 1 - g_{i,j}\\ b^{\prime}_{i,j} & = & 1 - b_{i,j} \end{array}\right.
où p_{i,j} = (r_{i,j},g_{i,j},b_{i,j}) est le pixel correspondant dans l'image de départ.
Exemple
Écrire une fonction keep_red(img)
qui annule les valeurs sur les composantes vertes et bleue d'une copie de img
et retourne le résultat (puis sur le même principe écrire keep_green(img)
et keep_blue(img)
).
Exemple :
Écrire une fonction extract(img, l1, c1, l2, c2)
qui retourne une nouvelle image avec les pixels de img
situés entre les lignes l1
et l2
et les colonnes c1
et c2
.
Exemple :
Écrire une fonction randimg_gsc(height, width)
qui crée une image de taille height
\timeswidth
dans laquelle le niveau de gris de chaque pixel est choisi au hasard.
Écrire une fonction randimg_rgb(height, width)
qui crée une image de taille height
\timeswidth
dans laquelle les valeurs de rouge, vert, et bleu de chaque pixel sont choisies au hasard.
Indication : la fonction random.random()
du module random (ne pas oublier de l'importer avec import random
) permettent de générer aléatoirement uniformément un réel compris entre 0 et 1.
Exemples :
Construire les tableaux permettant de reproduire les images suivantes :
Les exercices qui suivent sont facultatifs. Ne les faites qui si vous avez une avance suffisante. La suite est encore longue !
Construire les tableaux permettant de reproduire à peu près les images suivantes :
Remarque : sur l'image de gauche, le motif artefactuel est dû à un phénomène appelé moiré
Voici une fonction count_histo_gsc(img)
, qui :
float
) en un entier compris entre 0 et 255,Cette fonction construit un histogramme des niveaux de gris de l'image passée en paramètre.
Ensuite, la fonction draw_histo
construit une image de l'histogramme des fréquences. Grosso modo, on dessine une ligne verticale proportionnelle au nombre de fois où le niveau de gris correspondant apparaît dans l'image.
def draw_histo(img):
[height, width] = np.shape(img)
counth = count_histo_gsc(img)
# Attention cascade ; on transforme les comptages
# en hauteurs de barres en pixels (le max est à 255) :
ch = [int(i * 255 / max(counth)) for i in counth]
img_histo = np.zeros((256,256),dtype='float32')
for g in range(256):
if ch[g] > 0:
for k in range(255-ch[g],256):
img_histo[k,g] = 1.0
return img_histo
Exemple d'utilisation :
img = plt.imread("https://upload.wikimedia.org/wikipedia/commons/f/fa/Grayscale_8bits_palette_sample_image.png")
display([draw_histo(img), img], 5)
Écrire la fonction draw_histo_rgb(img)
qui retourne une liste des trois images des histogrammes des composantes chromatiques : rouge, vert, bleu.
Voilà. Cette introduction à l'image en python
est terminée. Mais... quel rapport avec l'algèbre linéaire ? On y vient. Dans la partie suivante, on utilise les outils de l'algèbre linéaire pour transformer les couleurs d'une image.