Algèbre linéaire et traitement des images

TROISIÈME PARTIE : TRANSFORMATIONS GÉOMÉTRIQUES

Étienne Coutant, Olivier Nocent, Frédéric Blanchard

Géométrie

Dans cette activité, vous aurez à nouveau besoin des modules suivants :

from math import *
import numpy as np
import matplotlib.pyplot as plt

ainsi que des fonctions display et greyscale, des parties précédentes.

display([img,img],5)

1 Transformations géométriques

Jusqu'à présent, nous avons utilisé les applications linéaires pour modifier les composantes chromatiques d'un pixel. Avec les transformations géométriques, nous allons modifier les coordonnées (j,i) d'un pixel afin de le déplacer au sein de l'image (attention, j désigne toujours l'indice de colonne, et i l'indice de ligne).

coordonnées image/repère

Toutes les transformations que nous allons implémenter se feront relativement à un centre passé en paramètre de chaque fonction. Par conséquent, avant d'appliquer la transformation, il faudra translater le pixel en lui retranchant les coordonnées du centre. Puis, après avoir appliqué la transformation, le pixel résultat sera à nouveau translaté dans le sens opposé en lui ajoutant les coordonnées du centre.

coordonnées centre de l'image

1.1 Symétrie par rapport à l'axe des abscisses

Une symétrie par rapport à l'axe horizontal (passant par le centre de l'image) est définie comme suit :

\left( \begin{array}{c} j' \\ i' \end{array} \right)= \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \left( \begin{array}{c} j - j_C \\ i - i_C \end{array} \right)+ \left( \begin{array}{c} j_C \\ i_C \end{array} \right)

Exercice

