• Aucun résultat trouvé

Raffinement adaptatif Identification de zones de prioritéIdentification de zones de prioritéIdentification de zones de priorité

Visualisation progressive de champ ultrasonore

8.1 Visualisation progressive

8.1.4 Raffinement adaptatif Identification de zones de prioritéIdentification de zones de prioritéIdentification de zones de priorité

Étant donné un échantillonnage incomplet d’une image de champ composé de n points, nous cherchons à déterminer un ensemble de points d’échantillonnage sup-plémentaires afin d’améliorer la qualité de l’image. La méthode d’interpolation que nous avons choisie utilise des polynômes de degré faible et tend à minimiser les fortes variations.

Les zones présentant les variations les plus fortes sont donc susceptibles de né-cessiter plus de points d’échantillonnage pour être efficacement reconstruites. Ainsi, pour améliorer la précision de la reconstruction, il est utile d’effectuer une détection de ces zones à fortes variations.

Calculer le champ des points voisins des points d’échantillonnage nous permet de déduire les dérivées secondes selon les axes horizontaux et verticaux en chaque point d’échantillonnage. D’après l’hypothèse de continuité du champ et de ses dérivées, les valeurs les plus élevées de dérivées secondes caractérisent les voisinages des zones à forte variation. On peut donc chercher à calculer en priorité les zones avoisinant les points où le champ présente une forte dérivée seconde.

À ce critère de variation vient s’ajouter un critère propre au contexte du contrôle non destructif. En effet, dans l’usage courant des images de champ ultrasonore, les zones de fortes amplitudes caractérisent la focalisation des ondes ultrasonores et donc les zones dont les échos éventuels ont la plus forte amplitude. Priorité est donc également donnée aux points présentant les plus fortes amplitudes.

Pour définir une valeur de priorité à partir des amplitudes et gradients, il nous faut donc connaître les maximums de ces deux grandeurs sur les points échantillon-nés. En notant respectivement(ak)1≤k≤n,(∂2xak)1≤k≤n et(∂2 yak)1≤k≤n l’amplitude, la dérivée seconde selon l’axe x et la dérivée seconde selon l’axe y du champ aux n points de l’échantillonnage, on peut définir des scores de priorité comme suit :

⎧ ⎪ ⎪ ⎨ ⎪ ⎪ ⎩ pampk = |ak| (ai)1≤i≤n pd i f fk = max |∂2xak| (∂2xai)1≤i≤n , |∂2 yak| (∂2 yai)1≤i≤n

Puis un score de priorité global entre 0 et 2 pour la zone avoisinant le point d’échantillonnage k : pk= pamp k + pd i f f k Raffinement d’images

Pour construire une distribution de scores de priorité dans l’image de champ, nous construisons une grille à partir des points d’échantillonnage. Nous choisissons un échantillonnage de départ de 5× 5 = 25 points uniformément répartis. Chaque

échantillon de l’image finale est identifié par ses coordonnées entières x et y. 〈Point2DEntier〉 struct Point2DEntier { int x, y; float amplitude; }; 〈Image〉 struct Image {

int xMinimal, xMaximal, yMinimal, yMaximal; };

L’image est décomposée en quatre nœuds, chacun composé de 9 points d’échan-tillonnage que l’on identifie par les points cardinaux Est (NE), Nord (N), Nord-Ouest (NO), Nord-Ouest (O), Sud-Nord-Ouest (SO), Sud (S), Sud-Est (SE), Est (E) et Centre (C).

〈Noeud〉

struct Noeud {

Point2DEntier pointNE, pointN, pointNO, Point2DEntier

183

pointE, pointC, pointO, pointSE, pointS, pointSE;

Noeud(int xMinimal, int xMilieu, int xMaximal, int yMinimal, int yMilieu, int yMaximal) { pointNE.x = xMaximal; pointNE.y = xMaximal; pointN.x = xMilieu; pointN.y = xMaximal; pointNO.x = xMinimal; pointNO.y = xMaximal; pointO.x = xMinimal; pointO.y = xMilieu; pointSO.x = xMinimal; pointSO.y = xMinimal; pointS.x = xMilieu; pointS.y = xMinimal; pointSE.x = xMaximal; pointSE.y = xMinimal; pointE.x = xMaximal; pointSE.y = xMilieu; pointC.x = xMilieu; pointC.y = xMilieu; }

〈estDivisible 186 〈score 185

float amplitudeMax; float dérivéeSecondeMax;

static float amplitudeMaxGlobale; static float dérivéeSecondeMaxGlobale; static

};

