Cours 6: Traitement d’images


Fanny Pouyet
L1 Informatique
Janvier - Mai 2022

Précédemment

  • Chaîne de traitement d’analyse de données

  • Application aux images : extraction d’attributs

  • Classificateurs

Cette semaine

  • Préparation des données : traitement d’images

Mise en contexte

Pour le moment nous avons traité des images «parfaites» pour notre problème:

  • images toutes la même taille,

  • images carrées,

  • fond blanc ou uni (pour le mélanome),

  • objet d’étude centré.

Dans la suite du cours et pour votre Projet 2, vous allez étudier des images imparfaites, que vous aurez peut-être vous même prises:

  • tailles variables (résolution, format paysage ou portrait, …),

  • fond «complexe»,

  • objet pas forcément centré,

  • images volumineuses

Aujourd’hui nous allons voir comment traiter ces images pour pallier à ces difficultés. Vous appliquerez ces méthodes lors du TP6

Les grandes étapes du traitement

  1. Transformation d’une image en image carrée

  2. Extraction de l’objet

  3. Recadrage et centrage de l’image

  4. Réduction de la résolution

# Automatically reload code when changes are made
%load_ext autoreload
%autoreload 2
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt
from scipy import signal

from utilities_cours_correction import *
from intro_science_donnees import *

Lors de la séance 6 nous allons traiter cette image:

img

Transformation en image carrée

Nous allons la transformer en image carrée à l’aide de la fonction crop, qui veut dire rogner en anglais.

img_carree = img.crop(box=(x_gauche, y_haut, x_droite, y_bas))
img_carree

Extraction de l’objet

Comment extraire le smiley ?

Filtre pixel-à-pixel

On peut regarder chaque pixel et déterminer s’il fait partie de l’objet ou bien de l’arrière plan. S’il est jaune, il appartient à l’objet! Pour cela trois étapes:

  1. En TP vous commencerez par construire la fonction yellowness_filter. Elle renvoie l’intensité de jaune pour chaque pixel. En RGB, le jaune \(y\) (pour yellow) se calcule comme suit:

    \(y = r + g -b \)

    La formule \(r+g-b\) est un produit scalaire \(v.w\) entre le vecteur \(v = (r,g,b)\) et le vecteur \(w=(1,1,-1)\).

    Le vecteur \(w\) a le coefficient \(-1\) pour le canal bleu car le jaune n’en a pas.

  1. Puis vous créerez la fonction color_correlation_filter qui calcule pour chaque pixel d’une image, sa corrélation avec une couleur donnée (dans notre exemple le jaune).

  1. En appliquant un seuil à la sortie de cette fonction vous obtiendrez une matrice de booléens : True si le pixel appartient à l’objet, False sinon.

plt.imshow(foreground, cmap='gray');

Recadrage

Ensuite on cherchera à recadrer l’image. On commencera par calculer son barycentre.

Définition du barycentre : Le barycentre est le centre de gravité d’un objet.

« Tout corps pesant a un centre de gravité bien défini en lequel tout le poids du corps peut être considéré comme concentré. » Archimède.

Par exemple, pour le disque c’est son centre, pour un triangle l’intersection entre les trois médianes

On effectuera un nouveau rognage (crop) centré autour du barycentre

Réduction de la résolution

Parfois certaines images sont trop volumineuses et on peut ainsi vouloir réduire leur résolution.

En TP nous verrons comment passer d’une image 256x256 en 128x128 à l’aide de différentes méthodes d’anti-crénelage: le sous-échantillonage et le lissage par convolution.

Anti-crénelage par sous-échantillonage

Lorsque la résolution d’une image est trop faible, les contours lisses d’un objet sont remplacés par des «escaliers».

plt.figure(figsize=(12,12)) 
plt.subplot(1,3,1)    
plt.imshow(resolution256x256)
plt.title("Résolution 256x256", fontsize=18) 
plt.subplot(1,3,2)    
plt.imshow(resolution64x64)
plt.title("Résolution 64x64", fontsize=18) 
plt.subplot(1,3,3)    
plt.imshow(resolution8x8)
plt.title("Résolution 8x8", fontsize=18) 

Solution pour lisser ?

  • On peut échantillonner la couleur des pixels à la limite des zones distinctes (l’objet et l’arrière plan par exemple)

  • puis on remplace ces pixels par le pixel « moyen » de ses voisins. L’image sera plus floue mais ne présentera plus cet aspect d’escalier.

plt.figure(figsize=(12,12)) 
plt.subplot(1,3,1)    
plt.imshow(resolution256x256)
plt.title("Résolution 256x256 ", fontsize=18) 
plt.subplot(1,3,2)    
plt.imshow(resolution64x64_lisse)
plt.title("Résolution 64x64 lissé", fontsize=18) 
plt.subplot(1,3,3)    
plt.imshow(resolution8x8_lisse)
plt.title("Résolution 8x8 lissé", fontsize=18) 

Lissage par convolution

Convolution d’images : En traitement d’images, une matrice de convolution ou noyau est une petite matrice utilisée pour le floutage, l’amélioration de la netteté de l’image, le gaufrage, la détection de contours, et d’autres. Tout cela est accompli en faisant une convolution entre le noyau et l’image (source: wikipédia).

http://mathinfo.alwaysdata.net/2016/11/filtres-de-convolution/

Convolution : Chaque pixel de la nouvelle image est obtenu en multipliant le pixel d’origine et ses voisins par des coefficients (poids ou weight) donnés par une matrice appelée le noyau (kernel). A chaque niveau de la pyramide, on peut donc diminuer la taille de l’image.