Écrire une fonction symetrieAbs(src,centerj,centeri) qui applique une symétrie par rapport à l'axe des abscisses à une image src passée en paramètre.
(on supposera que le centre du repère est placé en (centerj, centeri), c'est à dire à la i-ème ligne et à la j-ème colonne)

display([img,symetrieAbs(img,128,128)],5)

1.2 Changement d'échelle

Un changement d'échelle de centre (j_C,i_C) et de rapport \lambda est défini comme suit :

\left( \begin{array}{c} j' \\ i' \end{array} \right)= \begin{pmatrix} \lambda & 0 \\ 0 & \lambda \end{pmatrix} \left( \begin{array}{c} j - j_C \\ i - i_C \end{array} \right)+ \left( \begin{array}{c} j_C \\ i_C \end{array} \right)

Exercice

Écrire une fonction zoom(src,centerj,centeri,factor) qui applique un changement d'échelle de rapport factor et de centre (centerj,centeri) à une image src passée en paramètre.

Attention

Pour vous assurez d'obtenir des coordonnées (j',i') entières, dans le cas où factor est <1, vous pourrez utiliser mon_array.astype(int).

display([img,zoom(img,128,128,0.5)],5)

On constate qu'avec un facteur de changement d'échelle >1, l'image résultat est creuse. En effet, en plus de déplacer le pixel, la fonction devrait affecter la même couleur aux pixels voisins dans un carré de coté factor.

display([img,zoom(img,64,64,4)],5)

Pour éviter d'obtenir une image creuse, procédons autrement : pour faire un zoom, nous allons parcourir les pixels de l'image destination, puis appliquer la transformation inverse pour retrouver le pixel antécédent.

Exercice

Écrire une fonction smartzoom(src,centerj,centeri,factor) qui corrige le défaut de la fonction zoom en appliquant la transformation inverse aux pixels de l'image destination.

display([img,smartzoom(img,128,128,0.5),smartzoom(img,64,64,4)],5)

1.3 Rotation

Une rotation dans le plan de centre (x_C,y_C) et d'angle \alpha est définie comme suit :

\left( \begin{array}{c} j' \\ i' \end{array} \right)= \begin{pmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{pmatrix} \left( \begin{array}{c} j - j_C \\ i - i_C \end{array} \right)+ \left( \begin{array}{c} j_C \\ i_C \end{array} \right)

Exercice

Écrire une fonction rotate(src,centerj,centeri,alpha) qui applique une rotation de centre (centerj,centeri) et d'angle alpha à une image src passée en paramètre.

Attention

Pour vous assurez d'obtenir des coordonnées (j',i') entières, vous pourrez utiliser mon_array.astype(int).

display([rotate(img,128,128,pi/2),rotate(img,128,128,pi/3),rotate(img,128,128,2*pi/3)],5)

Remarque

Comme l'axe vertical est orienté vers le bas, le sens trigonométrique est similaire au sens des aiguilles d'une montre.

On constate quelques jolis effets de Moiré. Ceci est dû à la nature discrète de la grille de pixels de l'image. Après transformation, les coordonnées du pixel sont modifiées pour n'en conserver que la partie entière, ce qui explique que certaines cases de l'image ne sont jamais remplies.

Exercice

Pour corriger ce défaut, écrire une fonction smartrotate(src,centerx,centery,alpha) qui parcourt les pixels de l'image destination, et applique une rotation d'angle -alpha pour retrouver le pixel antécédent.

display([smartrotate(img,128,128,pi/2),smartrotate(img,128,128,pi/3),smartrotate(img,128,128,2*pi/3)],5)

1.4 Twist

Le twist consiste en une rotation du plan de centre (j_C,i_C), dont l'angle \alpha est fonction de la distance du pixel au centre.

\left( \begin{array}{c} j' \\ i' \end{array} \right)= \begin{pmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{pmatrix} \left( \begin{array}{c} j - j_C \\ i - i_C \end{array} \right)+ \left( \begin{array}{c} j_C \\ i_C \end{array} \right)

\alpha = \rho \sqrt{ \left(j - j_C\right)^2 + \left(i - i_C\right)^2 }

Exercice

Écrire une fonction twist(src,centerj,centeri,rho) qui applique un twist de centre (centerj,centeri) et de facteur rho à une image src passée en paramètre.

display([twist(img,128,128,0.01),twist(img,128,128,0.02),twist(img,128,128,0.05)],5)

2 Compression d'images

Le principe de la compression avec perte consiste à réduire la taille d'un fichier image en acceptant de perdre de l'information et donc d'altérer l'image d'origine. La technique présentée par la suite est détaillée dans le document Applications_changements_de_base.pdf, et elle est résumée ci-dessous.
Elle permet de diviser la taille d'un fichier par 4.

La technique proposée sera uniquement appliquée à des images de luminance (i.e. en niveaux de gris). Pour des considérations de symétrie, la luminance d'un pixel sera représentée par un nombre l \in [-1,1] lors de l'étape de compression, -1 correspondant au noir et 1 au blanc.

2.1 Compression dans la base canonique

Une première version naïve de la compression consiste à diviser l'image en paquets carrés de 4 pixels, représentés par des vecteurs de [-1,1]^4. Au sein de chaque paquet, on identifie le pixel avec la plus grande luminance en valeur absolue. Seul ce pixel prépondérant est conservé lors de la compression, les luminances des autres pixels étant fixées à 0.

Pour supprimer l'effet dentelle engendré par 3/4 de pixels gris avec une luminance à 0, on peut dupliquer la luminance du pixel prépondérant dans les 3 autres.

Cette première version de la compression peut-être réalisée en 3 étapes :

Exercice

Écrire une fonction compression(src) qui modifie une image de luminance src selon la méthode de compression naïve décrite précédemment.

img_nb = greyscale(img)
display([img_nb,compression(img_nb)],5)

2.2 Bonus : compression dans une base adaptée

Afin d'atténuer l'effet de pixelisation, il paraît judicieux de privilégier une autre base de vecteurs dans [-1,1]^4 qui rend mieux compte des différences de luminance au sein d'un paquet de 4 pixels. Cette nouvelle base est la base B' :
B'=\left\{(1,1,1,1),(1,1,-1,-1),(1,-1,1,-1),(1,-1,-1,1)\right\}, correspondant aux contrastes suivants dans le paquet de 4 pixels :

Une version smart de l'algorithme de compression consiste à :

Exercice

Écrire une fonction R smartcompression(src) qui modifie une image de luminance src selon la méthode de compression smart décrite précédemment.

Remarque

Pour inverser la matrice de passage P, on pourra utiliser np.linalg.inv(P). On pourra aussi utiliser sa fonction inverse(P) créée lors du TP du semestre 1 sur le pivot de Gauss.

display([img_nb,compression(img_nb),smartcompression(img_nb)],5)