La boîte à Tutoriels de Christopher PECAUD

Traitement d'images avec Java - Utilisation de la JAI
Laisser un commentaire

SOMMAIRE

I Introduction
II Les sujets abordés dans ce tutoriel
III Chargement d'une image dans un JPanel
IV Le concept de Java2D
V Renseignements sur la JAI
VI Création d'un histogramme
VII Quelques fonctions de traitement d'images
VIII Conclusion

I Introduction

Dans ce premier tutoriel réservé au traitement numérique des images, je vais vous montrer comment il est possible avec Java de créer, d'ouvrir et de modifier des images. Nous allons de même voir quelques techniques de traitement d'images. A cet effet, Sun propose deux outils pour développer des applications de traitement d'images :

  • La java.awt.image,
  • et la JAI (Java Advanced Imaging) qui est une librairie plus performante et qui permet de traiter une grande partie des formats d'images existants sur le marché.

A noter que la première est inluse en standard dans le SDK J2SE, alors que la seconde est téléchargeable sur le site de Sun.

II Les sujets abordés dans ce tutoriel

Premièrement nous allons montrer comment charger une image, et l'afficher sur un JPanel. Dans un second temps nous verrons comment réaliser des dessins sur un JPanel. Nous verrons comment nous avons intégré le concept de JAVA2D dans notre projet à ces effets. Dans une troisième partie nous allons décrire une méthode de création d'histogramme en utilisant la JAI. On donnera en même temps quelques renseignements sur cette librairie. Dans une dernière partie nous allons voir quelques techniques de traitements d'images que nous avons implémenté dans notre application.

III Chargement d'une image dans un JPanel

Cette opération intervient après avoir cliquer sur la commande Ouvrir du menu fichier, Ou quand on a cliqué sur l'icône du bouton Ouvrir un fichier de la barre d'outils. La boîte de dialogue d'ouverture de fichier s'exécute alors et l'utilisateur peut sélectionner un fichier. Une fois le nom fichier sélectionné il faut le charger à partir de la source pour pouvoir ensuite l'afficher à l'écran.

L'opération suivante permet de charger le fichier physique dans le même répertoire que celui de l'application :

Image imgFichier;
imgFichier = getToolkit().getImage(FentreInterne.nomfichier);

où nomfichier est le nom du fichier de l'image source à charger.

En fait il suffit de créer une variable de type Image. La classe image impose l'utilisation d'images au format .GIF et .JPG.

La méthode getImage permet de charger une image dans un objet de type Image. Le constructeur de cette classe prend en argument le nom du fichier à charger. Pour plus de sécurité on ajoute ce fichier image dans un objet de type MediaTraker.

MediaTracker tracker = new MediaTracker(this);
tracker.addImage(imgFichier, 0);
try {
  tracker.waitForAll();
}  
catch(InterruptedException e) {
  System.out.println(e);
}

La méthode WaitForAll permet d'attendre que le fichier image soit complètement chargé en mémoire. Une fois que cette opération est correctement terminée, il faut afficher cette image par l'intermédiaire de la méthode paint de la classe JPanel.

public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
int iw, ih;
int l, h;
if (imgBuf == null) {
  if (FenetreInterne.nom_fichier != "Nouveau000.gif") {
    System.out.println("Creation de l'image");
    setSize(imgFichier.getWidth(this), imgFichier.getHeight(this));
	iw = imgFichier.getWidth(null);
    ih = imgFichier.getHeight(null);
    imgBuf = new BufferedImage(iw, ih,                                                               
    bufImgGraph = imgBuf.createGraphics();
    bufImgGraph.drawImage(imgFichier, 0, 0, this);
    g2D.drawImage(imgBuf, 0, 0, null);
    BufferedImage.TYPE_BYTE_GRAY);
    bufImgGraph = imgBuf.createGraphics();
    bufImgGraph.drawImage(imgFichier, 0, 0, this);
    g2D.drawImage(imgBuf, 0, 0, null);
  }
}

IV Le concept de Java2D

C'est dans ce code que l'on voit apparaître le concept de JAVA2D. Ce concept est une extension de ce qu'il est possible de réaliser avec le package java.awt. JAVA2D permet d'utiliser un certain nombre de fonctionnalités :
" Des structures de remplissage telles que des dégradé ou des motifs.
" Des paramètres permettant de personnaliser la largeur et le style de trait.
" Des procédés permettant de lisser les bords des objets dessinés.
" Etc…

La compatibilité de cette API avec les classes du package java.awt permet de convertir un objet Graphics en graphics2D de cette façon là :

