[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> Général
> Vision par Ordinateur > Notions de base et traitement d'images > Lissage : moyenne et médiane
> Lecture du tutoriel
Lissage : moyenne et médiane
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Je sens bien que vous êtes déçus.
Le programme du TP 2 n'a pas suffi à rafistoler notre image

.
Il faut que vous sachiez une chose : en vision par ordinateur,
un traitement, c'est rarement assez. En règle générale, on applique toute une palette d'effets pour que l'image de base devienne exploitable (ou "jolie", si on se contente du traitement d'image).
Le TP 2 illustre d'ailleurs parfaitement la situation.
Il faut que je vous avoue que je n'ai pas pris cette image par hasard : elle provient d'un ensemble d'images standard utilisées communément dans le Monde pour tester les algorithmes de vision

.
Vous en découvrirez d'autres du même ensemble dans la suite (en particulier la jolie Lena et son regard énigmatique : la Joconde de la Vision par Ordinateur

).
Mais pour l'instant, on va essayer de débruiter notre pauvre demoiselle en niveaux de gris

.
Vous êtes prêts ?
On y va.
Le bruit... une définition statistique
Le
bruit, d'une manière générale, désigne un phénomène qui
"brouille" la perception d'un signal, et nous
gène.
Classiquement, lorsque vous faites de la guitare électrique à 15h30, c'est
de la musique pour la voisine d'en face qui est jolie comme tout, mais à 2h du matin avec vos potes, c'est
du bruit pour le voisin du dessous super-grincheux

.
En traitement d'images, ce que l'on appelle le
bruit, ce sont
les pixels qui ont des valeurs aberrantes.
En statistiques, on appelle de tels pixels (ou de telles valeurs), des
outliers (prononcez "a-out-la-yeur-z").
Quel est le rapport avec les statistiques ??
Eh bien, en réalité, le bruit est ce qui sort de la
norme. Or on peut considérer (en étant souple sur les définitions

) que les statistiques étudient les
tendances d'une population qui définissent une norme...
C'est ce qui nous a amené à utiliser deux indicateurs statistiques, la
moyenne et la
médiane, pour éliminer le bruit d'une image.
Ce parallèle avec les statistiques n'est pas gratuit.
L'exemple du lissage en traitement d'images est une excellente illustration du fait que la médiane est un indicateur plus robuste aux outliers que la moyenne (comme tout statisticien pourra vous le dire).
Je vous propose d'essayer les deux, pour que vous puissiez le constater
.
Bruit... et voisinage
Contrairement à ce que vous pouvez croire, je ne pensais pas particulièrement au
super-grincheux du dessous quand j'ai écrit ce titre

, même si cette comparaison est incroyablement juste !
Continuons donc dans cette voie.
Pour qu'il n'y aie plus de bruit quand vous avez des voisins grincheux, il faut que vous vous "
conformiez au comportement de votre voisinage" :
la nuit, à 2h du mat', vous devez dormir,
comme vos voisins (ce qui n'est pas forcément le cas dans l'internat en face de chez vous où tout le monde est peut-être en train de faire la fête, ce qui fait un barouf de tous les diables).
Maintenant imaginez que vous êtes un pixel blanc tout seul au milieu d'une forme sombre.
Vous êtes alors un
outlier, on vous considère comme
du bruit (et il y a de grandes chances pour que ce soit effectivement le cas).
Eh bien c'est pareil :
Pour qu'un pixel ne soit plus un pixel "de bruit", il faut
le conformer à son voisinage.
Mais c'est quoi le voisinage d'un pixel ?
J'y viens !
Considérons l'image suivante :
Comme vous le constatez, il s'agit d'une image
relativement uniforme ("gris moyen" en fait

) , avec 3 pixels aberrants.
Maintenant dans cette configuration nous allons considérer le pixel de la 4ème ligne / 4ème colonne.
Il s'agit donc de

(petit rappel au passage

).
Alors dans cette configuration, le
voisinage 3x3 de ce pixel est la "sous-image" :

,
c'est-à-dire
le carré de côté 3 pixels centré sur lui 
.
On peut définir aussi le voisinage 5x5, 7x7...
3x3, 5x5, 7x7... Pourquoi c'est des nombres impairs ?
Eh bien, c'est simplement pour pouvoir
centrer le carré sur le pixel en question

