Publicité

# Discussion: [java] Structure QuadTree [Sources]

Une implémentation de la structure QuadTree en Java. Cette structure permet de partitionner un espace 2D sous la forme d'un arbre. Chaque noeud possède 0 ou 4 enfants, suivant la granularité souhaitée pour une région.

Exemple de partitionnement 2D et le QuadTree correspondant

Code java :
```123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
import java.util.ArrayList;
import java.util.List;

/**
* Cette classe implémente la structure QuadTree
*
* @author Xavier Philippeau
*
*/

// organisation des noeuds enfant:
//  _________
// |    |    |
// | NW | NE |
// |____|____|
// |    |    |
// | SW | SE |
// |____|____|

// types de noeuds
static final int TYPE_ROOT=-1, TYPE_NW=0, TYPE_NE=1, TYPE_SW=2, TYPE_SE=3;

// types des ancetres communs
private static final int[] rightAncestors = new int[] {TYPE_NW,TYPE_SW};
private static final int[] leftAncestors  = new int[] {TYPE_NE,TYPE_SE};
private static final int[] bottomAncestors= new int[] {TYPE_NW,TYPE_NE};
private static final int[] topAncestors   = new int[] {TYPE_SW,TYPE_SE};

// tableau de symetrie pour l'inversion des chemins
private static final int[] horizontalReverter = new int[] {TYPE_NE,TYPE_NW,TYPE_SE,TYPE_SW};
private static final int[] verticalReverter   = new int[] {TYPE_SW,TYPE_SE,TYPE_NW,TYPE_NE};

// types des enfants
private static final int[] leftNodes   = new int[] {TYPE_NW,TYPE_SW};
private static final int[] rightNodes  = new int[] {TYPE_NE,TYPE_SE};
private static final int[] topNodes    = new int[] {TYPE_NW,TYPE_NE};
private static final int[] bottomNodes = new int[] {TYPE_SW,TYPE_SE};
private static final int[] AllNodes    = new int[] {TYPE_NW,TYPE_NE,TYPE_SW,TYPE_SE};

// profondeur maximum de l'arbre
public static int MAXDEPTH=0;

// composants du noeud
public int type;
public int depth;

public Object object; // champ libre (donnée utilisateur)

/** constructeur public (racine de l'arbre) */
this.parent = null;
this.type = TYPE_ROOT;
this.depth = 0;
this.object = o;
}

// constructeur privé (utilisé lors du split)
this.parent = parent;
this.type = type;
this.depth = parent.depth+1;
this.object = o;
if (depth>MAXDEPTH) MAXDEPTH=depth;
}

// retourne le noeud voisin d'un noeud (algorithme générique)
int[] path = new int[MAXDEPTH+1];
int pathlength=0;

// recherche du plus proche ancetre commun
while(true) {
if (ancestor.type==-1) return null; // no common ancestor -> exit
path[pathlength] = ancestor.type; pathlength++;
if (ancestor.type==ancestortype[0]) {ancestor = ancestor.parent; break;}
if (ancestor.type==ancestortype[1]) {ancestor = ancestor.parent; break;}
ancestor = ancestor.parent;
}

// parcours de l'arbre en utilsant le chemin symetrique
for(int i=pathlength-1;i>=0;i--) {
if (cursor.children==null) break;
next = cursor.children[ reverter[ path[i] ] ];
if (next==null) break;
cursor=next;
}
return cursor;
}

// parcours reccursif des enfants. Helper pour la methode childrens()
if (node==null) return;
if (node.children==null) {
return;
}
for(int type:finaltypes){
childrens_atom(results,node.children[type],finaltypes);
}
}

// retourne la liste des feuilles accessibles à partir d'un noeud
childrens_atom(results,node,finaltypes);
return results;
}

// ----------------------------------------------------------------------------------

/** split un noeud, i.e. création des 4 enfants (ordre=NW,NE,SW,SE)*/
public void split(Object... objects) {
if (children!=null) return;

if(objects.length>=1) children[TYPE_NW].object = objects[0];
if(objects.length>=2) children[TYPE_NE].object = objects[1];
if(objects.length>=3) children[TYPE_SW].object = objects[2];
if(objects.length>=4) children[TYPE_SE].object = objects[3];
}

// ----------------------------------------------------------------------------------

/** retourne le noeud représentant la case de droite */
}

/** retourne le noeud représentant la case de gauche */
}

/** retourne le noeud représentant la case du dessus */
}

/** retourne le noeud représentant la case du dessous */
}

// ----------------------------------------------------------------------------------

/** retourne toutes les feuilles de type: gauche */
}

/** retourne toutes les feuilles de type: droite */
}

/** retourne toutes les feuilles de type: haut */
}

/** retourne toutes les feuilles de type: bas */
}

/** retourne toutes les feuilles */
}

// ----------------------------------------------------------------------------------

/** retourne les noeuds représentant les cases voisines à gauche */
return 	sibling.getLeftChildren();
}

/** retourne les noeuds représentant les cases voisines à droite */
return 	sibling.getRightChildren();
}

/** retourne les noeuds représentant les cases voisines au dessus */
return 	sibling.getBottomChildren();
}

/** retourne les noeuds représentant les cases voisines en dessous */
return 	sibling.getTopChildren();
}
}```