Graphics2D g2D = (Graphics2D) g;

Il faut ensuite que le container prennent les mêmes dimensions que l'image à afficher, cette opération est réalisé par l'intermédiaire de la méthode setSize de la classe JPanel.

L'opération suivante va permettre de créer un une image " bufferisée " graphique. C'est par l'intermédiaire de celle-ci que l'on va pouvoir effectuer des opérations JAVA2D.

Cette création se fait par l'intermédiaire de la classe BufferedImage. Son constructeur prend en argument les dimensions de l'image, ainsi que le type de table de couleur que l'on souhaite utiliser. Dans notre cas nous utiliserons la table des couleurs en niveaux de gris.

imgBuf = new BufferedImage(iw, ih, BufferedImage.TYPE_BYTE_GRAY);

En utilisant l'image bufferisée on va pouvoir créer un contexte de périphérique spécifique à JAVA2D :

bufImgGraph = imgBuf.createGraphics();
bufImgGraph.drawImage(imgFichier, 0, 0, this);
g2D.drawImage(imgBuf, 0, 0, null); 

C'est par l'intermédiaire de celle-ci que l'on va pouvoir effectuer des opérations JAVA2D. En effet comme on peut le constater c'est par lui que l'on passe pour créer une copie de l'image en mémoire. Ensuite il est possible de dessiner l'image en appelant la méthode drawImage en donnant comme paramètre l'image Bufferisée.

V Renseignements sur la JAI

Tout d'abord nous allons donner quelques renseignements sur cette librairie de traitement d'images avancée. On peut dire que cette librairie apporte des améliorations notables par rapport à celle que nous venons de voir. En effet elle permet de lire n'importe quel type de fichiers images.

VI Création d'un histogramme

Nous allons voir maintenant la méthode utilisée pour la création d'un histogramme. La JAI offre un mode de chargement de fichier un peu différent de celui que l'on vient de voir précédemment. En effet l'objet image que l'on utilise n'est plus de type Image mais de type PlanarImage :


PlanarImage source ;	  

Pour charger l'image en mémoire il suffit d'appeler la méthode create de la classe de base JAI. Cette méthode prend en argument le paramètre " fileload " pour dire que l'on souhaite charger une image, ainsi que le nom du fichier physique :

source = JAI.create("fileload", FenetreInterne.nom_fichier);

La JAI possède une classe Histogram permettant de créer des objet de type histogramme :

Histogram histogram = new Histogram(bins, low, high);


On remarquera que cette méthode prend en argument :

  • Le nombre de valeurs possible c'est-à-dire 256.
  • La valeur minimale (=0).
  • La valeur maximale (=255).

Pour mémoriser toutes les informations concernant l'image on utilise un objet de type spécial qui est ParameterBlock. Dans cet objet on va pouvoir stocker toutes les informations concernant l'image et son histogramme comme nous pouvons le voir avec le code suivant :

ParameterBlock pb = new ParameterBlock();
pb.addSource(image);
pb.add(histogram);
pb.add(null);
pb.add(1);
pb.add(1);

Une fois cette opération réalisée il reste à créer l'histogramme réellement. Donc il faut calculer pour chaque niveau de gris représenté dans l'image le nombre de pixels associé. Pour pouvoir ainsi remplir la tableau qui contiendra les différentes valeurs.

Tout d'abord il faut utiliser la commande create de la classe JAI, en voici la syntaxe :

RenderedOp op = JAI.create("histogram", pb, null);

On crée en fait un opérateur de type RenderedOp qui va permettre la détermination des différentes valeurs que l'on va inclure dans l'histogramme. On s'aperçoit que la commande create prend en argument la constante chaînée " histogram ", ainsi que l'objet de type ParameterBlock que l'on vient de définir.


histogram = (Histogram) op.getProperty("histogram");

La fonction getProperty est ensuite utilisée pour extraire les propriétés de l'histogramme, elle prend en argument aussi la constante chaîne de caractères " histogram ". Les propriétés de l'histogramme sont obtenues par l'intermédiaire de l'objet op qu'on a définie.

C'est la méthode getBinSize qui permet d'obtenir pour chaque niveau de gris le nombre de pixels qui lui est associé. La méthode getNumBins permet d'obtenir le nombre de valeurs possible qui sera inclus dans l'histogramme.

Nous avons maintenant le procédé à suivre pour pouvoir créer notre histogramme. Il ne reste plus qu'à afficher ces différentes valeurs sous forme graphique. Cette phase fait intervenir la méthode paint de la classe JPanel. La méthode est sensiblement la même que pour l'affichage d'une image à l'écran. Il faut dire que toute l'opération de création de l'histogramme que l'on vient de voir fait partie d'une méthode que l'on a créé et qui retourne un tableau d'entier local_array.

