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

  1. 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 tableau R.

    • Ensuite, sachez que si on a un tableau R et un tableau booléen (tel que F) de mêmes dimensions, alors R[F] renvoie un tableau avec uniquement les valeurs R[i,j] telles que F[i,j] vaut True.

    • Enfin, nous rappelons que np.mean(R) calcule la moyenne de toutes les valeurs d’un tableau R ou G ;

    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.