TP1: Coloration de l'Empire Russe

«Le but de ce travail est de générer automatiquement une image couleur à partir des plaques de verre numérisées de la collection Prokudin-Gorskii, et ce, avec le minimum d'artifacts visuels possible. Pour ce faire, il vous faudra extraire les trois canaux de couleurs, les chevaucher l'un «par-dessus» l'autre, et les aligner pour que leur combinaison forme une image couleur en RGB. Dans ce TP, nous ferons l'hypothèse qu'un simple modèle de translation (en x,y) est suffisant pour aligner les images correctement. Par contre, puisque les plaques de verre numérisées ont une très grande résolution, votre procédure d'alignement devra être rapide et efficace.»

1. Approche à une seule échelle

L'alignement à une seule échelle permet d'aligner les 3 canaux de l'image pour une image à résolution relativement faible. Le décalage est limité à $[-10, 10]$ pixels en $x$ et $y$. L'algorithme consiste à appliquer un déplacement de toutes les permutations possibles de décalage sur le canal R et, par la suite, sur le canal G. À chaque décalage, on calcule la norme L2 entre le canal décalé et le canal B. Pour chacun des canaux (R et G), on conserve le déplacement optimal, celui avec l'erreur la plus basse.

Au niveau du code source, l'image de base est importée et divisée en 3 canaux dans le constructeur de la classe Image. Par la suite, l'appel de la fonction Image.align() sur cet objet effectue l'alignement à une seule échelle.

Résultats
00106v
00888v
00907v
01031v
01880v
00757v
00889v
00911v
01657v

2. Approche à échelles multiples

L'approche à échelles multiples réutilise la technique d'alignement à une seule échelle, mais avec des images de taille réduite sur plusieurs itérations. Ceci permet de trouver le décalage optimal sans essayer toutes les possiblités et accelère grandement le processus.

L'approche, avec 4 itérations, est la suivante:

  1. On copie l'image courante;
  2. On coupe l'image de moitié de façon à conserver le centre et exclure les bordures;
  3. On diminue la taille de l'image d'un ratio de $\frac{1}{2^{(2-i)}}$ où $i$ est l'itération courante ($\frac{1}{8}$, $\frac{1}{4}$, $\frac{1}{2}$ et $1$);
  4. L'algorithme de l'approche à une seule échelle est effectuée sur l'image réduite;
  5. Le déplacement optimal de l'image réduite est appliqué sur l'image original en le multipliant avec l'inverse du ratio de diminution ($8$, $4$, $2$ et $1$);
  6. Les étapes précédentes sont effectuées de nouveau pour la seconde et troisième itération.

Cette approche permet de trouver le décalage optimal dans une plage de $[-150, 150]$ en $x$ et $y$ en évitant de calculer la norme L2 sur toutes les possibilités ($4\times21^2$ au lieu de $301^2$ pour chaque axe).

Images fournis
00029u
00128u
00737u
00892u
01047u
00087u
00458u
00822u
01043u
Images suplémentaires
01858u
01862u
01866u
01870u
01876u
01882u
01860u
01863u
01867u
01871u
01877u
01883u
01861u
01864u
01869u
01872u
01880u

3. Mettez-vous dans la peau de Prokudin-Gorskii!

Le script rgb2pgpy permet de convertir trois images RGB en une image du même style que celles de Prokudin-Gorskii. Ce script a été utilisé pour produire les images personnelles suivante. | Images personnelles | :---: | :---: | :---: 1
| 2
| 3

Sur l'image de gauche, on voit que la position de la caméra c'est décalé entre chaque photo car on peut distinguer les canaux B et R. Pour l'image du centre, la caméra était plus stable et on le remarque sur la photo. Pour celle de droite, le sujet et le repère de la caméra été déplacer entre chaque photo. On remarque que les canaux sont bien alignés au centre mais moins sur les côtés. Ceci peut s'expliquer car l'algorithme d'alignement calcul la norme L2 seulement sur le centre de la photo.

4. Crédits supplémentaires

4.1 Accélération GPU

Pour obtenir un gain de performance, la majorité des opérations sont effectuée sur GPU au lieu du CPU. L'approche utilisée est d'employer PyTorch au lieu de NumPy. L'idée est d'effectuer les opérations sur des PyTorch Tensors au lieu des NumPy arrays car ceux-ci supportent l'accélération GPU.

