Traitement d'image

Introduction

L’objectif de cette activité est d’illustrer le rôle du calcul matriciel en traitement numérique des images.

pip install numpy matplotlib

Il existe de nombreux formats d’images numériques. On distingue deux catégories :

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. Parfois un quatrième pour un canal alpha qui gère la transparence.

Chargement des images

Téléchargez les quelques images suivantes:

images.zip

Images jack.jpg, black_pearl.jpg et iut.jpg

Puis exécutez le code suivant:

import numpy as np
import matplotlib.pyplot as plt

# Chargement d'une image normalisée entre 0 et 1
img = plt.imread("jack.jpg") / 255.

# Affichage de l'image
plt.imshow(img)

Voici ce qui devrait apparaître:

1) Exercices basiques

Exercice 1: Accès pixel
Accédez à la composante d'un des pixels de l'image et affichez la

Exercice 2: Inverse
É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.

Exercice 3: Couleurs
É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)).

Exercice 4: Extraction
É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.

Exercice 5. Aléatoire:

  • Écrire une fonction randimg_gsc(height, width) qui crée une image de taille height \(\times\) width dans laquelle le niveau de gris de chaque pixel est choisi au hasard.
  • Faire de même dans avec une image couleur dans laquelle les valeurs de rouge, vert, et bleu de chaque pixel sont choisies au hasard.

Exercice 6. Conversion en niveau de gris
Pour convertir une image couleur RGB en une image en niveaux de gris, il faut calculer la luminance de chaque pixel. Cette luminance est une combinaison linéaire des composantes rouge, vert et bleu. À un pixel p, de composantes (r,g,b) dans l’image en couleurs, on associe le niveau de gris :

$$ l = 0.2126 \times r + 0.7152 \times g + 0.0722 \times b $$

Écrire une fonction niveau_gris qui prend en paramètre une image en couleurs et renvoie une image en niveaux de gris.

Lorsque vous affichez une image en niveau de gris, pensez à passer l'argument `cmap="gray"` à matplotlib:

plt.imshow(img_gray, cmap="gray")

Exercice 7. Histogramme

Ecrire une fonction count_histo_gsc(img), qui :

  • Transforme chaque valeur de gris (un float) en un entier compris entre 0 et 255,
  • Compte le nombre de fois où chaque valeur entière apparaît dans l’image.
  • Cette fonction construit un histogramme des niveaux de gris de l’image passée en paramètre

Exercice 8. Mélange d'images:

On mélange deux images de même taille en calculant une moyenne pondérée des pixels des deux images source. La formule ci-dessous illustre le calcul à réaliser :

$$ \begin{pmatrix} r \\ g \\ b \end{pmatrix} = \alpha \begin{pmatrix} r_1 \\ g_1 \\ b_1 \end{pmatrix} + (1 - \alpha) \begin{pmatrix} r_2 \\ g_2 \\ b_2 \end{pmatrix} \space \text{où} \space \alpha \in [0, 1] $$

Écrire une fonction mix(src1,src2,factor) qui retourne une image issue du mélange des images src1 et src2 passées en paramètres avec un poids (\(\alpha\)) égal à factor.

Note: Vous utiliserez black_pearl.jpg pour l'autre image

Une autre manière de mélanger des images consiste à multiplier les composantes des pixels d’une image avec celles d’un masque (i.e. une image noir et blanc). La formule ci-dessous illustre le calcul à réaliser :

$$ \begin{pmatrix} r' \\ g' \\ b' \end{pmatrix} = \begin{pmatrix} r \times r_m \\ g \times g_m \\ b \times b_m \end{pmatrix} $$

Écrire une fonction multiply(src,mask) qui retourne une image issue du produit pixel par pixel d’une image src avec un masque mask.

Note: Vous utiliserez iut.jpg en tant que masque

2) Les filtres

2.1. Filtre de flou

Le principe des filtres de flous est de calculer la couleur d’un pixel de l’image destination à partir de la couleur du pixel de l’image source et de ses voisins. En général, le voisinage est carré, centré sur le pixel courant. Plus le voisinage choisi est grand, plus l’image est floue.

Pour le fitre moyenneur, la couleur d’un pixel de l’image de destination est la moyenne des couleurs du pixel source et de ses voisins.

Exercice 9: Ajout de flou

Écrire une fonction blur(src,s) qui floute une image src passée en paramètre, en utilisant un filtre

moyenneur, et un voisinage carré de demi-largeur \(s\).

2.2. Filtre de convolution

Nous travaillerons ici avec des images en niveau de gris

