# Discussion: [image] Snake (contour actif)

1. ## [image] Snake (contour actif)

Voici une implémentation Java de l'algorithme "Snake" (contour actif).

L'algorithme Snake permet de tracer le contour d'une zone irrégulière en déformant progressivement une courbe de départ. Pour plus d'informations, je vous conseille l'article de khayyam90.

La classe Snake (attributs+constructeur)
 Code java : Sélectionner tout - Visualiser dans une fenêtre à part
```1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
public class Snake {

// Points of the snake
private List<Point> snake;

// Length of the snake (euclidean distance)
private double snakelength=0;

// size of the image (and of the 2 arrays below)
private int width=0,height=0;

private int[][] flow;

// 3x3 neighborhood used to compute energies
private double[][] e_uniformity = new double[3][3];
private double[][] e_curvature  = new double[3][3];
private double[][] e_flow       = new double[3][3];
private double[][] e_inertia    = new double[3][3];

// auto add/remove points to the snake
// according to distance between points

// maximum number of iterations (if no convergence)
private static int MAXITERATION = 1000;

// coefficients for the 4 energy functions
public double alpha=1.1, beta=1.2, gamma=1.5, delta=3.0;

// alpha = coefficient for uniformity (high => force equals distance between points)
// beta  = coefficient for curvature  (high => force smooth curvature)
// gamma  = coefficient for flow      (high => force gradient attraction)
// delta  = coefficient for intertia  (high => get stuck to gradient)

/**
* Constructor
*
* @param width,height size of the image and of the 2 following arrays
* @param flow gradient flow (modulus)
* @param points inital points of the snake
*/
public Snake(int width, int height, int[][] gradient, int[][] flow, Point... points) {
this.snake = new ArrayList<Point>(Arrays.asList(points));
this.flow = flow;
this.width = width;
this.height = height;
}

// add here the other methods.

}```

Les méthodes de l'algorithme "snake"
 Code java : Sélectionner tout - Visualiser dans une fenêtre à part
```123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
/**
* main loop
*
* @return the final snake
*/
public List<Point> loop() {
int loop=0;

while(step() && loop<MAXITERATION) {
// auto adapt the number of points in the snake
}
loop++;
}

// rebuild using spline interpolation

return this.snake;
}

/**
* update the position of each point of the snake
*
* @return true if the snake has changed, otherwise false.
*/
private boolean step() {
boolean changed=false;
Point p = new Point(0,0);

// compute length of original snake (used by method: f_uniformity)
this.snakelength = getsnakelength();

// compute the new snake
List<Point> newsnake = new ArrayList<Point>(snake.size());

// for each point of the previous snake
for(int i=0;i<snake.size();i++) {
Point prev = snake.get((i+snake.size()-1)%snake.size());
Point cur  = snake.get(i);
Point next = snake.get((i+1)%snake.size());

// compute all energies
for(int dy=-1;dy<=1;dy++) {
for(int dx=-1;dx<=1;dx++) {
p.setLocation(cur.x+dx, cur.y+dy);
e_uniformity[1+dx][1+dy] = f_uniformity(prev,next,p);
e_curvature[1+dx][1+dy]  = f_curvature(prev,p,next);
e_flow[1+dx][1+dy]       = f_gflow(cur,p);
e_inertia[1+dx][1+dy]    = f_inertia(cur,p);
}
}

// normalize energies
normalize(e_uniformity);
normalize(e_curvature);
normalize(e_flow);
normalize(e_inertia);

// find the point with the minimum sum of energies
double emin = Double.MAX_VALUE, e=0;
int x=0,y=0;
for(int dy=-1;dy<=1;dy++) {
for(int dx=-1;dx<=1;dx++) {
e = 0;
e+= alpha * e_uniformity[1+dx][1+dy]; // internal energy
e+= beta  * e_curvature[1+dx][1+dy];  // internal energy
e+= gamma * e_flow[1+dx][1+dy];       // external energy
e+= delta * e_inertia[1+dx][1+dy];    // external energy

if (e<emin) { emin=e; x=cur.x+dx; y=cur.y+dy; }
}
}

// boundary check
if (x<1) x=1;
if (x>=(this.width-1)) x=this.width-2;
if (y<1) y=1;
if (y>=(this.height-1)) y=this.height-2;

// compute the returned value
if (x!=cur.x || y!=cur.y) changed=true;

// create the point in the new snake
}

// new snake becomes current
this.snake=newsnake;

return changed;
}

// normalize energy matrix
private void normalize(double[][] array3x3) {
double sum=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
sum+=Math.abs(array3x3[i][j]);

if (sum==0) return;

for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
array3x3[i][j]/=sum;
}

private double getsnakelength() {
// total length of snake
double length=0;
for(int i=0;i<snake.size();i++) {
Point cur   = snake.get(i);
Point next  = snake.get((i+1)%snake.size());
length+=distance2D(cur, next);
}
return length;
}

private double distance2D(Point A, Point B) {
int ux = A.x-B.x;
int uy = A.y-B.y;
double un = ux*ux+uy*uy;
return Math.sqrt(un);
}```