.
Filtre moyen
Le filtre
moyen consiste à
conformer un pixel avec son voisinage en le remplaçant par la
moyenne des niveaux de gris de son voisinage.
Ainsi, pour notre pixel de tout à l'heure avec son voisinage :
Nous allons remplacer

par :

.
Vous voyez, un "122" au milieu de plein de "120", en niveaux de gris, franchement ça fait moins tâche qu'un 255

. A 2 niveaux de gris près, on ne voit pas la différence à moins d'y faire vraiment très attention, et encore !
Filtre médian
Le filtre médian, c'est la même chose que le filtre moyen, sauf que cette fois on remplace le pixel par la
médiane de son voisinage.
La médiane, c'est quoi ?
La médiane, grossièrement c'est "la valeur du milieu".
Vous allez comprendre

.
Toujours avec notre même voisinage :
On va prendre toutes les valeurs de ce voisinage et
les classer (par ordre croissant ou décroissant, peu importe) :
médiane
Dans un ensemble
ordonné et
classé de 9 nombres, la médiane est la valeur du 5ème élément.
La formule pour avoir le "rang de la médiane", c'est :
Cette formule marche parce que 9 est un nombre impair. Et si on a un nombre impair de valeurs, c'est parce que notre voisinage est un carré de côté impair (ici 3).
Pour en revenir au filtre médian dans notre exemple, le pixel

est maintenant remplacé par

.
C'est
encore mieux qu'avec le filtre moyen 
.
Un problème gênant : l'effet de bord
Quand on commence à jouer avec les voisinages, on est très vite confronté à ce que l'on appelle
l'effet de bord.
Illustration

:
(Non, ce n'est pas de l'art moderne

.)
Cette image illustre le fait que l'
on ne peut pas dépasser les bords de l'image. Or lorsque l'on doit considérer le voisinage d'un pixel du bord, c'est ce qui risque de se produire.
Il n'existe pas de méthode standard pour y remédier.
On peut choisir de
ne pas s'occuper des pixels du bord en les laissant de côté (ce qui nous retourne une image plus petite), ou bien
en les laissant tels quels.
Ou encore on peut décider de
définir un voisinage plus petit au cas par cas (c'est lourd comme solution !), ou de
définir un cadre d'une couleur arbitraire autour de l'image pour quand même traiter les pixels, etc...
Quoi qu'on choisisse, le résultat est le même :
les pixels du bord ne sont pas traités comme les autres.
Personnellement, je choisis l'option numéro 2 :
nous allons laisser les pixels du bord tels quels.
Cela nous évite de faire des traitements fastidieux pour traiter les bords à tout prix d'une part, et nous procurera une image de même taille qu'à l'origine d'autre part

.
Avant de passer au TP numéro 3, nous allons voir comment définir une "région d'intérêt" sur l'image.
Pour cela, nous allons utiliser la sous-bibliothèque
cxcore d'OpenCV.
Nous rajouterons donc dans les "includes" :
Code : C++1
2
3
4
5 | //Sous Linux
#include <opencv/cxcore.h>
//Sous Windows
#include <cxcore.h>
|
La structure CvRect
Rapidement, voici le détail d'une structure dont nous allons nous servir, la structure
CvRect (rectangle).
Champs de la structure
Si
rect est un
CvRect, alors :
- rect.x est l'abscisse du coin en haut à gauche (l'origine
) du rectangle.
- rect.y est l'ordonnée du même coin.
- rect.width est la largeur du rectangle.
- rect.height est... sa hauteur.
Créer un rectangle
Très simple ! On utilise la fonction :
CvRect cvRect(int x, int y, int width, int height);
Par exemple :
Code : C++1
2
3
4 | //Je crée un rectangle dont l'origine a pour coordonnées (150,10),
//De largeur 30 pixels,
//De hauteur 50 pixels.
CvRect mon_rectangle = cvRect(150, 10, 30, 50);
|
Le champ "ROI" de IplImage
"ROI" est l'acronyme de "Region Of Interest", en français "Région d'intérêt".
Il sert à
isoler une partie de l'image de manière à se concentrer dessus.
En fait, notre image n'est pas modifiée, seulement on ne considère plus qu'un
rectangle avec son propre repère. On définit de cette manière
une sous-image 
.
Lorsque la région d'intérêt est définie dans une image en OpenCV, alors tous les changements que l'on applique à l'image, ne seront en fait appliqués qu'
à cette région de l'image. Et les coordonnées des pixels auxquels on accède
sont définies à partir de l'origine de la région d'intérêt.
Pour résumer :
La région d'intérêt (ROI) permet de faire abstraction de tout ce qui est autour de la sous-image : le reste "n'existe plus".
Définir, relâcher une région d'intérêt
Pour définir la région d'intérêt d'une
IplImage, on utilisera la fonction :
void cvSetImageROI(IplImage* image, CvRect roi);
et pour travailler à nouveau sur toute l'image :
void cvResetImageROI(IplImage* image);
Exemple :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | //Je crée un rectangle dont l'origine a pour coordonnées (150,10),
//De largeur 30 pixels,
//De hauteur 50 pixels.
CvRect mon_rectangle = cvRect(150, 10, 30, 50);
//Je définis la région d'intérêt de mon image.
cvSetImageROI(mon_image, mon_rectangle);
//Là je vais accéder au pixel de coordonnées (153, 17) de l'image,
//C'est-à-dire de coordonnées (3, 7) sur la région d'intérêt.
CvScalar scalaire = cvGet2D(mon_image, 7, 3);
//[...]
//Maintenant je relâche la région d'intérêt
cvResetImageROI(mon_image);
//Et là j'accède au pixel de coordonnées (3, 7) de l'image,
//pas de la ROI : elle n'existe plus ;)
scalaire=cvGet2D(mon_image, 7, 3);
|
Ce TP est très long. Il est conseillé de le voir éventuellement en plusieurs fois.
D'abord le filtre moyen, puis le filtre médian