Voici un exemple d'utilisation de cette classe qui permet de réaliser le partitionnement montré dans l'image d'exemple:
Code java :
```123456789101112
// Creation du noeud "racine"

// split du noeud "racine" en 4
root.split("1","2","3","4");

// Reference du noeud "1"

// split du noeud "1" en 4
n1.split("1.1","1.2","1.3","1.4");```

Pour afficher les voisins de gauche du noeud "2":
Code java :
```1234567
// Reference du noeud "2"

// Affiche les voisins de gauche du noeud "2" --> affiche: 1.2 1.4
for( QuadTreeNode voisin : n2.getLeftNeighbors() )
System.out.println(voisin.object);```

PRomu@ld : sources

2. Très très bonne initiative Xavier de ta part. Je dois transcrire le code en C++ et voir comment l’intégrer pour Split and Merge, pour décomposer les images en bloque homogène. Il y a du boulot à faire . j'ai jamais fait du java dans ma vie lol

3. Envoyé par highlight
Très très bonne initiative Xavier de ta part. Je dois transcrire le code en C++ et voir comment l’intégrer pour Split and Merge, pour décomposer les images en bloque homogène. Il y a du boulot à faire . j'ai jamais fait du java dans ma vie lol
Il existe surement des implémentations déjà faites de Quadtree en C++. A moins que tu ne doives/préfères coder ta propre implémentation.

L'implémentation que je propose ici est assez compliquée car elle inclut tout un système de navigation (sibling, neighbors, children, ...). Tout ce système n'est pas nécessaire si tu veux segmenter une image. Un bon graphe d'adjacence est tout aussi efficace et plus simple.

4. Oui effectivement j'en ai trouvé pas mal mais c'est pour detecter la collision des objets hors de mon application donc . Franchement j'ai perdu un temps fou à chercher cela. Mon but en fin de compte est de retrouvé une decomposition en régions homogene d'une carte de profondeur (depth map). l'idée c'est de bien determiner des région connexes homogènes.
Je n'ai pas trouvé d implémentation!

5. Envoyé par highlight
Oui effectivement j'en ai trouvé pas mal mais c'est pour detecter la collision des objets hors de mon application donc . Franchement j'ai perdu un temps fou à chercher cela. Mon but en fin de compte est de retrouvé une decomposition en régions homogene d'une carte de profondeur (depth map). l'idée c'est de bien determiner des région connexes homogènes.
Je n'ai pas trouvé d implémentation!
Dans ce cas là, le plus simple c'est d'utiliser le QuadTree uniquement pour la phase de "split". Au final, on construit une nouvelle image où chaque pixel à la valeur moyenne de la région. On fait alors le "merge" avec un algo classique de composantes connexes (cf. les implémentation dans ce forum).