Les méthodes des fonctions d'energie:
 Code java : Sélectionner tout - Visualiser dans une fenêtre à part
```1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
private double f_uniformity(Point prev, Point next, Point p) {

// length of previous segment
double un = distance2D(prev, p);

// mesure of uniformity
double avg = snakelength/snake.size();
double dun = Math.abs(un-avg);

// elasticity energy
return dun*dun;
}

private double f_curvature(Point prev, Point p, Point next) {
int ux = p.x-prev.x;
int uy = p.y-prev.y;
double un = Math.sqrt(ux*ux+uy*uy);

int vx = p.x-next.x;
int vy = p.y-next.y;
double vn = Math.sqrt(vx*vx+vy*vy);

if (un==0 || vn==0) return 0;

double cx = (vx+ux)/(un*vn);
double cy = (vy+uy)/(un*vn);

// curvature energy
double cn = cx*cx+cy*cy;
return cn;
}

private double f_gflow(Point cur, Point p) {
int dcur = this.flow[cur.x][cur.y];
int dp   = this.flow[p.x][p.y];
double d = dp-dcur;
return d;
}

private double f_inertia(Point cur, Point p) {
double d = distance2D(cur, p);
double e = g*d;
return e;
}```

 Code java : Sélectionner tout - Visualiser dans une fenêtre à part
```12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
// rebuild the snake using cubic spline interpolation
private void rebuild(int space) {

// precompute length(i) = length of the snake from start to point #i
double[] clength = new double[snake.size()+1];
clength[0]=0;
for(int i=0;i<snake.size();i++) {
Point cur   = snake.get(i);
Point next  = snake.get((i+1)%snake.size());
clength[i+1]=clength[i]+distance2D(cur, next);
}

// compute number of points in the new snake
double total = clength[snake.size()];
int nmb = (int)(0.5+total/space);

// build a new snake
List<Point> newsnake = new ArrayList<Point>(snake.size());
for(int i=0,j=0;j<nmb;j++) {
// current length in the new snake
double dist = (j*total)/nmb;

// find corresponding interval of points in the original snake
while(! (clength[i]<=dist && dist<clength[i+1])) i++;

// get points (P-1,P,P+1,P+2) in the original snake
Point prev  = snake.get((i+snake.size()-1)%snake.size());
Point cur   = snake.get(i);
Point next  = snake.get((i+1)%snake.size());
Point next2  = snake.get((i+2)%snake.size());

// do cubic spline interpolation
double t =  (dist-clength[i])/(clength[i+1]-clength[i]);
double t2 = t*t, t3=t2*t;
double c0 =  1*t3;
double c1 = -3*t3 +3*t2 +3*t + 1;
double c2 =  3*t3 -6*t2 + 4;
double c3 = -1*t3 +3*t2 -3*t + 1;
double x = prev.x*c3 + cur.x*c2 + next.x* c1 + next2.x*c0;
double y = prev.y*c3 + cur.y*c2 + next.y* c1 + next2.y*c0;
Point newpoint = new Point( (int)(0.5+x/6), (int)(0.5+y/6) );

// add computed point to the new snake
}
this.snake = newsnake;
}

private void removeOverlappingPoints(int minlen) {
// for each point of the snake
for(int i=0;i<snake.size();i++) {
Point cur = snake.get(i);

// check the other points (right half)
for(int di=1+snake.size()/2;di>0;di--) {
Point end  = snake.get((i+di)%snake.size());
double dist = distance2D(cur,end);

// if the two points are to close...
if ( dist>minlen ) continue;

// ... cut the "loop" part og the snake
for(int k=0;k<di;k++) snake.remove( (i+1) %snake.size() );
break;
}
}
}

// for each point of the snake
for(int i=0;i<snake.size();i++) {
Point prev  = snake.get((i+snake.size()-1)%snake.size());
Point cur   = snake.get(i);
Point next  = snake.get((i+1)%snake.size());
Point next2  = snake.get((i+2)%snake.size());

// if the next point is to far then add a new point
if ( distance2D(cur,next)>maxlen ) {

// precomputed Uniform cubic B-spline for t=0.5
double c0=0.125/6.0, c1=2.875/6.0, c2=2.875/6.0, c3=0.125/6.0;
double x = prev.x*c3 + cur.x*c2 + next.x* c1 + next2.x*c0;
double y = prev.y*c3 + cur.y*c2 + next.y* c1 + next2.y*c0;
Point newpoint = new Point( (int)(0.5+x), (int)(0.5+y) );

snake.add( i+1 , newpoint ); i--;
}
}
}```

