Aller au menu - Aller au contenu

[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)
Avatar
Auteur : NoHaR
Note : 19 / 20 (11 votes)
Visualisations : 4 075
Licence : Creative Commons BY-SA


Plus d'informations Plus d'informations
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 :D ).

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

Vous êtes prêts ?
On y va.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

[Théorie] Bruit et Filtres

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 :D , 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 :
\begin{matrix}120 & 120 & 120 & 120 & 120\\255 & 120 & 120 & 120 & 120\\120 & 120 & 120 & 120 & 0\\120 & 120 & 120 & 255 & 120\\120 & 120 & 120 & 120 & 120\end{matrix}


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 p_{3,3} = 255 (petit rappel au passage :D ).

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

\begin{matrix}120 & 120 & 0\\120 & 255 & 120\\120 & 120 & 120\end{matrix},

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 ;) .

[Théorie] Filtre moyen et filtre médian

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 :

\begin{matrix}120 & 120 & 0\\120 & 255 & 120\\120 & 120 & 120\end{matrix}


Nous allons remplacer p_{3,3} = 255 par :

p_{3,3}^{moy} = \frac{120 + 120 + 0 + 120 + 255 + 120 + 120 + 120 + 120}{9} \approx 121,67 \approx 122.


Vous voyez, un "122" au milieu de plein de "120", en niveaux de gris, franchement ça fait moins tâche qu'un 255 :D . 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 :

\begin{matrix}120 & 120 & 0\\120 & 255 & 120\\120 & 120 & 120\end{matrix}


On va prendre toutes les valeurs de ce voisinage et les classer (par ordre croissant ou décroissant, peu importe) :

\begin{matrix}0 & 120 & 120 & 120 & \underbrace{120}_{\uparrow} & 120 & 120 & 120 & 255\end{matrix}
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 :

5 =\frac{9-1}{2} + 1

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 p_{3,3}=255 est maintenant remplacé par p_{3,3}^{med} = 120.

C'est encore mieux qu'avec le filtre moyen :D .

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 ^^ :

Image utilisateur


(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 ;) .

[OpenCV] Rectangles et voisinages

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 :

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);

[ TP 3 ] Lissage

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 :

Image utilisateur


Filtre moyen



Nous allons créer la fonction :

void filtreMoyenNVG(IplImage *src, IplImage *dst, int voisinage);

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 :

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++
1
#include <algorithm>


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 :D .





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



Image utilisateur


Après



Image utilisateur


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 :

Image utilisateur


C'est un "peu mieux qu'avant" mais pas révolutionaire ^^ .

Q.C.M.

Pour utiliser la structure CvRect et les fonctions permettant de gérer les régions d'intérêt des images (ROI), quelle sous-librairie doit-on inclure ?
La moyenne est un indicateur plus robuste au bruit que la médiane.
[Vu en TP] : Rappel
Pour utiliser la fonction std::sort, on doit inclure...

Statistiques de réponses au QCM


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);


... :-° ...


:colere: Aaaaargh ! tu nous as fait coder tout ça pour rien !! :colere:

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. ^^
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 03/05/2008 à 20:57:08
Modifié : le 20/09/2008 à 12:20:00
Avancement : 100%

21 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 162 Zéros connectés | Requêtes SQL 9 requêtes | Temps de génération de la page : Total (SQL) 0.2649s (0.2512s)