Extraction d’attributs: rougeur et élongation#
Dans cette feuille, vous allez implémenter l’extraction de la rougeur et de l’élongation, les deux attributs que nous avons utilisés la semaine dernière sur le jeu de données pommes/bananes.
import matplotlib.pyplot as plt
%matplotlib inline
%load_ext autoreload
%autoreload 2
from intro_science_donnees import * #les librairies classiques pour ISD (numpy, PIL...) y sont incluses
from utilities import *
dataset_dir = os.path.join(data.dir, 'ApplesAndBananasSimple')
images = load_images(dataset_dir, "*.png")
Extraction du premier plan de l’image#
Pour calculer les caractéristiques de nos images, nous devons d’abord
extraire le premier plan de l’image en séparant l’objet de son
arrière-plan. Pour la plupart des images de cet ensemble de données
simple, l’objet se trouve sur un fond clair. Une stratégie simple
consiste donc à choisir un seuil theta
(threshold en anglais) et
décider que tout pixel dont la valeur rouge, verte ou bleue est en
dessous du seuil appartient au premier plan.
Prenons une pomme :
img = images['a10.png']
img
On calcule, pour chaque pixel, le minimum de la valeur rouge, verte et bleue :
M = np.array(img)
G = np.min(M[:,:,0:3], axis=2)
Visualisons le résultat :
fig = Figure(figsize=(5, 5)) # Construction d'une nouvelle figure
subplot = fig.add_subplot(1, 2, 1) # Ajout d'une zone de dessin à gauche
subplot.imshow(M) # Ajout de l'image à la zone de dessin
subplot = fig.add_subplot(1, 2, 2) # Ajout d'une zone de dessin à droite
subplot.imshow(G, cmap='Greys_r', vmin=0, vmax=255) # Ajout de l'image
fig # Affichage de la figure
On choisit un seuil, ici à 150 à partir duquel on déduit un tableau
F
de booléens où F[i,j]
est True
chaque fois que le pixel de
coordonnées i
, j
est au premier plan. Le tableau F
peut être vu
comme une image en noir et blanc (respectivement, False
et True
) :
theta = 150
F = G < theta
plt.imshow(F);
Exercice
Essayez de changer la valeur de theta
.
Exercice
En vous inspirant de ce qui précède, implémentez dans
utilities.py la fonction foreground_filter(img, theta = 150)
qui prend comme argument un tableau numpy img
(c’est à dire
l’image PIL) et un seuil theta
. La fonction renvoie une image
seuillée. Vérifiez-le sur notre image:
plt.imshow(foreground_filter(img, 150));
show_source(foreground_filter)
F = foreground_filter(img, 150)
assert isinstance(F, np.ndarray)
assert F.shape == (32, 32)
assert F.dtype == np.dtype('bool')
Exercice
Maintenant, appliquez le filtre avec un seuil de 200 à toutes les
images du jeu de données. Gardez le résultat en mémoire dans la
variable images_filtrees
. Puis, affichez le résultat.
Astuce
Indications
Utiliser une compréhension
[f(x) for x in ...]
pour appliquer le filtre à toutes les images.Utilisez
image_grid
pour afficher le résultat.
# VOTRE CODE ICI
raise NotImplementedError()
assert len(images_filtrees) == 20
assert images_filtrees[10].sum() == 166
assert images_filtrees[14].sum() == 363
assert images_filtrees[7].sum() == 1014
Exercice
utilities.py fournit une fonction
transparent_background_filter
qui appelle foreground_filter
et
filtre tous les pixels en arrière-plan transparent. Appliquez ce
filtre à toutes les images du jeu de données. Essayez différents
seuils de theta
. A la fin, affichez les images pour le meilleur
seuil obtenu.
Astuce
Indications
Utiliser une compréhension
[f(x) for x in ...]
pour appliquer le filtre à toutes les images.Utilisez
image_grid
pour afficher le résultat.Commencez par tester des theta très différents les uns des autres comme 50, 100, 150, 200 puis rafinez votre seuil pour trouver le meilleur seuil.
# VOTRE CODE ICI
raise NotImplementedError()
2. Extraction de l’attribut rougeur
#
Nous voulons maintenant extraire la « rougeur » de l’image, définie
comme la moyenne (mean/average) des pixels de premier plan du canal
rouge
(red) (c’est-à-dire ceux qui sont True
in F
) moins la
moyenne des pixels de premier plan dans le canal vert
(green).
Exercice
Implémentez la fonction
redness(img)
dans utilities.py.Astuce
Indications
Pour le calcul du premier plan, vous utiliserez le seuil par défaut de
foreground_filter
.Pour calculer la moyenne, il est préférable de travailler avec des nombres à virgule flottante.
Commencez par extraire, le canal vert avec
G = M[:, :, 1] * 1.0
. Faites de meme avec le canal rouge dans un tableauR
.Ensuite, sachez que si on a un tableau
R
et un tableau booléen (tel queF
) de mêmes dimensions, alorsR[F]
renvoie un tableau avec uniquement les valeursR[i,j]
telles queF[i,j]
vaut True.Enfin, nous rappelons que
np.mean(R)
calcule la moyenne de toutes les valeurs d’un tableauR
ouG
;
Par exemple:
R = np.array([[1,2], [3,4]])
R
F = np.array([[True, False], [True, True]])
F
R[F]
show_source(redness)
image_grid(images,
titles=["{0:.2f}".format(redness(img)) for img in images])
assert abs(redness(images['b01.png']) - 0 ) < 0.1
assert abs(redness(images['a01.png']) - 41.48) < 0.1
assert abs(redness(images['a09.png']) - -3.66) < 0.1
3. Extraction de l’attribut elongation
#
Comme second attribut pour distinguer les pommes des bananes, nous avons extrait l”élongation du fruit. Cela correspond au rapport de la longueur sur la largeur de l’objet. Mais comment mesurer ces caractéristiques en premier lieu, lorsque les fruits peuvent avoir n’importe quelle orientation, et qu’il peut y avoir du bruit dans l’image ? C’est ce que nous allons voir maintenant.
Nous profiterons de l’occasion pour montrer une astuce élégante, mise
en oeuvre dans la fonction elongation
déjà implémentée.
Exercice
Affichez les images des fruits du jeu de données à l’aide
d”image_grid
et utilisez la fonction elongation
pour leur donner
en titre leur élongation. Vérifiez visuellement que c’est
plausible. Vous voudrez peut-être utiliser une règle dans le titre!
# VOTRE CODE ICI
raise NotImplementedError()
Alors, comment cela marche ?
Nous convertissons l’image en noir et blanc en un nuage de points : Chaque point représente les coordonnées d’un des pixels de premier plan. Ensuite, nous identifions les axes principaux du nuage de points, en utilisant un algorithme très utilisé appelé décomposition en valeurs singulières. Le premier axe principal est la direction de la plus grande variance du nuage de points. La seconde est la direction orthogonale à la première. Le rapport d’élongation sera défini comme le rapport des écarts-types dans les deux directions principales.
Illustrons ce principe avec une image de banane :
# VOTRE CODE ICI
raise NotImplementedError()
# Build the cloud of points defined by the foreground image pixels
F = foreground_filter(img)
xy = np.argwhere(F)
# Build the picture
fig = Figure(figsize=(20, 5))
# Original image
subplot = fig.add_subplot(1, 3, 1)
subplot.imshow(img)
subplot.set_title("Original image", fontsize=18)
# The foreground as a black and white picture
subplot = fig.add_subplot(1, 3, 2)
subplot.imshow(foreground_filter(img))
subplot.set_title("Foreground", fontsize=18)
# The cloud of points, as a scatter plot, together with the principal axes
subplot = fig.add_subplot(1, 3, 3)
subplot.scatter(xy[:,1], xy[:,0])
elongation_plot(img, subplot)
subplot.set_xlim(0, 31)
subplot.set_ylim(31, 0)
subplot.set_aspect('equal', adjustable='box')
subplot.set_title("Cloud of points and principal axes", fontsize=18)
fig
Exercice
Essayez de nouveau avec d’autres figures
assert img != images['b01.png']
Indication
Pour aller plus loin
L’astuce a été d’utiliser la décomposition en valeurs singulières. Nous avons vu ce principe lors du CM4 qui parlait en détail des ACP (Analyses en Composantes Principales, PCA en anglais) sur des poissons. Vous verrez les mathématiques derrière cette méthode lors de vos cours d’algèbre linéaire mais sachez que, grâce aux bibliothèques existantes, vous pouvez déjà utiliser cette méthode en quelques lignes !
show_source(elongation)
Conclusion#
Exercice
Ici, nous avons implémenté l’extraction de deux attributs, suffisants pour séparer les pommes des bananes. Cherchez quels attributs vous pourriez utiliser pour vos images et implémentez dans utilities.py les fonctions adéquates !
Mettez à jour votre rapport, puis passez à la feuille d”analyse de données.