.
J'espère que vous avez bien digéré ce qui précède.
Nous allons implémenter le
filtre moyen et le
filtre médian afin de débruiter l'image suivante :
Filtre moyen
Nous allons créer la fonction :
void filtreMoyenNVG(IplImage *src, IplImage *dst, int voisinage);
où
src et
dst sont les images "source" et "destination" du filtre
de taille identique et en niveaux de gris, et
voisinage est un entier qui désigne le côté du carré de voisinage.
Vérifications préliminaires
Tout d'abord, il faut vérifier que les images sont au bon format, avec les bonnes tailles, et que le nombre
voisinage est bien impair.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12 | //On vérifie que les 2 images que l'on nous a fournies sont bien en NVG
if(src->nChannels!=1||dst->nChannels!=1)
return;
//On vérifie que les 2 images ont les mêmes dimensions
if((src->width!=dst->width)||(src->height!=dst->height))
return;
//On vérifie que le voisinage est impair,
//on le corrige si ce n'est pas le cas
if(voisinage%2!=1)
voisinage++;
|
Vous avez reconnu le petit opérateur
% :
On vérifie la parité en regardant
le reste de la division euclidienne par 2.
S'il est égal à 0 le nombre est pair, et s'il est égal à 1 le nombre est impair.
Initialisations
On va initialiser le
CvRect pour la région d'intérêt, ainsi qu'un entier pour calculer la moyenne et un petit scalaire "pour la route"