Utilisation:

Le constructeur de la classe Snake a besoin des paramètres suivants:

• int width,height: la taille de l'image (et des deux tableaux suivants)
• int[][] gradient: un tableau contenant la norme du gradient pour chaque pixel [x][y]
• int[][] flow: un tableau contenant la norme du vecteur de flux pour chaque pixel [x][y].
En pratique, on peut utiliser la carte des distances jusqu'au pic de gradient le plus proche
• Point... points: la liste des points constituant le snake initial

La méthode publique "loop()" fait évoluer le snake jusqu'a convergence et retourne la liste des points du snake final.

2. L'implémentation sous forme d'un JAR executable: snake.jar

3. Bonjour,
Tout d'abord merci PseudoCode pour cette contribution,c'est exactement ce que je cherchais...
J'essais d'implémenter cette algorithme en utilisant C++ Builder.
Mais je ne suis pas arrivé à comprendre la procédure f_gflow
 Code java : Sélectionner tout - Visualiser dans une fenêtre à part
```1234567private double f_gflow(Point cur, Point p) {
int dcur = this.flow[cur.x][cur.y];
int dp   = this.flow[p.x][p.y];
double d = dp-dcur;
return d;
}```
Que représente flow, est ce la carte des distances de l'image du gradient binarisée ?

-Concernant la fonction gradient(i,j),je l'ai implémenter de cette façon :
Tq:

&

Est ce la bonne méthode de calcul du gradient?!

4. Envoyé par b_reda31
Bonjour,
Tout d'abord merci PseudoCode pour cette contribution,c'est exactement ce que je cherchais...
Heureux que cela serve à quelqu'un. J'en profite pour citer ici l'article de khayyam90 qui sert de support à cette contribution.

Que représente flow, est ce la carte des distances de l'image du gradient binarisée ?
oui, c'est exactement cela.

-Concernant la fonction gradient(i,j),je l'ai implémenter de cette façon :
Tq:

&

Est ce la bonne méthode de calcul du gradient?!
C'est une définition qui en vaut une autre. Personnellement j'utilise plutôt un noyau SOBEL ou MDIF, mais c'est une question d'habitude.

5. Envoyé par pseudocode
Heureux que cela serve à quelqu'un. J'en profite pour citer ici l'article de khayyam90 qui sert de support à cette contribution.
J'ai lu dans cet article (II-4 Paragraphe 3)que le nombre de points dans le snake peut augmenter selon la taille du snake !!

à quel moment et comment ajoute t on un point?

PS:Je ne sais pas si je suis au bon endroit pour poser cette question,si ce n'est pas le cas je m'en excuse...

6. Envoyé par b_reda31
J'ai lu dans cet article (II-4 Paragraphe 3)que le nombre de points dans le snake peut augmenter selon la taille du snake !!

à quel moment et comment ajoute t on un point?
Dans la boucle principale "loop()", si le mode auto-adaptation est activé (AUTOADAPT=true) alors on vérifie la forme du snake toutes les N itérations (N = AUTOADAPT_LOOP).

L'auto-adaptation est composée de 2 phases:
- Si 2 points sont trop près: suppression de l'un des 2 ( méthode removeOverlappingPoints() )
- Si 2 points sont trop éloignés: ajouts de points entre les 2 ( méthode addMissingPoints() )

7. Je m'y connais pas dutout en langage Java donc merci pour ces explications .
Envoyé par pseudocode
Dans la boucle principale "loop()", si le mode auto-adaptation est activé (AUTOADAPT=true) alors on vérifie la forme du snake toutyes les N itérations (N = AUTOADAPT_LOOP).
Donc si je comprend bien,Si AUTOADAPT=False le nombre de points du snake restera fixe?
Dans le cas contraire (AUTOADAPT=True),à chaque N itérations de l'algorithme je dois calculer pour chaque point du snake,la distance entre ce dernier et ses voisins (Successeur et prédécesseur),cette distance etant inférieure ou supérieure à un certain seuil (distance minimale,distance maximale respectivement),Je dois ajouter ou supprimer un point...
Est ce bien ça la façon de procéder?!