6. Ok, je viens de définir qlqs structures essentielles pour la décomposition en Quadtree. C'est representation tres simple. Mettez en tete, qu'une image est decomposée en 4 régions a la forme suivante: L00, L01,L10 et L11, (comme les coefficients d'une matrices de 2x2).

Code :
```1234567891011typedef struct data{

float * c; // le centre de la feuille (leaf) c=[x,y]

float *corner_leaf00;
float *corner_leaf10;
float *corner_leaf11;
float *corner_leaf01;

}data;```
Maintenant structure de la feuille

Code :
```1234567891011typedef struct leaf {

data *coords;
struct leaf *LP; // le noeud
struct leaf *L00;  // Les feuilles
struct leaf *L10;
struct leaf *L11;
struct leaf *L01;

}leaf;```
Code :
```1234typedef struct quadtree{
leaf *root;
Maintenant l'allcoation de la memoire :

Code :
```123456789101112131415161718192021222324252627leaf * leaf_alloc(float *center, float *corner00, float *corner10, float *corner11, float *corner01)
{
leaf * new_leaf;
if ( (new_leaf = (leaf*)malloc(sizeof(leaf)) ) == NULL )
{
fprintf(stderr,"Error : A problem of allocation of the leaf" );
}

if ( (new_leaf->coords = (data*)malloc(sizeof(data)) ) == NULL )
{
fprintf(stderr," Error: A problem of allocation of the leaf" );
}

new_leaf->coords->c = center ;
new_leaf->coords->corner_leaf00 = corner00 ;
new_leaf->coords->corner_leaf01 = corner01 ;
new_leaf->coords->corner_leaf11 = corner11 ;
new_leaf->coords->corner_leaf10 = corner10 ;

new_leaf->LP  = NULL;
new_leaf->L00 = NULL;
new_leaf->L10 = NULL;
new_leaf->L11 = NULL;
new_leaf->L01 = NULL;
return new_leaf;
}```
Et bien evidemment une fonction de la subidivision de la feuille:

Code :
```12345678910111213141516171819202122232425262728293031void leaf_subdivise(leaf * leaf) {

if (!leaf)
{ // If the leaf is NULL (the null pointer)
fprintf(stderr,"[e] The leaf points to NULL and cannot be subdivided.\n" );
exit(EXIT_FAILURE);

}
else
{

float * center     = leaf->coords->c    ;
float * corner_leaf00 = leaf->coords->corner_leaf00;
float * corner_leaf10 = leaf->coords->corner_leaf10;
float * corner_leaf11 = leaf->coords->corner_leaf11;
float * corner_leaf01 = leaf->coords->corner_leaf01;

// the first child: L00 child ( the upper-left)

float L00_corner00[2] = {corner_leaf00[0] ,corner_leaf00[1]};
float L00_corner10[2] = {corner_leaf00[0] ,center[1]    };
float L00_corner11[2] = {center[0]     ,center[1]    };
float L00_corner01[2] = {center[0]     ,corner_leaf00[2]};

float L00_center[2]   = { (L00_corner00[0] + L00_corner01[0])/2.0f, (L00_corner00[1] + L00_corner10[1])/2.0f};

leaf->L00 = leaf_alloc(L00_center,L00_corner00,L00_corner10,L00_corner11,L00_corner01);
leaf->L00->LP = leaf;

// Et on fait pareil pour les 3 restants, je vais pas les ecrire ici :)```
Je sais pas si cela est correct... C'est vraiment tres simple comme approche d'implémentation des quadtree!!
A vous

7. Envoyé par highlight
Je sais pas si cela est correct... C'est vraiment tres simple comme approche d'implémentation des quadtree!!
A vous
Ca m'a l'air correct.

Faut bien faire attention au moment de la subdivision à créer 4 zones jointives, mais non couvrantes. A force de subdiviser, la zone de départ n'est pas toujours un carré parfait, ni une dimension multiple de 2. Avec ton approche de "centre" de la feuille, ca devrait marcher.

8. Merci Pseudocode pour ta réponse. Je veux juste avoir quelques precisions pour determiner un critere d'homogeneité, on peut par exemple qui est d'ailleurs le plus simple:

Calculer l'intesité moyenne d'un bloque intensity_average, et tester si l'intensité maximale et minimale de ce bloque appartient à l'intervalle:
[intensity_average-seuil,intensity_average+seuil]

Qu'est ce que tu en pense?
Je te remercie infiniment

9. Envoyé par highlight
Calculer l'intesité moyenne d'un bloque intensity_average, et tester si l'intensité maximale et minimale de ce bloque appartient à l'intervalle:
[intensity_average-seuil,intensity_average+seuil]

Qu'est ce que tu en pense?
J'en pense que c'est extrêmement sensible au bruit. Un seul pixel trop clair dans un gros bloc va entrainer une cascade de division jusqu'à isoler ce pixel.

Si ton image est synthétique (synthèse 2D ou 3D), ce critère peut être acceptable à condition de bien choisir le seuil. Si c'est une image réelle ou bruitée, il vaut mieux utiliser la variance.

10. ## Voisins des blocs

Bonjour ,

Merci beaucoup Philippeau pour le code moi je travaille sur le split and merge.
J'ai terminé le split sauf mnt au merge je dois connaitre les voisins de chaque Bloc.
J'ai lu la méthode utilisé dans votre code mais j'ai pas compris comment t'as fais pour connaitre les voisins.
STP peux tu m'expliquer l'algorithme avec un petit algo ou les étapes que t'as fais pour que je puisse mettre le code dans mon propre projet.

Merci d'avance

11. Envoyé par ballou129
J'ai terminé le split sauf mnt au merge je dois connaitre les voisins de chaque Bloc.
J'ai lu la méthode utilisé dans votre code mais j'ai pas compris comment t'as fais pour connaitre les voisins.
Il faut chercher indépendamment chacun des 4 voisins.

Prenons l'exemple du "voisin de droite" d'un noeud X. Il y a deux cas:

Le cas simple: Si X est du type NW ou SW, alors son voisin de droite Y est le noeud NE ou SE dans le même carré.

Le cas compliqué: Si X est du type NE ou SE, alors son voisin de droite Y est dans un autre carré. Cet autre carré est forcément à droite de X. Il nous faut donc un moyen de nous ramener au cas simple...

Et pour cela, on va remonter d'un cran dans la hiérarchie et inspecter le parent de X : si le parent de X est du type NW ou SW, alors le parent de Y est le NE ou SE dans le même carré. Si ce n'est pas le cas on inspecte le parent du parent de X, et ainsi de suite.

Lorsqu'on fini par trouver un parent de X de type NW/SW, on a donc trouvé le parent de Y correspondant. Il nous reste a redescendre la hiérarchie du parent de Y pour trouver Y.

L'astuce consiste a remarquer que la redescente s'effectue de manière symétrique (en miroir) à la remontée. Cela s'explique car le voisin de droite de X est de l' "autre coté" de la ligne verticale qui sépare les parents.

Donc, a chaque fois qu'on a fait une remontée de "NE -> parent", alors il faudra faire un redescente "parent -> NW". Et idem pour "SE -> parent" qui devient "parent -> SW".

``` -------------------------
|  ___N_W___   ___N_E___  |
| |    |    | |    |    | |
| | NW | NE | | NW | NE | |
| |____|____| |____|____| |
| |    |    | |    |    | |
| | SW | SE | | SW | SE | |
| |____|____| |____|____| |
|  _________   _________  |
| |    |    | |    |    | |
| | NW | NE | | NW | NE | |
| |____|____| |____|____| |
| |    |    | |    |    | |
| | SW | SE | | SW | SE | |
| |____|____| |____|____| |
|     S W         S E     |
-------------------------

SE -parent-> NW  <-miroir->  NE -enfant-> SW

```

12. ## compliment

tres bon exemple ,
mais si je veux apliquer cette structure il faut que je sauvgarde un objet dans le noed comme les pixles du quadtrant
si qlq 1 peut nous aider a faire ca

et merci

#### 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
•