Pour raffiner l’image, nous procédons à la subdivision d’un ou plusieurs nœuds de l’image. En adoptant la structure arborescente schématisée en figure8.1pour la

représentation des nœuds de l’image, le problème de l’ordre de raffinement de l’arbre se rapporte à une exploration d’arbre.

NW W SW N C S NE E SE Nœud racine NE, NW, SW, SE Sous-nœud 1 NE, N, C, E Sous-nœud 2 N, NW, W, C Sous-nœud 3 C, W, SW, S Sous-nœud 4 E, C, S, SE

Figure 8.1 – Représentation en quad-tree d’une image de champ

De la même façon que ce qui a été fait pour l’algorithme de lancer de pinceaux, nous utilisons un algorithme d’exploration de l’arbre se servant des critères de prio-rité. Un score est donné à chaque nœud de l’image et les nœuds aux scores les plus élevés sont subdivisés prioritairement.

Le critère de score calculé prend en compte le maximum d’amplitude sur le nœud et les dérivées secondes discrètes calculées à l’échelle du nœud. Ainsi, on a :

⎧ ⎪ ⎨ ⎪ ⎩ 2xak= aE,k− 2aC,k+ aW,k 4Δx2 2 yak= aE,k− 2aC,k+ aW,k 4Δy2

avecΔx et Δy les pas d’échantillonnage respectivement horizontal et vertical du

nœud.

Enfin, pour empêcher une exploration en profondeur dans les zones à forte am-plitude menant à une densité très hétérogène de l’échantillonnage, nous choisissons d’appliquer un coefficient lié à la taille du nœud :

pk= (pamp

k + pd i f f

k ) ∗%ΔxΔy

Cela revient à diviser par deux le score d’un nœud pour chaque niveau de subdivision déjà parcouru avant de l’atteindre. De cette façon, les nœuds les plus profonds dans l’arbre sont pénalisés par rapport aux autres, de sorte à équilibrer l’exploration.

En pratique, de la même manière que pour le calcul de champ ultrasonore, nous utilisons une file de priorité pour l’exploration de l’arbre d’échantillonnage de l’image de champ :

〈Algorithme d’exploration de l’image〉

void explorationImage(Image image, CalculChamp calculateur) {

CalculChamp

priority_queue<Noeud, ComparaisonNoeuds> file;

Noeud 183

ComparaisonNoeuds

186 Noeud noeudRacine(image.xMinimal,

Noeud 183 (image.xMinimal + image.xMaximal) / 2, image.xMaximal, image.yMinimal, (image.yMinimal + image.yMaximal) / 2, image.yMaximal); calculer(calculateur, noeudRacine); calculerNoeud 185 file.push(noeudRacine); while(!file.empty()) {

Noeud noeudCourant = file.pop(); if(noeudCourant.estDivisible()) {

estDivisible

186 array<Noeud, 4> noeudsFils = noeudCourant.subdiviser();

subdiviser

for(int i = 0; i < 4; ++i) { calculateur.calculer(noeudsFils[i]); file.push(noeudsFils[i]); } } } }

Dans un premier temps, le nœud racine est initialisé pour couvrir l’ensemble de la surface de l’image de champ. Les coordonnées de ses 9 échantillons permettent une subdivision équitable du nœud en sous-nœuds de surface égale (à condition que le nœud soit de largeur et de hauteur paires). Ses échantillons et son score de priorité sont calculés et il est ajouté à la file de priorité. Les maximums globaux d’amplitude et de dérivée seconde sont mis à jour à chaque calcul de score.

〈calculerScore〉