La convolution est courante en traitement d’images. Elle consiste en une opération de multiplication de deux matrices de tailles différentes (généralement une petite et une grande), mais de même dimensionnalité semblable (p.ex. 1D, 2D), produisant une nouvelle matrice (également de même dimensionnalité). La convolution est donc le traitement d’une matrice (p.ex. une image) par une autre petite matrice appelée matrice de convolution ou noyau (kernel).

Le filtre parcourt toute la matrice principale de manière incrémentale et génère une nouvelle matrice constituée des résultats de la multiplication. Notez qu’il y a une marge dans la matrice finale pour laquelle nous ne pouvons pas calculer de valeur. Dans le traitement d’image, ceci est utilisé par exemple pour effectuer un flou gaussien, ou détourer les éléments d’une photo

Exercice 10: Convolution

Écrire une fonction convolution(src,kernel) qui applique un filtre de convolution à une image src passée en paramètre, en utilisant un noyau kernel.

La détection de contours est une technique de traitement d’image permettant de détecter les contours d’objets présents dans une image. Elle est souvent utilisée en prétraitement pour des applications de vision par ordinateur, comme la reconnaissance de formes ou la détection d’objets.

Exercice 11: Détection des contours

Écrire une fonction contours(src) qui détecte les contours d’une image src passée en paramètre, en utilisant un filtre de convolution. le kernel est le suivant :

$$ \begin{pmatrix} -1 & -1 & -1 \\ -1 & 8 & -1 \\ -1 & -1 & -1 \\ \end{pmatrix} $$

Les pixels sur les bordures de l'image sont mis à 0.

Vous pouvez essayer avec différents kernels présents sur cette page: https://setosa.io/ev/image-kernels/

3) Stéganographie

3.1 Les opérateurs bit à bit

En Python, les opérateurs au niveau du bit sont utilisés pour effectuer des calculs au niveau du bit sur des entiers. Les entiers sont d’abord convertis en binaire, puis les opérations sont effectuées bit à bit, d’où le nom d’opérateurs au niveau du bit. Ensuite, le résultat est renvoyé au format décimal.

OpérateurOpérationDescription
&ET logiqueMet à 1 si les deux bits sont à 1
|OU logiqueMet à 1 si au moins un bit est à 1
^OU exclusifMet à 1 si un seul bit est à 1
~NON logiqueInverse les bits
<<Décalage à gaucheDécalle les bits à gauche de n positions
>>Décalage à droiteDécalle les bits à droite de n positions

Par exemple:

\(a = 10 = (1010)_2\)

\(b = 4 = (0100)_2\)

\(a \& b = (1010_2) \& (0100)_2 = (000)_2 = 0_{10}\)

\(a | b = (1010)_2 | (0100)_2 = (1110)_2 = (14)_{10}\)

\(\text{~} a = \text{~} (1010)_2 = (0101)_2 = 5_{10}\)

2.2 Les opérateurs de décalage

Ces opérateurs sont utilisés pour décaler les bits d’un nombre vers la gauche ou vers la droite, multipliant ou divisant ainsi le nombre par deux respectivement.

Le décalage à droite au niveau du bit décale les bits du nombre vers la droite et remplit 0 sur les vides à gauche en conséquence. Son effet est similaire à la division du nombre avec une puissance de deux.

\(a = 10 = (00001010)_2\)

\(a \gg 1 = (00000101)_2 = 5_{10}\)

Le décalage à gauche au niveau du bit décale les bits du nombre vers la gauche et remplit 0 sur les vides à droite en conséquence. Son effet est similaire à la multiplication du nombre avec une puissance de deux.

\(a = 5 = (00000101)_2\)

\(a \ll 1 = (00001010)_2 = (10)_{10}\)

$$a \ll 2 = (00010100)_2 = (20)_10$

Exercice 12: Décalage de bits

Nous chargerons les images en entier:

# Les valeurs sont des entiers entre 0 et 255
img = plt.imread("jack.jpg")

Faire subir à chaque composante RGB des pixels un masquage \(n \rightarrow n \& (11110000)_2\) puis afficher l’image obtenue.

2.3 Principe de la stéganographie

Le principe de la stéganographie est de cacher un message(ou une autre image) dans une image source. Pour cela, on va utiliser les bits de poids faible des composantes RGB des pixels de l’image source.

Expliquez votre démarche.

Exercice 13: Stéganographie image

Ecrire une fonction qui prend en paramètre une image et une autre image à cacher de même taille

et qui renvoie l’image avec l’image cachée.

Ecrire une fonction qui prend en paramètre une image et qui renvoie l’image cachée.

Exercice 14: Stéganographie texte

Écrire une fonction qui , cette fois ci, cache un texte à l’intérieure d’une image.

Prévoir une fonction qui permet de récupérer le texte caché dans l’image.