onilink_ Modérateur
Messages : 9183 Localisation : Montpellier Projet Actuel : Planet Centauri
OniDev
| Sujet: [Tutoriel]Initiation au traitement d'images numériques Mer 6 Avr 2011 - 20:43 | |
| Initiation au traitement d'images numériques
Introduction
Une image numérique est définie comme une matrice de taille Largeur*Hauteur. Elle se lit de gauche a droite et de haut en bas. Le pixel de position [0,0] est donc celui en haut a gauche. Chaque valeur de cette matrice représente une couleur.
Pour image en niveaux de gris on pourras coder tonalité de 0 a 255. Pourquoi de telles valeurs ? Car physiquement, c'est la capacité d'un octet (ou huit bits). De ce fait chaque pixel de l'image seras codé sur un octet.
La taille (ou poids) qu'une image prend en mémoire est donc facile a calculer : taille = largeur * hauteur
On appelle le nombre de niveaux de gris la tonalité maximale moins la tonalité minimale de chaque pixel. Une image ayant peu de niveaux de gris pourras être codé sur moins de bits mais auras une moins bonne résolution.
Pour coder une image de couleur il faut d'abord savoir ce qu'est une couleur. "La couleur est la perception subjective qu'a l'œil d'une ou plusieurs fréquences d'ondes lumineuses, avec une (ou des) amplitude(s) donnée(s)." (wikipedia) Pour faire simple la couleur d'un rayon lumineux dépend de la longueur d'onde de celui ci, donc de sa fréquence. Ainsi pour les fréquences comprises entre 480 et 405 est associé la couleur rouge, celles comprises entre 510 et 480 le orange, et ainsi de suite. La lumière visible pour l'œil humain est dans le domaine des fréquences comprises entre 405 et 790 (Infrarouges et ultraviolets).
D'où le spectre lumineux suivant : Intéressons nous maintenant a la perception des couleurs par l'œil humain. L'œil humain permet la perception des couleurs grâce a des récepteurs, les cônes. Il y en a 3 types, et chacun d'eux est sensible a un domaine de fréquences particulier. Les cônes L sont sensibles aux rouges, les cônes M au verts et les cônes S au bleu. D'où leur nom : ondes longues -> Long, ondes moyennes -> Median et ondes courtes -> Small.
Une couleur peut donc être codée avec une tonalité de rouge, de vert et de bleu. C'est la synthèse additive, le contraire de la synthèse soustractive qui elle est visible dans les mélanges de peintures par exemple (de la peinture jaune mélangé a de la peinture cyan donneras du vert.) Elle est un peu déconcertante au début, mais on s'y habitue vite.
Mélanges des couleurs primaires de la lumière en mode additif Nous allons donc utiliser 3 octets pour coder une couleur (un octet par composante). Nous noterons donc une couleur dans une notation hexadécimale, qui simplifieras l'écriture d'une couleur. L'octet de poids fort coderas le bleu et celui de poids faible de rouge. La couleur 0xFFFFFF seras donc du blanc (soit r=255,g=255,b=255), la couleur 0x000000 du noir, 0xFF du rouge, 0xFF00 du vert, 0xFF0000 du bleu. Le 0x signifie que nous utilisons une notation hexadécimale.
La taille d'une image 24 bits (true color) est donc : taille = largeur * hauteur * 3
Il existe aussi les images 32 bits, qui elles ont en plus un canal alpha. Les formats png, tga et bmp gèrent sans problèmes ces formats.
Maintenant que nous savons ce qu'est une image numérique, nous allons apprendre a créer des filtres de traitement. Ces filtres servirons a améliorer la qualité de nos images (modification du contraste par exemple), ou a obtenir divers effets intéressants.
Matrices de convolution
Une méthode de traitement efficace d'image numérique est l'utilisation d'une convolution entre l'image, et le filtre. Une convolution est un traitement d'une matrice par une autre appelée matrice de convolution (ici le filtre).
La convolution d'une matrice I par la matrice F consiste a calculer pour chaque point de I la somme des produits de chaque point avec la matrice F. Voici un schémas qui montre comment calculer la valeur d'un pixel de l'image a filtrer.
Nous travaillerons avec des valeurs allant de 0 a 255 dans notre image, et par conséquent une image obtenue après convolution avec un filtre ne doit pas comporter de pixel ayant une valeur en dehors de ce champs. Hors nous observons qu'avec le filtre précédent, la valeur d'un point peut aller jusqu'à 3*255. Pour pallier a ce problème il faut normaliser. Cela consiste a, si la somme des valeurs d'un filtre (que l'on noteras S) est supérieur a 1, ou inférieur a 0, de diviser après la convolution la valeur du point obtenu par S. Ce qui empêcheras les résultat d'avoir des valeurs normalement impossibles pour une matrice représentant une image.
Passons a la pratique !
Mettons maintenant la théorie en pratique. Pour la suite j'utiliserai µDev++, un logiciel qui nous permettra de coder notre application en C++ sans avoir a nous soucier du fenêtrage, du temps, et autres... Bien sur vous pouvez utiliser tout autre logiciel mais après c'est a vous de voir (et donc de 'traduire' le code en conséquences).
Tout d'abord voici l'image que je vais utiliser pour tester chacun des filtres que nous créerons :
La première opération va consister a stocker notre image dans une matrice de unsigned int. Voici le code µDev pour charger l'image en mémoire (dans "Create") : - Code:
-
uint w, h, *data = image_load_bmp("im_ref.bmp", w, h); et pour libérer la mémoire utilisé (a la fin du "Create", ou dans "End") : - Code:
-
delete[] data; Essayons d'afficher notre image pour voir si tout va bien : - Code:
-
uint w, h, *data = image_load_bmp("im_ref.bmp", w, h); for(int x=0; x<w; x++) for(int y=0; y<h; y++) { draw_point_color(x, y, data[x+w*y]); } delete[] data;
Voila. Maintenant nous pouvons créer notre matrice de convolution.
- Code:
-
const int filter_w=3, filter_h=3; double filter[filter_w][filter_h] = { 0, 0, 0, 0, 1, 0, 0, 0, 0 }; double factor = 1.0; double sway = 0;
factor est la valeur dont je vous ai parlé plus haut, celle qui serviras a normaliser. sway elle s'additionnera a la valeur de chaque pixel après convolution (utile dans le cas ou une image est trop sombre par exemple).
Maintenant pour appliquer la convolution il va falloir appliquer deux balayages successifs. Un serviras a parcourir l'image, un autre le filtre pour faire le calcul de convolution. - Code:
-
glBegin(GL_POINTS); // Pour indiquer a openGl que nous allons commencer a dessiner des points for(int x=0; x<w; x++) // Double boucle pour balayer l'image a filtrer for(int y=0; y<h; y++) { int r = sway, g = sway, b = sway; // Les composantes rgb ou l'on stockera la valeur obtenu pour la convolution au point de coordonnées (x,y) for(int xx=0; xx<filter_w; xx++) // Balayage du filtre for(int yy=0; yy<filter_h; yy++) { int X = (x+xx-filter_w/2+w)%w; int Y = (y+yy-filter_h/2+h)%h; byte *c = (byte*) &data[X + Y*w]; // Un pointeur qui nous permettra de récupérer les composantes rgb de la couleur du pixel de position (x,y) comme s'il s'agissait d'un simple tableau de byte. // Calculs de la convolution r += (double)c[0] * filter[xx][yy]; g += (double)c[1] * filter[xx][yy]; b += (double)c[2] * filter[xx][yy]; } if(r<0) r=0; if(g<0) g=0; if(b<0) b=0; // au cas ou on aurais des valeurs négatives // Et on affiche le point de la couleur obtenue glColor3ub(r*factor, g*factor, b*factor); glVertex2d(x, y); } glEnd();
Et voici le code complet du filtrage par matrice de convolution : - Code:
-
window_set_size(640, 360);
// Le filtre const int filter_w=3, filter_h=3; double filter[filter_w][filter_h] = { 0, 0, 0, 0, 1, 0, 0, 0, 0 }; double factor = 1; double sway = 0;
uint w, h, *data = image_load_bmp("im_ref.bmp", w, h);
glBegin(GL_POINTS); for(int x=0; x<w; x++) for(int y=0; y<h; y++) { int r = sway, g = sway, b = sway; for(int xx=0; xx<filter_w; xx++) for(int yy=0; yy<filter_h; yy++) { int X = (x+xx-filter_w/2+w)%w; int Y = (y+yy-filter_h/2+h)%h; byte *c = (byte*) &data[X + Y*w];
r += (double)c[0] * filter[xx][yy]; g += (double)c[1] * filter[xx][yy]; b += (double)c[2] * filter[xx][yy]; } if(r<0) r=0; if(g<0) g=0; if(b<0) b=0; glColor3ub(r*factor, g*factor, b*factor); glVertex2d(x, y); } glEnd();
delete[] data;
Bien sur ce code va se contenter de nous afficher la même image que celle d'entré. Si vous observez le filtre c'est tout a fait logique. Pour briser la monotonie nous allons donc commencer a créer nos filtres (et qui ont un effet cette fois, si je suis pas trop gentil )
Flou simple
- Code:
-
double filter[filter_w][filter_h] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; double factor = 1.0/9.0; double sway = 0;
Ce filtre va nous faire un flou des plus simple. Pour chaque pixel on fait la moyennes avec ses voisins.
Flou gaussien
- Code:
-
double filter[filter_w][filter_h] = { 1, 2, 1, 2, 4, 2, 1, 2, 1 }; double factor = 1.0/16.0; double sway = 0;
Un filtre un peu plus recherché qui nous donne un flou de bonne qualité :p
Augmenter le contraste
- Code:
-
double filter[filter_w][filter_h] = { 0,-1, 0, -1, 5,-1, 0,-1, 0 }; double factor = 1; double sway = 0;
Repoussage
- Code:
-
double filter[filter_w][filter_h] = { -2,-1, 0, -1, 1, 1, 0, 1, 2 }; double factor = 1; double sway = 0;
Détection des contours simple
- Code:
-
double filter[filter_w][filter_h] = { 0, 0, 0, -1, 1, 0, 0, 0, 0 }; double factor = 1; double sway = 0;
Détection des contours (Laplaciens)
- Code:
-
double filter[filter_w][filter_h] = { 0,-1, 0, -1, 4,-1, 0,-1, 0 }; double factor = 1; double sway = 0;
- Code:
-
double filter[filter_w][filter_h] = { -1,-1,-1, -1, 8,-1, -1,-1,-1 }; double factor = 1; double sway = 0;
- Code:
-
double filter[filter_w][filter_h] = { 1,-2, 1, -2, 4,-2, 1,-2, 1 }; double factor = 1; double sway = 0;
Flou cinétique diagonal (léger)
- Code:
-
double filter[filter_w][filter_h] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; double factor = 1.0/3.0; double sway = 0;
Bien sur vous n'êtes pas limité dans la taille du filtre. Par exemple pour avoir un filtre cinétique de meilleur qualité vous pouvez utiliser une matrice de 5x5 : - Code:
-
const int filter_w=5, filter_h=5; double filter[filter_w][filter_h] = { 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, }; double factor = 1.0/8.0; double sway = 0;
Par contre plus la matrice seras grande et plus les calculs seront longs.
Voila, je présenterai d'autres méthodes de traitement prochainement, comme le traitement par histogramme et la transformée de fourrier. Aussi le tuto n'est pas fini, je prendrais le temps de le relire plus tard, et corrigerais les éventuelles fautes.
Dernière édition par onilink_ le Mer 6 Avr 2011 - 22:23, édité 4 fois |
|