Maintenant que nous avons la procédure à suivre pour créer un histogramme, nus allons voir quelques méthodes de traitement d'images par l'intermédiaire de la librairie java.awt.image.

VII fonctions de traitement d'images

Nous allons voir maintenant comment il est possible en Java et par l'intermédiaire de la librairie standard image de pouvoir mettre en œuvre facilement quelques fonctions de traitements d'images. Nous allons voir dans un premier temps comment il est possible par seuillage de binariser une image en niveaux de gris.

Dans notre application nous avons voulu réaliser un seuillage avec un seul seuil et un seuillage avec deux seuils. Les seuils sont fixés manuellement par l'intermédiaire de glissière. Lorsque l'on clique sur la commande correspondant à l'un ou l'autre type de seuillage, on fait ouvrir une boîte de dialogue permettant de fixer les seuils. Les variables statiques qui mémorisent les seuils, vont être utilisées pour l'opération de binarisation proprement dite.

Nous avons écrit deux méthodes nous méthode seuillageOp permettant d'agir sur la LUT. En effet lorsque l'on binarise une image on va effectuer l'opération suivante, dans le cas d'un seul seuil :

Pour i allant de 0 à 255 faire
  Début
  Si i > seuil alors seuillage[i] = 255
  Sinon seuillage[i] = 0
  Fin

Dans le cas de deux seuils l'algorithme est le suivant :

Pour i allant de 0 à 255 faire
  Début
  Si i < seuil1 alors seuillage[i] = 0
  Si i > seuil2 alors seuillage[i] = 255
  Si i >seuil1 et i < seuil2 alors seuillage[i] = i
  Fin

Il faut définir un opérateur de type LookupOp qui va gérer cette LUT en utilisant le constructeur de la classe byteLookupTable. Ce constructeur prend en argument, le tableau seuillage que nous avons définit à l'instant.

biop = new LookupOp(new ByteLookupTable(0, seuillage), null);

Voici les deux méthodes que nous avons défini.

public static void seuillageOp(int seuil1, int seuil2) {
int i;
byte seuillage[] = new byte[256];
for (i = 0; i < 256; i++) {
  if (i > seuil2) {
    seuillage[i] = (byte) 255;
  }
  if ((i < seuil2) && (i > seuil1)) {
    seuillage[i] = (byte) i;
  }
  if (i < seuil1) {
    seuillage[i] = (byte) 0;
  }

}
biop = new LookupOp(new ByteLookupTable(0, seuillage), null);
}


public static void seuillageOp(int seuil) {

byte seuillage[] = new byte[256];
for (int i = 0; i < 256; i++) {
  if (i > seuil) {
    seuillage[i] = (byte) 255;
  }
  else {
    seuillage[i] = (byte) 0;
  }
}	

Pour visualiser le résultat de la binarisation on va réécrire la méthode paint de notre classe qui étend la classe JPanel. On va ainsi créer une image en mémoire bi de l'image originale c'est-à-dire de celle qui a été chargée ou nouvellement créée. On va ensuite obtenir l'image résultat par l'intermédiaire de la méthode filter de l'opérateur biop que nous avons créé précédemment. Cette méthode prend en argument l'image originale mise en mémoire et l'image en mémoire résultat. La phase finale correspond à l'affichage de l'image binarisée résultante.

public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
int iw = PanelImage.imgBuf.getWidth(null);
int ih = PanelImage.imgBuf.getHeight(null);
BufferedImage bi = new BufferedImage(iw,ih,BufferedImage.TYPE_BYTE_GRAY);
biop.filter(PanelImage.imgBuf, bi);
g2D.drawImage(bi, 0, 0, iw, ih, null);

}

Conclusion

J'avais envie de vous montrer comment il est possible de mettre en œuvre des fonctions de traitements d'images. Cela vous a permis d'entrevoir quelques fonctionnalités des librairies comme la JAI ou le package standard java.awt.image. On a pu voir que la librarie de fonctions JAI était bien plus évolué que la bibliothèque image, fournie en standard avec le jdk. Cependant je ne vous ai donné qu'un mince apperçu des possibilités énormes de cette librairie. A vous d'approfondir vos connaissances dans ce domaine pour pouvoir utiliser toutes les fonctions (comme la restauration, les filtres, etc...). Prochainement vous aurez le droit à quelques illustrations et aux fichiers sources.

Top