TD/TME Traitement d`images

Transcription

TD/TME Traitement d`images
Module AAGB
TD/TME img – page 1/8
TD/TME Traitement d’images
Version du 29 septembre 2016
Ce sujet ne dispose pas de correction automatique.
Objectif(s)
O Initier aux techniques de traitement d’image.
O Consolider les notions des Makefile et des applications composées de plusieurs fichiers.
O Lecture et l’écriture de fichiers textes.
O Répétition sur les enregistrements.
O Répétition de l’allocation dynamique.
Présentation
La manipulation d’images est aujourd’hui une activité importante de l’informatique. Au cours de ce TD/TME vous
allez découvrir des techniques permettant d’améliorer le rendu esthétique d’images mais aussi des techniques dont l’objectif est l’extraction d’informations sur le contenu d’une image.
Ces applications vont vous permettre de consolider vos compétences nouvellement acquises en programmation C.
Dans l’objectif de simplifier votre programmation, nous nous limiterons au traitement d’images en noir et blanc
(en niveaux de gris). Le traitement d’images couleurs (codées en Rouge Vert Bleu) alourdirait votre code du fait de la
nécessité de traiter de façon identique les trois couleurs.
Ce sujet donnera lieu à deux séances de TD et deux séances de TME mais si vous voulez le mener à bout vous devrez
probablement ajouter quelques heures de travail personnel.
Format PGM
Toujours dans l’objectif de simplifier votre travail, nous avons choisi de ne traiter que le format de fichier .pgm
(Portable GrayMap file format). Ce format est peu fréquent mais particulièrement facile à interpréter (”parser”). Vous
pouvez très simplement obtenir des fichiers au format pgm à partir de n’importe quel format standard (.jpg etc..) en
utilisant l’utilitaire en ligne de commande convert ou des outils plus sophistiqués tels que gimp :
convert fichier.jpg fichier.pgm
Wikipédia 1 définit les deux variantes du format .pgm comme suit :
ASCII : Les données sont organisées en lignes, toutes les lignes commençant par # correspondent à des commentaires et doivent être ignorées.
— Une ligne contenant un nombre magique (P2),
— une ligne contenant Largeur de l’image (codée en caractères ASCII), un espace et la hauteur de l’image
(codée en caractères ASCII),
— Une ligne contenant la valeur maximale utilisée pour coder les niveaux de gris, cette valeur doit être inférieure
à 65536 (codée en caractères ASCII).
— Données binaires de l’image :
— L’image est codée ligne par ligne en partant du haut
1. http://fr.wikipedia.org/wiki/Portable pixmap
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 2/8
— Chaque ligne est codée de gauche à droite
— Chaque pixel est codé par une valeur en caractères ASCII, précédée et suivie par un caractère d’espacement. Un pixel noir est codé par la valeur 0, un pixel blanc est codé par la valeur maximale et chaque
niveau de gris est codé par une valeur entre ces deux extrêmes, proportionnellement à son intensité.
— Aucune ligne ne doit dépasser 70 caractères.
binaire : Le début du fichier peut être interprété comme un fichier texte, la fin comme un fichier binaire. Toutes les
lignes commençant par # correspondent à des commentaires et doivent être ignorées.
— Une ligne contenant un nombre magique (P5),
— une ligne contenant Largeur de l’image (codée en caractères ASCII), un espace et la hauteur de l’image
(codée en caractères ASCII),
— Une ligne contenant la valeur maximale utilisée pour coder les niveaux de gris, cette valeur doit être inférieure
à 65536 (codée en caractères ASCII).
— Données binaires de l’image :
— L’image est codée ligne par ligne en partant du haut
— Chaque ligne est codée de gauche à droite
— Chaque pixel est codé par 1 ou 2 octets selon que la valeur maximale est inférieure ou supérieure à 256.
Un pixel noir est codé par la valeur 0, un pixel blanc est codé par la valeur maximale et chaque niveau de
gris est codé par une valeur entre ces deux extrêmes, proportionnellement à son intensité.
Voici un exemple de format ASCII pour une image de 7 lignes constituées de 24 pixels codés sur un octet dans
l’intervalle [0; 15].
P2
# Commentaire bla bla
24 7
15
0 0 0 0 0 0 0 0
0 3 3 3 3 0 0 7
0 3 0 0 0 0 0 7
0 3 3 3 0 0 0 7
0 3 0 0 0 0 0 7
0 3 0 0 0 0 0 7
0 0 0 0 0 0 0 0
0
7
0
7
0
7
0
0
7
0
7
0
7
0
0
7
0
0
0
7
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0 0 0 0
11 11 11 11
11 0 0 0
11 11 11 0
11 0 0 0
11 11 11 11
0 0 0 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0 0 0 0
15 15 15 15
15 0 0 15
15 15 15 15
15 0 0 0
15 0 0 0
0 0 0 0
0
0
0
0
0
0
0
Pour simplifier le code à écrire, nous allons nous limiter à des images constituées de 255 niveaux de gris dont les
valeurs des pixels seront donc codées sur un octet.
Structure des fichiers
Certains fichiers sont fournis complets d’autres sont à compléter. Tous sont à copier depuis le répertoire :
/Infos/lmd/2016/licence/ue/2I001-2016oct/fournis/S4-5
image.h : fichier à récupérer et qui contient la définition du type image t, de la macro VAL et les prototypes des
fonctions de création, destruction et copies.
image.c : fichier à écrire, contenant les implémentations des fonctions déclarées dans le .h.
pgm image.h : fichier à récupérer et qui contient les prototypes de lecture et d’écriture des fichiers .pgm.
pgm image.c : fichier à écrire, contenant les implémentations des fonctions déclarées dans le .h.
trans image.h : fichier à récupérer et qui contient les prototypes de toutes les fonctions de transformation d’images.
trans image.c : fichier à écrire, contenant les implémentations des fonctions déclarées dans le .h.
noyau.h : fichier à récupérer et qui contient la description du type noyau t et les prototypes des fonctions correspondantes.
noyau.c : fichier à écrire et qui contient les implémentations des fonctions déclarées dans le .h.
bindump.c : un petit utilitaire permettant de visualiser le contenu de fichiers binaires et/ou texte.
image test bin.pgm et image test bin.pgm : fichiers images exemples, n’hésitez pas à en ajouter.
noyau *.txt : Fichiers fournis correspondant à des noyaux de convolution typiques.
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 3/8
Pour visualiser les images chargées nous allons utiliser la bibliothèque GTK qui est à la base de l’environnement
GNOME dont la maı̂trise nécessite une expérience certaine. Un canevas très complet permettant la manipulation de
fenêtres graphiques vous est fourni. Cette fourniture est composée trois fichiers :
guimpe.c : fichier contenant la fonction main, que vous ne devriez pas avoir à modifier sauf si souhaitez modifier
l’interface.
guimpe callback.c : Fichiers contenant tous les appels aux fonctions que vous avez à écrire, à modifier seulement
pour les dernières questions (UNDO . . .).
Makefile : fichier permettant de compiler vos fichiers sources et réalisant l’édition de lien avec la bibliothèque GTK.
Exercice(s)
Lecture, écriture et affichage de fichiers images
Exercice 1 (obligatoire) – Manipulation du type image t
La structure image t est définie comme suit :
typedef struct
{
unsigned long w; // largeur en pixels
unsigned long h; // hauteur en pixels
char *path; // le chemin absolu du fichier correspondant
unsigned char *buff; // w x h octets correspondant aux pixels
} image_t;
1. Écrivez la fonction (fichier image.c) :
image_t *creer_image();
Cette fonction alloue une structure image t, initialise tous ses champs à 0 ou NULL et retourne un pointeur sur la
structure allouée. Attention, cette fonction n’alloue pas le buffer correspondant à l’image.
2. Écrivez la fonction (fichier image.c) :
void detruire_image(image_t *p);
Cette fonction libère la mémoire allouée pour une image.
3. Écrivez la fonction (fichier image.c) :
image_t *copier_image(image_t *src);
Cette fonction alloue une nouvelle image et l’initialise comme une copie de l’image fournie en argument.
Exercice 2 (obligatoire) – Fonctions de lecture et d’écriture de fichiers .pgm
1. Écrivez la fonction (fichier pgm image.c) :
image_t *charger_image_pgm(char *nom_fichier);
Cette fonction alloue une nouvelle image à partir de la lecture du fichier nom fichier fournie en argument. Cette
fonction devra traiter les différentes erreurs possibles : fichier inexistant, format incorrect, etc ...
Nous vous invitons à dans un premier temps ne traiter que les fichiers de pgm ASCII et afficher un message d’erreur
si le fichier débute par autre chose que P2.
2. Écrivez la fonction (fichier pgm image.c) :
int sauver_image_pgm(char *nom_fichier, image_t *img);
Duale de la précédente, cette fonction permet de sauvegarder une image dans un fichier au format .pgm. Cette
fonction devra traiter les différents cas d’erreur. Par cohérence avec la lecture vous ne traiterez que le cas ASCII.
3. A cette étape vous devez tester le programme complet, vous devriez arriver à afficher une image. Si vous avez
récupéré tous les fichiers, il vous suffit de lancer la commande make pour tout recompiler. Lancez ensuite l’exécutable
guimpe et demandez la lecture d’un fichier à partir du menu.
Pour tester la sauvegarde il vous suffit d’enregistrer l’image sous un autre nom et la charger à nouveau.
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 4/8
Transformation d’images
Dans un premier temps, il va être question de transformations simples ne nécessitant aucune compétence mathématique
particulière. Dans un second temps, un peu de théorie vous sera présentée, mais sa compréhension ne sera pas indispensable à la réalisation des questions.
Dès que vous avez écrit une fonction, vous pouvez la tester en recompilant l’intégralité de l’application avec un make
et en lançant l’exécutable guimpe.
Exercice 3 (obligatoire) – Transformations simples d’une image
1. Écrivez la fonction (fichier trans image.c) :
image_t *negatif(image_t *src);
Cette fonction réalise une copie de l’image fournie en argument src et inverse tous les pixels de la copie. Cette
fonction retourne un pointeur sur la nouvelle image.
L’inversion des nuances de gris se fait par une boucle qui parcourt chaque pixel de l’image, récupère sa nuance de
gris et l’inverse (le blanc devient noir, le gris clair devient gris foncé etc..).
2. Écrivez la fonction (fichier trans image.c) :
image_t *rotation(image_t *src, int angle);
Cette fonction réalise une copie de l’image fournie en argument src et applique une des trois rotations possibles
à cette image (90 ˚ , 180 ˚ ou 270 ˚ ). La rotation est considérée dans le sens trigonométrique. La fonction devra
vérifier la validité de l’argument angle fourni. Cette fonction retourne un pointeur sur la nouvelle image. Nous
vous conseillons d’utiliser la macro VAL pour simplifier l’écriture de votre code.
#define VAL(img,i,j) (img)->buff[(i)*(img)->w+(j)]
Où i correspond à l’indice de ligne et j à celui de colonne.
Une autre transformation simple à appliquer sur une image est la modification de sa luminosité.
3. Écrivez la fonction (fichier trans image.c) :
image_t *modifier_lumin(image_t *src, int pourcent);
Cette fonction réalise une copie de l’image fournie en argument src et modifie la valeur des pixels suivant un ratio
défini en % par l’argument pourcent. Prenez soin de ne pas affecter une valeur supérieure à 255 à un pixel. 100%
correspond à l’identité. Une valeur inférieure à 100 assombrira l’image et une valeur supérieure l’éclaircira.
Filtrage d’images
Exercice 4 (obligatoire) – Filtres intégrateurs
Une bonne connaissance du filtrage d’images n’est pas nécessaire pour faire ces questions. Si néanmoins vous voulez
aller plus loin que ce qui est dit ici, vous pouvez consulter le polycopié suivant qui a inspiré ce qui suit : www.ensta.
fr/˜manzaner/Cours/Poly/Filtrage.pdf
Principe : Les méthodes de filtrage d’images qui font objet de ce TD/TME sont basées sur des opérations matricielles
(convolution, rotation). Chaque filtre va être décrit par une matrice de dimension N*N, avec N impair, matrice appelée
noyau de filtrage. Cette matrice va décrire le comportement du filtre et permettre de calculer l’image filtrée.
Suivent trois formules permettant de calculer les coefficients de ces matrices. Ces formules donnent la valeur du
coefficient se trouvant à la position (x, y) dans la matrice. Les valeurs correspondantes devront ensuite être normalisées,
c’est à dire divisées par la somme de tous les coefficients de la matrice.
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 5/8
Moyenneur
h(x, y) = 1
Tous les coefficients valant 1, leur somme est N*N et leur valeur normalisée est donc :
hn (x, y) =
Gauss
1
N2
h(x, y) =
1
x2 + y 2
exp
−
(
)
2πσ 2
2σ 2
h(x, y) =
γ2
exp(−γ(|x| + |y|))
4
σ est un paramètre du filtre.
Exponentiel
γ est un paramètre du filtre.
Pour ces deux derniers filtres, la valeur normalisée n’est pas évidente à exprimer, néanmoins elle peut se calculer très
simplement en parcourant la matrice et en sommant toutes les valeurs qu’elle contient. La normalisation consistera alors
à diviser chaque coefficient par cette valeur.
On nomme convolution l’opération qui consiste à remplacer la valeur d’un pixel par la somme des produits des
valeurs de ses voisins par les coefficients définis dans un noyau. Le pixel considéré est multiplié par le coefficient qui est
au centre de la matrice (il y a bien une valeur centrale, N étant impair). Le pixel immédiatement à gauche de celui-ci est
multiplié par le coefficient qui est immédiatement à gauche du coefficient central, celui qui est à droite est multiplié par
le coefficient immédiatement à droite du coefficient
central, 
etc.