.
Code : C++1
2
3
4
5
6
7
8 | //On initialise un carré de côté "voisinage"
CvRect roi=cvRect(0,0,voisinage,voisinage);
//On initialise un entier, pour la moyenne
int moyenne=0;
//Un petit scalaire pour la route ^^
CvScalar scalaire;
|
On accédera plus tard directement aux champs de
roi.
Parcours de l'image, atténuation de l'effet de bord
En parcourant l'image, on va d'abord vérifier que l'on ne se trouve pas trop près des bords.
Si on est trop près, on recopie bêtement les pixels de la source vers la destination.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | //On parcourt toute l'image
for(int x=0; x<src->width; x++)
{
for(int y=0; y<src->height; y++)
{
//Pour chaque pixel
//S'il est trop "au bord" :
if(x<(voisinage-1)/2 || x>(src->width - 1 - (voisinage-1)/2) || y<(voisinage-1)/2 || y>(src->height - 1 - (voisinage-1)/2))
{
//Dans ce cas on recopie simplement le pixel
cvSet2D(dst, y, x, cvGet2D(src, y, x));
}
// [ ... ]
}
}
|
Vérifier que les pixels ne sont pas trop au bord, c'est vérifier que l'on peut centrer le voisinage dessus, d'où l'utilisation du
rayon :
(voisinage-1)/2.
Calcul de la moyenne
Si notre pixel n'est pas au bord, alors on calcule la moyenne de son voisinage :
- On initialise le voisinage.
- On parcourt le voisinage :
- On accumule toute les valeurs du voisinage dans une variable "moyenne".
- On n'oublie pas de relâcher le voisinage.
- On divise la variable "moyenne" par le nombre de pixels du voisinage : (hauteur * largeur).
- On remplace la valeur du pixel dans l'image destination.
Une fois codé et commenté, ça donne quelquechose qui ressemble à :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | //On parcourt toute l'image
for(int x=0; x<src->width; x++)
{
for(int y=0; y<src->height; y++)
{
//Pour chaque pixel
//S'il est trop "au bord" :
if(x<(voisinage-1)/2 || x>(src->width - 1 - (voisinage-1)/2) || y<(voisinage-1)/2 || y>(src->height - 1 - (voisinage-1)/2))
{
//Dans ce cas on recopie simplement le pixel
cvSet2D(dst, y, x, cvGet2D(src, y, x));
}else
{
//On remet la moyenne à zéro
moyenne=0;
//On centre le voisinage sur le pixel en cours
roi.x= x - (voisinage-1)/2;
roi.y= y - (voisinage-1)/2;
//On initialise la région d'intéret
cvSetImageROI(src, roi);
//On parcourt le voisinage
for(int i=0; i<voisinage; i++)
{
for(int j=0; j<voisinage; j++){
//On récupère les valeurs du voisinage que l'on additionne
scalaire = cvGet2D(src, j, i);
moyenne+= scalaire.val[0];
}
}
//On relache la région d'intéret
cvResetImageROI(src);
//On calcule la moyenne
scalaire.val[0] = moyenne/(voisinage*voisinage);
//On la remplace dans l'image de destination
cvSet2D(dst, y, x, scalaire);
}
}
}
|
Filtre médian
Pour le filtre médian, le corps de la fonction sera le même que pour le filtre moyen.
Voici sa signature :
void filtreMedianNVG(IplImage *src, IplImage *dst, int voisinage);
Initialisation
Au lieu d'initialiser un entier
moyenne, nous allons avoir besoin d'un
tableau d'entiers que nous appellerons
voisins.
Attention à ne pas oublier de le désallouer à la fin du traitement.
Si je suis regardant là-dessus, c'est parce que la gestion de la mémoire va devenir
très importante lorsque l'on travaillera avec des vidéos.
Imaginez que vous allouez un tableau de 10x10 (=100) pixels en niveaux de gris 60 fois par seconde. Si vous ne le désallouez pas, vous prenez 6Ko de mémoire dans la vue par seconde.
C'est de l'ordre de
20 Mo par heure.
Sur des traitements de vidéo temps réel comme la surveillance intelligente, les programmes peuvent tourner des
semaines... et on utilise des images
un peu plus grosses que 10x10

.
Donc :
Code : C++1
2
3
4
5
6
7 | //On initialise un tableau d'entiers, pour le calcul de la médiane
int *voisins=new int[voisinage*voisinage];
//[...]
//On le désalloue à la fin
delete[] voisins;
|
Calcul de la médiane
Voici comment on calcule la médiane. C'est
comme en théorie, on applique la formule

.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | //On initialise la région d'intéret
cvSetImageROI(src, roi);
//On parcourt le voisinage
for(int i=0; i<voisinage; i++)
{
for(int j=0; j<voisinage; j++)
{
//On récupère les valeurs du voisinage
scalaire = cvGet2D(src, j, i);
voisins[i*voisinage + j] = scalaire.val[0];
}
}
//On relache la région d'intéret
cvResetImageROI(src);
//On classe les valeurs
sort( voisins, voisins + (voisinage*voisinage));
//On choisit la valeur médiane
scalaire.val[0] = voisins[ (voisinage*voisinage-1)/2 + 1];
|
Vous remarquerez que l'on utilise la fonction "
std::sort(pointeur début, pointeur fin)" pour trier le tableau "
voisins".
Il faut donc que l'on insère dans les "include" :
Code : C++
Et voilà ! tout le reste vous savez faire

.
Programme final
Vous disposez de
tous les outils nécessaires pour réaliser le code.
Alors soyez honnêtes envers vous-mêmes :
Essayez de réfléchir un peu avant de regarder la solution