void calculerScore(Noeud noeud) {

amplitudeMax = max(noeud.pointNE.amplitude, ..., noeud.pointC.amplitude); Maximum d’amplitude des échantillons Noeud::amplitudeMaxGlobale = max(Noeud::amplitudeMaxGlobale, amplitudeMax); dérivéeSecondeMax = max(abs(dérivéeSecondeX(noeud), abs(dérivéeSecondeY(noeud))); Noeud::dérivéeSecondeMaxGlobale = max(Noeud::dérivéeSecondeMaxGlobale, dérivéeSecondeMax); } 〈score〉 float score() {

float facteurTaille = sqrtf((float)((pointE.x - pointW.x) * (pointN.y - pointS.y))); float termeAmplitude = amplitudeMax / amplitudeMaxGlobale; float termeDérivéeSeconde = dérivéeSecondeMax /

dérivéeSecondeMaxGlobale;

return facteurTaille * (termeAmplitude + termeDérivéeSeconde); }

〈calculerNoeud〉

void calculerNoeud(CalculChamp calculateur, Noeud noeud) {

noeud.pointNE.amplitude = calculateur.calculAmplitudeMax(noeud.pointNE.x, noeud.pointNE.y);

... Calcul de tous les échantillons du nœud.

noeud.pointC.amplitude = calculateur.calculAmplitudeMax(noeud.pointNE.x, noeud.pointC.y);

〈calculerScore 185

}

Vient ensuite la boucle principale qui est exécutée tant que la file de priorité contient au moins un élément. À chaque itération, le nœud de plus haut score est retiré de la file de priorité. Avant de lancer le processus de subdivision, un test est effectué pour vérifier que le nœud est suffisamment étendu pour être subdivisé. Le cas contraire signifie que l’exploration de la branche de l’arbre contenant le nœud courant est terminée et donc que la définition finale de l’image est atteinte pour cette zone.

〈estDivisible〉

bool estDivisible() {

bool testHorizontal = pointE.x > pointC.x + 1 && pointC.x > pointW.x + 1; bool testVertical = pointN.x > pointC.x + 1 &&

pointC.x > pointS.x + 1; return testHorizontal && testVertical; }

Dans le cas où le nœud est assez étendu, il est subdivisé équitablement (sauf s’il est de largeur ou de hauteur impaire). Les échantillons des nœuds résultant de cette subdivision sont calculés pour pouvoir extraire les valeurs de score de priorité. 〈subdiviser〉

array<Noeud, 4> subdiviser() {

Noeud noeudNE { pointE.x, (pointE.x + pointC.x) / 2, pointC.x, pointN.y, (pointN.y + pointC.y) / 2, pointC.y }; Noeud noeudNW { pointC.x, (pointC.x + pointW.x) / 2, pointW.x,

pointN.y, (pointN.y + pointC.y) / 2, pointC.y }; Noeud noeudSW { pointC.x, (pointC.x + pointW.x) / 2, pointW.x,

pointC.y, (pointC.y + pointS.y) / 2, pointS.y }; Noeud noeudSE { pointE.x, (pointE.x + pointC.x) / 2, pointC.x,

pointC.y, (pointC.y + pointS.y) / 2, pointS.y }; array<Noeud, 4> noeudsFils = { noeudNE, noeudNW, noeudSW, noeudSE }; return noeudsFils;

}

Les noeuds ainsi créés sont ajoutés à la file de priorité. Un opérateur de comparai-son entre nœuds est utilisé pour cela. Celui-ci se base sur les scores pour établir une relation d’ordre entre les nœuds. Le score est calculé à la volée afin de tenir compte de l’évolution des maximums globaux.

〈ComparaisonNoeuds〉

bool ComparaisonNoeuds(Noeud noeudA, Noeud noeudB) { return noeudA.score() < noeudB.score();

score 185

}

À la fin de l’exploration, il est garanti que l’ensemble des points d’échantillonnage a été calculé, l’image est donc complète. Notons que le critère de score ici utilisé est spécifique au cas d’application des images de champ ultrasonore pour le contrôle non destructif et s’appuie sur des propriétés connues de ces images. Puisque la méthode que nous présentons ici est adaptable à d’autres applications, le calcul du score peut être différent pour favoriser d’autres critères.

De cette façon, nous disposons d’un algorithme de raffinement progressif et adap-tatif d’images de champ. Il favorise à la fois les zones de forte amplitude, pour favori-ser un critère métier associé au contrôle non destructif et les zones à fortes variations, pour que l’interpolation polynomiale de degré faible s’approche des valeurs réelles plus rapidement.