TD2 : détection de contours par passages par zéro du laplacien

Le but du TD est de realiser un programme qui prend en entree :

et qui sorte dans un fichier ou sur la sortie standard un fichier texte contenant les chaines de contours sous la forme:

[nb de chaines de contours]
[nb de maillons de la chaine 1]
[x_chaine1_maillon1] [y_chaine1_maillon1]
...
[x_chaine1_maillonn] [y_chaine1_maillonn]
[nb de maillons de la chaine 2]
...

Ce fichier de contours peut etre affiche, superpose a l'image, par une commande du type :

affgros -ima image.pgm -pix1 image.contours

Vous pourrez reprendre le squelette du programme d'exemple du TD1

Le programme contient plusieurs etapes:

  1. calcul du laplacien et de la norme du gradient de l'image
  2. calcul des transitions entre zones positives et negatives du laplacien
  3. chainage et seuillage par hysteresis

Calcul du laplacien

Utilisez la fonctions de la bibliotheque Rpti pour calculer :

Sauvegardez chacune de cez images respectivement dans les fichiers "lapl" et "grad", affichez-les avec affgros pour en tester le contenu

Images de transition du gradient

Sur l'image suivante, observez les transitions entre les zones positives et negatives du laplacien.

Vous observez qu'elles sont de deux types: transitions horizontales et verticales. Elles sont localisees a la frontiere entre deux pixels. Nous allons calculer une image pour chaque type de transition.

Quelle est la taille de l'image des transitions horizontales, c.-a-d. entre pixel (x,y) et (x+1,y) ? des transitions verticales, c.a-d. entre pixel (x,y) et (x,y+1) ?

Creez une image pour chaque type de transition, de type char, qui vaut 0 quand il n'y a pas de changement de signe, et 1 quand il y en a un. Sauvegardez et visualisez ces deux images.

Chainage et seuillage par hysteresis

Dans cette derinere etape, il s'agit d'effectuer le chainage des transitions, en effectuant un seuillage par hysteresis sur la norme du gradient.

Pour effectuer ce chainage, il suffit de parcourir chaque image de transitions, et des qu'on trouve une transition pour laquelle la norme du gradient est superieure au seuil haut, de la suivre dans ces deux images pour toutes les transitions dont la norme du gradient est superieurs au seuil bas. La norme du gradient associee a une transition pourra par exemple la moyenne de la norme des deux pixels voisins d'une transition.

Lorsqu'on rencontre une transition, il faut parcourir la chaine dans les deux sens a partir de ce point, et fusionner les deux tableaux de points ainsi obtenus. Le tableau final doit permettre de parcourir la chaine en gardant la zone positive du laplacien sur la droite

Visualisez le resultat obtenu en faisant varier les seuils

Ameliorations possibles

Indices

Environnement, images

Editez le fichier ~/.zshenv pour ajouter les lignes :

export ROBOTVIS=/home/divers/devernay/robotvis
PATH=$ROBOTVIS/bin:$PATH

Pour relire l'environnement :

. ~/.zshenv

Des images se trouvent dans /home/divers/devernay/herve

Calcul du laplacien

#include <Rpti.h>
#include <Rimage.h>

...

IMAGE d2x, d2y, lapl;
int dimx, dimy;
float *d2xdata, *d2ydata, *lapldata;

d2x = ptiGaussianRecDerivative2X(imin, sigma, IMAGE_FLOAT);
d2y = ptiGaussianRecDerivative2Y(imin, sigma, IMAGE_FLOAT);
/* on alloue lapl de memes caracteristiques que d2x */
lapl = imageMalloc(IMAGE_HEADER(d2x));
dimx = IMAGE_DIMX(lapl);
dimy = IMAGE_DIMY(lapl);
d2xdata= (float*)IMAGE_DATA(d2x);
d2ydata= (float*)IMAGE_DATA(d2y);
lapldata= (float*)IMAGE_DATA(lapl);

/* parcours de l'image ligne par ligne */
for (y=0; y<dimy; y++) {
  for (x=0; x<dimx; x++) {
    lapldata[x+y*dimx] = d2xdata[x+y*dimx]
                               + d2ydata[x+y*dimx];
  }
}
/* on peut liberer l'espace utilise par d2x et d2y */
imageFree(d2x);
imageFree(d2y);
/* si on veut enregistrer lapl... */
imageSave("lapl", lapl, IMAGE_INRIMAGE_FORMAT);

Calcul de la norme du gradient

Utiliser de la meme meme maniere ptiGaussianRecGradient :

IMAGE dx, dy, gradnorm;
float  *dxdata, *dydata, *gradnormdata;
dx = imageMalloc(IMAGE_HEADER(lapl));
dy = imageMalloc(IMAGE_HEADER(lapl));
gradnorm =imageMalloc(IMAGE_HEADER(lapl));
ptiGaussianRecGradient(imin, sigma, IMAGE_FLOAT, &dx, &dy);
dxdata = (float*)IMAGE_DATA(dx);
dydata = (float*)IMAGE_DATA(dy);
gradnormdata = (float*)IMAGE_DATA(gradnorm);

etc...

Allocation des images de transition

On alloue des images de taille dimx*dimy, sachant que la derniere ligne de tx ne sert pas, de meme que la derniere colonne de ty.

IMAGE tx, ty;
unsigned chqr *txdata, *tydata;
IMAGE_HEADER_P header;

IMAGE_HEADER_DIMX(header)  = dimx;
IMAGE_HEADER_DIMY(header) = dimy;
IMAGE_HEADER_TYPE(header) = IMAGE_CHAR;
tx = imageMalloc(header);
ty = imageMalloc(header);
if ((tx == NULL) || (ty == NULL)) {
  fprintf(stderr,"alloc_free: pb with imageMalloc CHAR\n");
}
/* parcourir l'image de laplacien, remplir tx et ty avec des 0 et des 1 */
txdata = (unsigned char *)IMAGE_DATA(tx);
tydata = (unsigned char *)IMAGE_DATA(ty);
...
/* transition de (x,y) a (x+1,y) */
txdata[x+y*dimx] =  ...
/* transition de (x,y) a (x,y+1) */
tydata[x+y*dimx] =  ...
...

Structure de donnees pour une chaine de contour

typedef struct {
  float x;
  float y;
} point_t;

typedef struct {
  int nbpts;
  point_t *pts;
} contour_t;

typedef struct {
  int nbcont;
  contour_t *cont;
} lescontours_t;

lescontours_t lescontours = { 0, NULL};
...
contour_t cont = { 0, NULL };
...
/* ajouter un point de contour a cont */
cont.nbpts++;
/* on realloue la taille necessaire pour le tableau */
cont.pts = (point_t*) realloc(cont.pts, cont.nbpts*sizeof(point_t));
cont.pts[nbpts-1].x = 123.5;
cont.pts[nbpts-1].y = 72;
...

/* afficher le contour */
for (i=0;i<cont.nbpts; i++) {
  printf("%g %g\n", cont.pts[i].x, cont.pts[i].y);
}
...
/* ajouter un contour a lescontours */
lescontours.nbcont++;
/* on realloue la taille necessaire pour le tableau */
lescontours.cont = (contour_t*) realloc(lescontours.cont, lescontours.nbcont*sizeof(contour_t));
lescontours.cont.[nbcont-1].nbpts = cont.nbpts;
lescontours.cont.[nbcont-1].pts = cont.pts;
...

Frederic Devernay