L'auto-adaptation est composée de 2 phases:
- Si 2 points sont trop près: suppression de l'un des 2 ( méthode removeOverlappingPoints() )
- Si 2 points sont trop éloignés: ajouts de points entre les 2 ( méthode addMissingPoints() )
Encore une question :
Concernant les valeurs de N(AUTODAPAT_LOOP),Distance minimale et maximale entre deux points.
Comment choisir ces valeur?

Merci encore.

Cordialement.

8. Envoyé par b_reda31
Donc si je comprend bien,Si AUTOADAPT=False le nombre de points du snake restera fixe?
oui.

Dans le cas contraire (AUTOADAPT=True),à chaque N itérations de l'algorithme je dois calculer pour chaque point du snake,la distance entre ce dernier et ses voisins (Successeur et prédécesseur),cette distance etant inférieure ou supérieure à un certain seuil (distance minimale,distance maximale respectivement),Je dois ajouter ou supprimer un point...
Est ce bien ça la façon de procéder?!
je ne sais pas si c'est "LA" façon de procéder, mais c'est celle que j'ai choisie.

Encore une question :
Concernant les valeurs de N(AUTODAPAT_LOOP),Distance minimale et maximale entre deux points.
Comment choisir ces valeur?
Généralement je choisis une valeur de Distancemin et je calcule "DistanceMax = 2 * DistanceMin", et "AUTODAPAT_LOOP = (Distance min + Distance max)/2".

C'est totalement empirique mais ca me convient.

9. Merci PseudoCode

10. Décidement dés qu'un problème est résolu survient un autre!
L'image Gradient+Flow que je réalise est differente de celle de l'application!

Voici comment je la calcule:

-A partir de "gradientbinnaire.bmp" je calcule la carte des distances et j'obtient "gradientflow.bmp".

Et voici comment je calcule la carte des distances(a partir de "gradientbinnaire.bmp"):

-Je mets les coordonnées de tous les points ayant 255 comme valeur dans un vecteur de point Contour[i].x,Contour[i].y.
càd (Contour[i].x,Contour[i].y) sont les coordonnées du iéme point ayant la valeur 255 dans "gradientbinnaire.bmp"(Est ce clair?!)

-Pour chaque point de l'image calculer la distance entre ce point et TOUS les point du vecteur Contour
-La nouvelle valeur du point dans l'image sera la distance minimale,si cette valeur est supérieure à 255 elle prend 255.

Est ce bien la méthode de calcul de la carte des distances?

Rq:
-Le traitement est relativement long!!
-La distance entre deux point je la calcule de cette maniere : dist(A,B)=SQRT((Ax-Bx)²+(Ay-By)²)

Voici les images que j'obtient,comme je l'ai déja dis elle sont differentes de celles de la contribution,Auriez vous une explication?

11. Envoyé par b_reda31
-Pour chaque point de l'image calculer la distance entre ce point et TOUS les point du vecteur Contour
-La nouvelle valeur du point dans l'image sera la distance minimale,si cette valeur est supérieure à 255 elle prend 255.

Est ce bien la méthode de calcul de la carte des distances?
Non, on ne doit pas limiter la valeur de la distance à "255". La valeur de la distance varie entre 0 et l'infinie, quoique dans le cas d'une image la plus grande distance possible est la diagonale = sqrt(largeur²+hauteur²) .

-Le traitement est relativement long!!
Il y a une méthode plus rapide: Carte des Distances (Chamfer)

-La distance entre deux point je la calcule de cette maniere : dist(A,B)=SQRT((Ax-Bx)²+(Ay-By)²)
Voici les images que j'obtient,comme je l'ai déja dis elle sont differentes de celles de la contribution,Auriez vous une explication?
C'est juste que je n'utilise pas les meme couleurs.
 Code : Sélectionner tout - Visualiser dans une fenêtre à part
```123456if (gradient>seuil) {
} else {
rgb[0]=Math.max(0,255-flow); rgb[1]=0; rgb[2]=0;
}```

12. Envoyé par pseudocode
Non, on ne doit pas limiter la valeur de la distance à "255". La valeur de la distance varie entre 0 et l'infinie, quoique dans le cas d'une image la plus grande distance possible est la diagonale = sqrt(largeur²+hauteur²) .
Cela concerne l'image dans laquelle l'algorithme se déroulera(Gradient+Flow)?

Si la distance est supérieure à 255,la valeur du pixel depassera l'intervalle de couleur?!

ça provoque des résultat pas trés jolis jolis chez moi!

13. Envoyé par b_reda31
Cela concerne l'image dans laquelle l'algorithme se déroulera(Gradient+Flow)?
oui.

