Publicité

# 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 :
```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;

// gradient value (modulus)

// gradient flow (modulus)
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
private static int AUTOADAPT_LOOP=10;
private static int AUTOADAPT_MINLEN=8;
private static int AUTOADAPT_MAXLEN=16;

// 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 :
```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 :
```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 g = this.gradient[cur.x][cur.y];
double e = g*d;
return e;
}```

Les méthodes du mécanisme d'auto-adaptation:
Code java :
```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;
}
}
}

private void addMissingPoints(int maxlen) {
// 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
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;
}
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:

-Je calcule le gradient de l'image originale ("gradient.bmp").
-Ensuite je binarise "gradient.bmp" en utilisant un seuil=100,j'obtient "gradientbinnaire.bmp".
-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 :
```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)?

Mais dans GradientFlow à afficher:
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.

Mais dans GradientFlow à afficher:
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 :
`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. ## GVF

Bonjour tout le monde!

Dans le but d'améliorer mon snake, je voudrais utilisais le Gradient Vector Flow. Cependant, je ne trouve pas la méthode pour calculer ce flow. Quelqu'un pourrait-il m'orienter vers une méthode ?

Aparement c'est obligatoirement une méthode itérative qui doit minimiser une équation mais je ne comprend pas très bien.

Merci d'avance !!!

PS: J'essaye d'utiliser un maximun OpenCV

16. Envoyé par batoub
Dans le but d'améliorer mon snake, je voudrais utilisais le Gradient Vector Flow. Cependant, je ne trouve pas la méthode pour calculer ce flow. Quelqu'un pourrait-il m'orienter vers une méthode ?

Aparement c'est obligatoirement une méthode itérative qui doit minimiser une équation mais je ne comprend pas très bien.
Une manière plus "visuelle" de voir le GVF, c'est d'imaginer une diffusion sur la carte d'élévation du gradient. Les "pics" de gradient vont s'étaler dans les régions uniformes et on obtient à la fin une carte d'élévation avec seulement des montées ou des descentes (= sans plateaux). Le GVF d'un point (x,y) est alors le gradient du pixel (x,y) dans cette carte d'élévation.

La méthode itérative vient de cette notion de diffusion.

Personnellement, je préfère approximer cette cette carte d'élévation en utilisant une variante de l'algo de la carte des distances (un peu comme c'est fait dans ma contribution, mais en utilisant un gradient non "binaire"):

17. Bonjour tout le monde,

Tout d'abord, merci pour ta réponse Pseudocode

Je me répond a moi-même au cas ou des autres personnes serait intérréssé. L'algo a été implanté en matlab et en C ici : http://iacl.ece.jhu.edu/projects/gvf/

J'ai utilisé leur source pour coder mon propre GVF, ca fonctionne super bien. (reste a bien comprendre cette résolution qd meme )

Bonne journée a vous tous !!

EDIT : Je viens de regarder ta contribution sur la carte des distance Pseudocode, ca a l'air super intérréssant ! Je vais essayer d'implanter ca et tester cette méthode par rapport à celle avec les GVF.
Si tu veux en savoir plus sur mon projet, en fait j'utilise la méthode CHEVP pour détecter la route, je la modélise avec une B-spline (la ligne médiane) et après je cherche a faire du suivit de ligne avec un snake + GVF (ou peut étre carte des distance ). Mon gros programme, c'est que j'essaye de réaliser ca avec en temps réel (aie aie ^^).

Pourrais-tu m'en dire un peu plus sur l'algorithme de Chamfer ? Merci !

18. J'ai ajouté quelques explications sur la contribution de la Carte des Distances

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

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

#### Règles de messages

• Vous ne pouvez pas créer de nouvelles discussions
• Vous ne pouvez pas envoyer des réponses
• Vous ne pouvez pas envoyer des pièces jointes
• Vous ne pouvez pas modifier vos messages
•