La difficulté avec cette approche est que plusieurs fonctions implémentées pour traiter les Numpy arrays n'ont pas d'équivalent avec les PyTorch Tensors. Par example, le filtre de Prewitt utilisé dans la section 4.4 est disponible depuis SciPy mais pas depuis PyTorch. Le problème est contourné en appliquant la convolution directement sur l'image.

L'utilisation du GPU permet de réduire le temps d'éxécution de plus de 90% pour aligner une image de taille $3238\times3722$ avec l'approche à échelles multiples.

4.2 Transformation affine avec descente du gradient

Pour obtenir une meilleure superposition des différents canaux, une transformation affine est appliquée sur le canal R et G. La transformation affine est une matrice de $2x3$ qui permet d'appliquer une translation, une mise à l'échelle, une rotation et une distorsion à l'image dépendamment de ses valeurs. Pour trouver les valeurs optimales de la matrice, une approche par descente du gradient est utilisée. En partant de la matrice identité, celle-ci est appliquée au canal et la norme L2 est calculée par rapport au canal B. Par la suite, l'erreur est rétropropagée dans la matrice initiale selon la descente de gradient. Cette étape est répétée jusqu'à convergence.

L'implémentation est relativement simple car PyTorch permet de calculer automatiquement les gradients. Ainsi, un Model PyTorch applique la transformation affine et celui-ci possède la matrice de transformation comme paramètre. Après chaque itération, les poids de la matrice sont mis à jour.

Avant Après
Avant
Apres
Avant
Apres
Avant
Apres

4.3 Détection des bordures et recadrage de l'image

Pour détecter les bordures, l'approche est de conserver seulement le blanc et le noir car les coutours de l'image originale sont principalement de cette couleur. Concrètement, sur chaque canal, tous les pixels entre 0.1 et 0.9 sont mis à 0 et les autres sont mis à 1. Par la suite, on additionne les trois canaux ensemble et limite la valeur maximale d'un pixel à 1.

Pour détecter les 4 bordures, on itère de l'extérieur vers l'intérieur de l'image en se limitant à $10\%$ de la taille de l'image sur chacune des bordures. On retient, les deux colonnes et les deux lignes qui sont les plus proches du centre avec une moyenne supérieure à $0.85$.

La figure suivante montre l'image originale (gauche), la détection des bordures (centre) et le résultat après recadrage (droite).

Avant
Avant
crop
Détection
Apres
Après
Avant Après
Avant
crop
Avant
crop
Avant
crop
Avant
crop

On voit rapidement que le découpage n'est pas parfait et que dans plusieurs cas, l'image devrait être encore plus réduite. Ceci est souvent causé par des taches d'un autre canal sur la bordure (bordure gauche de la dernière image) mais aussi par une bordure qui n'est pas suffisamment noire ou blanche à l'origine.

4.4 Détection et correction des imperfections

Pour la correction des imperfections sur les images, l'approche est basée sur le fait que les imperfections sont souvent seulement présentes sur un seul des canaux. Pour les détecter, un filtre un Prewitt est appliqué en $x$ et $y$ individuellement sur chaque canal. Par la suite, en itèrant sur chaque canal, on soustrait les deux autres canaux sur le canal courant en appliquant une dilatation) sur les canaux soustraits. Sur le canal courant, les pixels avec valeurs positives sont considérés comme des imperfections. Ceux-ci sont remplacés par la valeur médiane depuis le canal original. L'opération est répétée pour les deux autres canaux.

La figure suivante montre les différentes étapes.

Original
Original
Prewitt R
Prewitt R
Prewitt G
Prewitt G
Prewitt B
Prewitt B
Différence R
R - (G + B)
Différence G
G - (R + B)
Différence B
B - (R + G)
Correction
Correction
Avant Après
Avant
Apres
Avant
Apres
Avant
Apres
Avant
Apres

On remarque que cette approche est efficace pour les endroits uniformes (comme le ciel) mais moins où il y a beaucoup de détails. Ceci s'explique car, pour la détection des imperfections, un filtre de prewitt est utilisé. Celui-ci ne fait pas la distinction entre une bordure et une tache. Pour obtenir de meilleur résultat, il faut envisager un autre algorithme de détection.

De plus, on remarque que les imperfections de plus grandes tailles ne sont pas corrigées même quand elles sont détectées. Ceci s'explique par le fait qu'elles sont encore visibles sur le canal avec le filtre médian appliqué.