.
Code : C++ - lissage.cpp 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 | /**
* lissage.cpp
**/
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv/cxcore.h>
#include <algorithm>
using namespace std;
/**
* Lissage d'une image en NVG au moyen du filtre moyen.
**/
void filtreMoyenNVG(IplImage *src, IplImage *dst, int voisinage){
//On vérifie que les 2 images que l'on nous a fournies sont bien en NVG
if(src->nChannels!=1||dst->nChannels!=1)
return;
//On vérifie que les 2 images ont les mêmes dimensions
if((src->width!=dst->width)||(src->height!=dst->height))
return;
//On vérifie que le voisinage est impair,
//on le corrige si ce n'est pas le cas
if(voisinage%2!=1)
voisinage++;
//On initialise un carré de côté "voisinage"
CvRect roi=cvRect(0,0,voisinage,voisinage);
//On initialise un entier, pour la moyenne
int moyenne=0;
//Un petit scalaire pour la route ^^
CvScalar scalaire;
/////////// FIN DE L'INITIALISATION //////////////
//On parcourt toute l'image
for(int x=0; x<src->width; x++)
{
for(int y=0; y<src->height; y++)
{
//Pour chaque pixel
//S'il est trop "au bord" :
if(x<(voisinage-1)/2 || x>(src->width - 1 - (voisinage-1)/2) || y<(voisinage-1)/2 || y>(src->height - 1 - (voisinage-1)/2))
{
//Dans ce cas on recopie simplement le pixel
cvSet2D(dst, y, x, cvGet2D(src, y, x));
}else
{
//On remet la moyenne à zéro
moyenne=0;
//On centre le voisinage sur le pixel en cours
roi.x= x - (voisinage-1)/2;
roi.y= y - (voisinage-1)/2;
//On initialise la région d'intéret
cvSetImageROI(src, roi);
//On parcourt le voisinage
for(int i=0; i<voisinage; i++)
{
for(int j=0; j<voisinage; j++)
{
//On récupère les valeurs du voisinage que l'on additionne
scalaire = cvGet2D(src, j, i);
moyenne+= scalaire.val[0];
}
}
//On relache la région d'intéret
cvResetImageROI(src);
//On calcule la moyenne
scalaire.val[0] = moyenne/(voisinage*voisinage);
//On la remplace dans l'image de destination
cvSet2D(dst, y, x, scalaire);
}
}
}
}
/**
* Lissage d'une image en NVG au moyen du filtre médian.
**/
void filtreMedianNVG(IplImage *src, IplImage *dst, int voisinage){
//On vérifie que les 2 images que l'on nous a fournies sont bien en NVG
if(src->nChannels!=1||dst->nChannels!=1)
return;
//On vérifie que les 2 images ont les mêmes dimensions
if((src->width!=dst->width)||(src->height!=dst->height))
return;
//On véridie que le voisinage est impair, on le corrige si ce n'est pas le cas
if(voisinage%2!=1)
voisinage++;
//On initialise un carré de côté "voisinage"
CvRect roi=cvRect(0,0,voisinage,voisinage);
//On initialise un tableau d'entiers, pour le calcul de la médiane
int *voisins=new int[voisinage*voisinage];
//Un petit scalaire pour la route ^^
CvScalar scalaire;
/////////// FIN DE L'INITIALISATION //////////////
//On parcourt toute l'image
for(int x=0; x<src->width; x++)
{
for(int y=0; y<src->height; y++)
{
//Pour chaque pixel
//S'il est trop "au bord" :
if(x<(voisinage-1)/2 || x>(src->width - 1 - (voisinage-1)/2) || y<(voisinage-1)/2 || y>(src->height - 1 - (voisinage-1)/2))
{
//Dans ce cas on recopie simplement le pixel
cvSet2D(dst, y, x, cvGet2D(src, y, x));
}else
{
//On centre le voisinage sur le pixel en cours
roi.x= x - (voisinage-1)/2;
roi.y= y - (voisinage-1)/2;
//On initialise la région d'intéret
cvSetImageROI(src, roi);
//On parcourt le voisinage
for(int i=0; i<voisinage; i++)
{
for(int j=0; j<voisinage; j++)
{
//On récupère les valeurs du voisinage
scalaire = cvGet2D(src, j, i);
voisins[i*voisinage + j] = scalaire.val[0];
}
}
//On relache la région d'intéret
cvResetImageROI(src);
//On classe les valeurs
sort( voisins, voisins + (voisinage*voisinage));
//On choisit la valeur médiane
scalaire.val[0] = voisins[ (voisinage-1)/2 + 1];
//On la remplace dans l'image de destination
cvSet2D(dst, y, x, scalaire);
}
}
}
delete[] voisins;
}
int main()
{
//On déclare deux images : celle de base, et celle qui sera étirée
IplImage *image = cvLoadImage("/home/arnaud/Images/enhance-me_etiree.png");
IplImage *image_nvg = cvCreateImage(cvGetSize(image), image->depth, 1);
IplImage *image_median= cvCloneImage(image_nvg);
IplImage *image_moyen= cvCloneImage(image_median);
//Correction de l'origine si nécessaire
int flip = 0;
if(image->origin!=IPL_ORIGIN_TL){
flip = CV_CVTIMG_FLIP;
}
cvConvertImage(image, image_nvg, flip);
//Filtre moyen
filtreMoyenNVG(image_nvg, image_moyen, 5);
//Filtre median
filtreMedianNVG(image_nvg, image_median, 5);
//On affiche les résultats dans une fenêtre
cvNamedWindow("Moyen 5x5", CV_WINDOW_AUTOSIZE);
cvShowImage("Moyen 5x5", image_moyen);
cvNamedWindow("Median 5x5", CV_WINDOW_AUTOSIZE);
cvShowImage("Median 5x5", image_median);
//On attend que l'utilisateur appuie sur une touche
cvWaitKey(0);
cvDestroyWindow("Moyen 5x5");
cvDestroyWindow("Median 3x3");
cvReleaseImage(&image);
cvReleaseImage(&image_nvg);
cvReleaseImage(&image_moyen);
cvReleaseImage(&image_median);
return 0;
}
|
Résultat et conclusion
Je vous ai dit au tout début que
la médiane est un indicateur plus robuste au bruit que
la moyenne.
Démonstration :
J'ai utilisé un voisinage de 5x5 pour les deux filtres (taille minimale pour totalement supprimer le bruit avec au moins l'une des deux méthodes), et voici le "avant - après".
Avant
Après
Vous voyez...
Y'a pas photo !
... sans mauvais jeu de mots