4 1 0
Si on considère le noyau 3x3 suivant 14  0 2 0 , l’équation suivante nous donne la nouvelle valeur d’un
0 0 −3
1
pixel. Pi,j = 4 (4Pi−1,j−1 + Pi,j−1 + 2Pi,j − 3Pi+1,j+1 )
Voici des exemples typiques de noyaux de filtrage pour une taille N=5 : Moyenneur, Gauss (σ = 1.41) et Exponentiel
(γ = 0.8).


1 1 1 1 1
 1 1 1 1 1 

1 
 1 1 1 1 1 

25 
 1 1 1 1 1 
1 1 1 1 1


11 23 29 23 11
 23 48 62 48 23 

1 
 29 62 80 62 29 

864 
 23 48 62 48 23 
11 23 29 23 11


1 1 3 1 1
 1 3 7 3 1 

1 
 3 7 16 7 3 

80 
 1 3 7 3 1 
1 1 3 1 1
Remarque : Pour simplifier la lecture, le coefficient de normalisation est indiqué devant la matrice. Chacun des
coefficients devra être multiplié par cette valeur.
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 6/8
On peut également remarquer que les filtres présentés sont de type ”intégrateurs” : ils vont avoir tendance à atténuer
les transitions fortes de l’image (par exemple, une transition brutale du noir au blanc va être remplacée par un dégradé).
L’image va paraı̂tre plus ”floue”.
Afin d’éviter l’écriture d’une fonction de filtrage par noyau, nous avons défini dans le fichier noyau.h la structure
noyau t qui permet de représenter un noyau de convolution :
typedef struct
{
unsigned int dim;
int *coeffs;
} noyau_t;
La matrice est stockée dans le tableau à une dimension coeffs dans lequel les lignes seront placées les unes à la
suite des autres. La taille n’étant pas connue à l’avance, le tableau est alloué dynamiquement.
1. Écrivez la fonction (fichier noyau.c) :
noyau_t *creer_noyau(unsigned int dim);
Cette fonction réalise l’allocation de la structure contenant un noyau ainsi que de la mémoire nécessaire pour stocker
la matrice des coefficients.
2. Écrivez la fonction (fichier noyau.c) :
void detruire_noyau(noyau_t *pn);
Les noyaux sont stockés dans des fichiers textes .txt sous la forme d’une première ligne contenant la dimension
du noyau suivie des lignes de la matrice noyau (voir fichiers fournis).
3. Écrivez la fonction (fichier noyau.c) :
noyau_t *charger_noyau(char *nom_fic);
Cette fonction alloue un noyau et l’initialise avec les valeurs lues dans le fichier nom fichier. Elle retourne un
pointeur sur le noyau créé. Après avoir lu la taille du tableau et créé la structure, cette fonction lira le noyau, ligne par
ligne et, dans chaque ligne coefficient par coefficient (chaque coefficient étant transformé d’une chaı̂ne de caractères
en un entier). Il y a plusieurs façons de faire cela, la plus rapide est d’utiliser la fonction strtol :
long strtol(const char *str, char **endptr, int base);
Cette fonction prend en argument une chaı̂ne de caractères str et renvoie le premier entier long qu’elle aura réussi à
extraire de cette chaı̂ne. base doit ici être égal à 0. endptr est un pointeur passé par pointeur. Cet argument permet
de récupérer l’adresse du premier caractère après l’entier lu. Cela permet de faire avancer facilement le pointeur de
lecture dans la chaı̂ne. Si cette fonction n’a pas réussi à lire un entier, alors *endptr prend la valeur de str.
Maintenant que nous avons les noyaux, nous allons pouvoir donner la pêche à nos images grâce au filtrage.
4. Écrivez la fonction (fichier trans image.c) :
image_t *convoluer(image_t *src, noyau_t *pn);
C’est la fonction centrale de ce TME qui réalise la convolution d’une image quelconque par un noyau quelconque.
Cette fonction ne modifie pas l’image source et retourne un pointeur sur l’image produite par la convolution.
Remarque :
— Il faut traiter le cas des pixels se trouvant aux bordures de l’image, pour lesquels la convolution voudrait prendre
en compte la valeur de pixels extérieurs à l’image. Les pixels ”extérieurs” devront alors, dans le calcul, être
remplacés par ceux des bordures de l’image : ainsi P0,0 remplacera les pixels P−1,0 , P−1,−1 et P0,−1 .
— Il faut également traiter le cas ou la somme des coefficients du noyau est égale à 0 pour lequel il ne faut pas faire
de normalisation.
— Dans le cas de coefficients négatifs, la valeur calculée peut être négative ; ce qui n’a pas de sens pour une image,
il faudra alors prendre la valeur absolue.
Ces filtres intégrateurs permettent de supprimer des détails gênants d’une image (bruit de capteur, poussières). Pour
tester leur efficacité, nous allons devoir écrire une fonction permettant de dégrader la qualité d’une image.
5. Écrivez la fonction (fichier trans image.c) :
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 7/8
image_t *bruiter_image(image_t *src, int pourcent);
Cette fonction a pour objet d’ajouter aléatoirement du bruit à une image. Pour chaque pixel de l’image une valeur
aléatoire va être tirée dans l’intervalle [0, 99]. Si cette valeur est inférieure au paramètre pourcent, la valeur du
pixel va être remplacée par une autre valeur tirée aléatoirement dans l’intervalle [0, 255].
Pour l’élimination du bruit, un dernier filtre mérite notre attention : le filtre médian. Il s’agit de remplacer la valeur
d’un pixel par la valeur médiane des pixels de son voisinage. On utilise fréquemment un voisinage de rayon égal à 3
suivant, la valeur retenue pour le pixel (i, j) correspond à la valeur médiane du pixel et celle de ces 20 plus proches
voisins. La figure suivante montre les 20 pixels concernés dans un voisinage 5x5 :
6. Écrivez la fonction (fichier trans image.c) :
image_t *filtrer_median(image_t *src);
Cette fonction affecte à un pixel la valeur médiane parmi sa valeur et celles de ses 20 voisins. Vous pourrez utiliser
les fonctions de tri que vous avez réalisées lors du premier TME.
Exercice 5 (obligatoire) – Filtres dérivateurs
Le filtres dérivateurs servent au rehaussement ou à la détection de contours, entre autres. Ils exploitent la variation
locale d’intensité. Cette variation est mesurée par le gradient vectoriel en fonction des pixels [i, j] :
∇f [i, j] = (
δf
δf
[i, j], [i, j])
δx
δy
Le Laplacien ∆ est une fonction scalaire :
∆f [i, j] = (
δ2f
δ2f
[i,
j],
[i, j])
δx2
δy 2
Il n’est pas indispensable pour la suite que vous connaissiez ces notations mathématiques.
Sobel Les opérations les plus simples des dérivées directionnelles se font par différences finies calculées, encore une
fois, par convolution avec des noyaux simples :
−1
δf
[−1 1] pour une approximation de δf
δx , et 1 pour une approximation de δy .
Comme ces opérations sont généralement très sensibles au bruit, on les combine avec un filtre lisseur en direction
orthogonale à celle de la dérivation. Le calcul des dérivées en x et y revient à la convolution avec les noyaux de Sobel.
fx [i, j] = (f ∗ hx )[i, j]
et
fy [i, j] = (f ∗ hy )[i, j]
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016
Module AAGB
TD/TME img – page 8/8
avec


−1 0 1
hx =  −2 0 2 
−1 0 1
et


−1 −2 −1
0
0 
hy =  0
1
2
1
Laplace Le Laplacien ∆f [i, j] peut donc être approximé par une convolution par le noyau suivant (8-connexité) :


1 1 1
 1 −8 1 
1 1 1
1. Testez les noyaux dérivateurs fournis.
Exercice 6
(approfondissement) – Compléments à l’application
Ces questions sont difficiles. Il n’est pas nécessaire de les avoir réussies pour la suite.
1. Ajoutez les options Annuler ou Rétablir. Les modifications sont à apporter aux fichiers pile image.c et
guimp callback.c. Pour ces fonctions, vous devrez créer une pile de type LIFO. Vous pourrez utiliser une
liste chaı̂née ou, pour simplifier, un tableau (cela imposera un nombre maximum d’opérations susceptibles d’être
annulées).
Vous pourrez vous inspirer du fichier fourni pile image basic.c qui donne un exemple de ces fonctions dans
le cas où il n’y a qu’une seule image et pas de pile.
c
2016-2017
(by UPMC/Master BIM/AAGB)
29 septembre 2016