Source: wikimedia

La convolution consiste à filtrer nos données et à étudier le résultat.

Exemple mathématique: le résultat correspond au produit de convolution de la fonction rouge sur la fonction bleue. C’est l’intégrale (l’aire jaune) en chaque point de la fonction bleue quand on lui applique la rouge.

Source : wikimedia

Il existe une multitude de noyaux. Nous en verrons 2 en TP:

  • un noyau \(3\times3\) : le pixel d’origine et ses huit voisins (plus on s’éloigne du centre, plus le poids diminue)

  • la pyramide de Gauss : les poids correspondent à une moyenne discretisée de la fonction normale (en fait, une fonction binomiale cf. le théorème central limite)œ

ker
# l'objet foreground_cropped_clean sera créé en TP
plt.imshow(foreground_cropped_clean, cmap='gray');

Traitement au bord : Comment faire pour les pixels aux bords ?

  • Enroullage (Wrap) : Les valeurs sont récupérées des bords opposés.

  • Extension : Le pixel le plus près du bord est dupliqué pour donner des valeurs à la convolution.

  • Miroir : L’image est reflétée sur les bords.

  • Crop : Tout pixel demandant l’utilisation de pixel en dehors de l’image n’est pas utilisé

Extraction de l’objet

On peut maintenant extraire uniquement l’objet

smiley = transparent_background_filter(img_crop, foreground_cropped_clean)
smiley

Conclusion intermédiaire

À cette étape, nous avons:

  • cadré;

  • filtré le jaune et identifié l’image;

  • recadré et centré l’image;

  • diminué la résolution et lissé;

  • extrait l’image

image_grid(images)

Extraction des contours

Comment feriez vous pour extraire les contours de l’objet ?

Ici, on va calculer la différence entre chaque pixel et son voisin de gauche (c’est aussi de la convolution !).

  • S’ils sont tous les deux en arrière-plan ou bien dans l’objet alors la différence est 0.

  • Sinon, la différence est -1 ou 1, et alors on est sur le contour.

Pour ce calcul, on perd une bande de 1 pixel en largeur; pour conserver l’image carrée, on supprimera aussi une bande de 1 pixel en hauteur.

# M est la matrice de booléens obtenue après lissage
# M[:-1, 1:] : on enlève la dernière ligne et la première colonne
# M[:-1, 0:-1] : on enlève la dernière ligne et la dernière colonne

contour_horizontal = np.abs(M[:-1, 1:] - M[:-1, 0:-1])
plt.imshow(contour_horizontal, cmap='gray');

Superposition d’images

Superposer deux images revient a remplacer les pixels d’une image par ceux d’une autre image. Remplacons ici, un oeil par une banane.

Prenons une image de banane des TP précédents. Cette image fait 32x32 pixels, ce qui est beaucoup plus petit que notre smiley qui fait 256x256! Dans le cas inverse nous devrions réduire la résolution avant l’incorporation.

banana = fruits[19]
M = np.array(smiley)
plt.imshow(M);
B = np.array(banana)
i = 106
j = 100
P = M[i:i+32, j:j+32]
F = foreground_filter(banana)
P[F] = B[F]
M[i:i+32, j:j+32] = P
j = 136
M[i:i+32, j:j+32] = P

j=200
i= 30
M[i:i+32, j:j+32] = P

plt.imshow(M);

Réalisation d’une animation

En fin de TP, vous assemblerez toutes ces images pour faire une mini-vidéo!

  • Les première vidéos faisaient 16/24 fps (frames per second; images par seconde).

  • Aujourd’hui à la TV on est en moyenne à 60 fps.

  • En fait, 30 fps est un ordre de grandeur confortable.

  • En TP, nous ferons une vidéo faisant 2 fps (on n’a qu’une dizaine d’images).

Remarques conclusives et domaine de la vision par ordinateur

Aujourd’hui nous avons vu comment traiter des images :

  • cadrage et centrage;

  • filtrage;

  • changement de résolution par convolution;

  • extraction de l’image et manipulation;

  • superposition d’images;

  • animations.

Ces manipulations peuvent aller beaucoup plus loin; c’est le domaine de recherche de la vision par ordinateur (computer vision). Elle vise à:

  • reconnaître des image

  • les comprendre

  • traiter les informations qui en découlent

Un des objectifs de ce domaine est de comprendre et reproduire l’oeil humain, et plus généralement le système visuel humain. Connaissez vous des exemples d’applications de ce domaine ?

  • Mouvements et reconnaissance d’activités

Reconnaissance d'activités en vidéo

  • Voitures autonomes

    Identification des piétons et planification de leur intention

    http://wider-challenge.org/img/2019/portfolio/pedestrian.jpeg
Source : Thèse de Marin Toromanoff, Septembre 2021, tel-03347567
  • Biomédical

    Classification de grains de beauté, analyses d’imagerie médiale (IRM, scanners etc), reconnaissance d’espèces de bactéries

dataset_dir = os.path.join(data.dir, 'Melanoma')
gdb = load_images(dataset_dir, "*.jpg")
image_grid(gdb, titles=gdb.index)

Perspectives

  • CM7

    • Biais dans les données et notion de métadonnées

  • CM8

    • La classification d’images en 2022: RCNN

  • CM9

    • Éthique et déontologie de l’IA

  • TP6

    • Traitement d’images (en vue du projet 2)

    • Rendu obligatoire avant Lundi 14 mars 22h.