Le filtre de malik est perona est un des premiers filtres basé sur des équations différentielles permettant une diffusion anisotropique.
Au début, certains filtres avaient été proposés (basé sur l'équation de la chaleur), mais avait l'inconvénient de ne pas garder les contours.
Malik et Perona propose de déterminer l'état stationnaire de l'équation :
du/dt = div(g(|grad u|) grad u) où u est l'image.
g est une fonction qui peut prendre plusieurs forme.
Celle que j'ai prise fonctionne bien et a été proposé par malik et perona :
g : x -> lambda²/(lambda² + x²)
J'ai trouvé sur le papier ici :http://www.cs.berkeley.edu/~malik/papers/MP-aniso.pdf assez efficace, simple et rapide.
Déjà la simple fonction qui calcule g (on note lambda2 = lambda²) :
(Math::square calcule le nombre au carré)
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 float PeronaMalikFlow::computeDiffusion(float v) { return (lambda2)/((lambda2) + Math::square(v)); }
Voici le code qui permet de calculer le flux en un point (x,y,canal) précis :
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
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 float PeronaMalikFlow::compute(const Image & image, int x, int y, int canal) { float current = image.getPixel(x,y, canal); int width = image.getWidth(); int height = image.getHeight(); int px = x-1; int nx = x+1; int py = y-1; int ny = y+1; if (px<0) px=0; if (nx>=width) nx=width-1; if (py<0) py=0; if (ny>=height) ny=height-1; float ixp = image.getPixel(px, y, canal); float ixn = image.getPixel(nx, y, canal); float iyp = image.getPixel(x, ny, canal); float iyn = image.getPixel(x, py, canal); float diffxn = computeDiffusion(current - ixn); float diffxp = computeDiffusion(current - ixp); float diffyn = computeDiffusion(current - iyn); float diffyp = computeDiffusion(current - ixp); float delta = (diffxn * (ixn - current)) + (diffxp * (ixp - current)) + (diffyp * (iyp - current)) + (diffyn * (iyn - current)); return delta; }
Et voici la résolution :
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
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 /** * Résout une équation de type perona et malik suivant un diffuseur particulier * * @param out l'image de sortie * @param in l'image d'entrée * @param dt la discrétisation spatiale * @param nbiter le nombre d'itération pour la résolution * * @throw Millie::IllegalArgument si le nombre de composantes de out et * de in ne sont pas pareils * @throw Millie::IllegalArgument si dt<=0 * @throw Millie::IllegalArgument si nbiter<0 */ void Millie::peronaMalikOperator(Image & out, const Image & in, float dt, int nbiter) { if(out.getNumComponents() != in.getNumComponents()) throw IllegalArgument("peronaMalikOperator"); if(dt<0) throw IllegalArgument("peronaMalikOperator negative value"); if(nbiter<=0) throw IllegalArgument("peronaMalikOperator : <= iteration"); /*image temporaire*/ Image itempo(in.getWidth(), in.getHeight(), in.getNumComponents()); out = in; itempo = in; int n; int i,j; for(n=0; n<nbiter;n++) { for(int canal = 0; canal< in.getNumComponents(); canal++) for(j=0;j<in.getHeight();j++) for(i=0;i<in.getWidth();i++) { float value = dt * ( compute(out, i, j, canal)) //calcul de la diffusion + out.getPixel(i,j, canal); itempo.setPixel(i, j, canal, value); } out = itempo; } }
A noter que dans la fonction : compute, on ne prend en compte que les pixels au nord, est, sud et ouest.
Il existe une version plus sympatique qui prend en compte les 4 autres points (NE, SE, NO, SO) :
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
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 float NeighbourPeronaMalikFlow::compute(const Image& image, int x, int y, int canal) { float current = image.getPixel(x,y, canal); int width = image.getWidth(); int height = image.getHeight(); int px = x-1; int nx = x+1; int py = y-1; int ny = y+1; if (px<0) px=0; if (nx>=width) nx=width-1; if (py<0) py=0; if (ny>=height) ny=height-1; float ixp = image.getPixel(px, y, canal); float ixn = image.getPixel(nx, y, canal); float iyp = image.getPixel(x, ny, canal); float iyn = image.getPixel(x, py, canal); float diffxn = computeDiffusion(current- ixn); float diffxp = computeDiffusion(current- ixp); float diffyn = computeDiffusion(current- iyn); float diffyp = computeDiffusion(current- ixp); float iNE = image.getPixel(nx, py, canal); float iSW = image.getPixel(px, ny, canal); float iNW = image.getPixel(px, py, canal); float iSE = image.getPixel(nx ,ny, canal); float diffNE = computeDiffusion(current - iNE); float diffSW = computeDiffusion(current- iSW); float diffNW = computeDiffusion(current- iNW); float diffSE = computeDiffusion(current- iSE); float delta = diffxn * (ixn - current) + diffxp * (ixp - current) + diffyp * (iyp - current) + diffyn * (iyn - current) + 0.5f * ( diffNE * (iNE- current) + diffSW * (iSW - current) + diffSE * (iSE - current) + diffNW * (iNW -current) ); return delta; }
Voici par exemple ce que ça donne :
dt = 0.1, lambda = 5, nbiter = 20
Image bruitée :
Image corrigée :
Partager