.
Pour aller plus loin...
Il est possible d'obtenir d'encore meilleurs résultats (éviter que le résultat soit "trop flou") en considérant
le cas particulier de notre image :
Vous avez remarqué que la majorité du bruit est composée de pixels totalement blancs (puisque l'image est étirée

).
Dans ce cas précis, il nous suffirait de ne modifier
QUE les pixels blancs dans un premier temps (rajouter une condition au filtre médian, pour l'adapter à ce cas particulier).
Puis de faire un lissage avec un second filtre médian 3x3, mais cette fois sur toute l'image, pour "virer le reste du bruit" (les points noirs).
Vous pouvez le coder si vous êtes curieux

.
Vous pouvez aussi chercher votre méthode pour vous affranchir des effets de bord.
Ce n'est pas forcément très utile du point de vue "vision", mais ça peut être intéressant pour votre curiosité personnelle et votre compréhension du problème.
Moi, j'ai pu obtenir quelquechose comme ça :
C'est un "peu mieux qu'avant" mais pas révolutionaire

.
Soufflez, reposez-vous !
Ce chapitre était extrêmement chargé.
Vous allez me tuer...
Essayez ("juste pour rigoler"), de remplacer la fonction du filtre médian dans votre main, par :
Code : C++1 | cvSmooth(image_nvg, image_median, CV_MEDIAN, 5);
|
...

...
Aaaaargh ! tu nous as fait coder tout ça pour rien !!
Non, non, rassurez-vous, je ne vous ai pas fait faire tout ça pour le simple plaisir de réinventer la roue (en moins bien de surcroît) !
Le but est de vous faire coder
vos propres traitements d'image afin que vous les
compreniez 
.
Parfois, on tombe sur un traitement qui est déjà implémenté dans OpenCV (comme pour le filtre médian), mais quand ça sera le cas,
je vous le préciserai toujours.
Et puis, avouez que vous êtes au moins un peu fiers du résultat qu'on obtient avec
NOTRE filtre médian.
Allez, promis, le prochain chapitre sera beaucoup moins lourd, pour vous permettre de récupérer.