Si la distance est supérieure à 255,la valeur du pixel depassera l'intervalle de couleur?!
ça provoque des résultat pas trés jolis jolis chez moi!
Dans mon post précédent, tu remarqueras que la valeur du pixel pour le flow (en rouge) est limitée a l'intervalle 0-255:

 Code : Sélectionner tout - Visualiser dans une fenêtre à part
`r=Math.max(0,255-flow); g=0; b=0;`
flow = 0 --> r = Math.max(0,255-0) = 255
flow = +oo --> r = Math.max(0,255-infinity) = Math.max(0, -infinity) = 0

14. Merci pour la rapidité de tes réponses .
Je vais essayer de relire le code ligne par ligne.

15. Si le fond était blanc au lieu de noir, ça marcherait plus trop ? (j'ai pas tout suivi)

Je voyais l'algo comme un truc du genre :

On génère une carte d'élévation du terrain (donc noir à 0 mètre et blanc à 100m par exemple), on entoure le truc d'une grande corde avec des roues, et les roues avance vers le milieu (si une pente est trop élevé (on peut voir ça en utilisant le gradient pour la direction du terrain et en regardant la pente à cet endroit), alors la roue s'arrête))

A moins que tu ne considère que la valeur absolu de la pente, dans ce cas, il n'y a pas de soucis.

16. Envoyé par millie
On génère une carte d'élévation du terrain (donc noir à 0 mètre et blanc à 100m par exemple)
On génère une carte d'élévation du GRADIENT du terrain.

17. Bonjour,
J'ai terminé l'implémentation de cet algorithme en utilisant les même méthodes d'énergie de cette contribution.
J'ai essayé de faire quelque essais en jouant sur les différents parametres pour tester chaque énergie mais les résultats ne sont pas très encourageants
J'ai fais quelques captures de mon application en ajoutant les différentes valeurs des parametres ainsi que le nombre d'itérations:

Vous remarquerez que lorsque Beta est non nul,le snake se déplace bien vers les endroits de fort gradient mais il y a une force qui attire le coté droit du snake vers la droite!!

J'ai pourtant implémenter la méthode CURVATURE de la même manière qu'il a été donnée dans la contribution !

D'un autre coté en mettant Beta à 0 afin de tester la méthode UNIFORMITY,Le résultat est carrément chaotique!!

Je vais continuer à vérifier mon code,si quelqu'un aurait une idée sur ce phénomène qu'il m'en fasse part svp...

18. hum... tu es sûr que ton "gradient vector flow" est bon ?

19. Envoyé par pseudocode

hum... tu es sûr que ton "gradient vector flow" est bon ?

Apparement Oui puisque ça converge bien vers les point de fort gradient?!

Je pense que le problème est dans l'énergie Interne.
Je vais quand même revérifier tous ça.

20. Je ne sais pas si c'est le manque de sommeil ou autre chose,mais là tout commence à se chambouler dans ma tête

Je vais essayer de résumer un peu la chose: (y a quelque chose qui cloche mais je ne sais pas ou)

Nous avons l'image originale qui est à droite dans l'application JAR de cette contribution.

Afin de déterminer l'énergie externe du snake il faut calculer la matrice FLOW qui est la la carte de distance normalisée du gradient binarisé de l'image originale....ouf
Donc il faut tout d'abord calculer le gradient de l'image originale (Sobel par ex),ensuite binariser le gradient.

à partir de gradient binaire il est donc possible de construire la carte de distance en utilisant un masque de chamfer selon cette contribution.
A ce stade nous avons une carte de distance normalisée FLOW,qui est appelée dans cette méthode :
 Code : Sélectionner tout - Visualiser dans une fenêtre à part
```12345678private double f_gflow(Point cur, Point p) {
int dcur = this.flow[cur.x][cur.y];
int dp   = this.flow[p.x][p.y];
double d = dp-dcur;
return d;
}```
J'ai utilisé le masque chamfer3 (new int[] {1,0,3},new int[] {1,1,4}) donc pour normaliser j'ai diviser tous les elements de la matrice FLOW par 3.Et voici FLOW que j'ai obtenu après normalisation:

Elle parait noir mais elle est juste trop sombre.

Et si je ne normalise pas FLOW voici ce que j'obtiens :

Envoyé par Pseudocode :

Cela n'a rien à voir avec les résultats que j'ai obtenu.
Aurais je fais une erreur quelque part?

FLOW et GRADIENT+FLOW est ce la même chose?

Merci d'avance pour vos réponses.

 Actualités COURS ALGO FAQ ALGO LIVRES ALGO SOURCES ALGO