I. Introduction
Cet article est destiné aux développeurs Java qui souhaitent écrire des applications permettant de faire du traitement d'images en Java avec l'API Java 2D.
Pour comprendre les notions présentées dans cet article, une connaissance préalable de l'API SWING est un minimum requis.
I-A. Image numérique
Les images que vous affichez sur votre écran, que vous créez ou modifiéez avec votre logiciel de dessin comme Photoshop ou Gimp, sont en fait stockées sur votre ordinateur sous forme binaire (suite de 0 et de 1). Elles sont appelées de ce fait des images numériques.
Ces images sont divisées on deux types : les images matricielles et les images vectorielles.
Ces images sont divisées on deux types : les images matricielles et les images vectorielles.
I-A-1. Image vectorielle
D'après Wikipédia, une image vectorielle (ou image en mode trait), en informatique, est une image numérique composée d'objets géométriques individuels (segments de droite, polygones, arcs de cercle, etc.) définis chacun par divers attributs de forme, de position, de couleur, etc.
Elle se différencie de cette manière des images matricielles (ou " bitmap "), dans lesquelles on travaille sur des pixels.
Ces images peuvent être créés avec des logiciels spécifiques comme Adope Flash ou Adope Illustrator.
Il existe de nombreux formats de fichiers vectoriels, parmi lesquels, on peut citer : .SVG, .DXF ou .DWG.
Elle se différencie de cette manière des images matricielles (ou " bitmap "), dans lesquelles on travaille sur des pixels.
Ces images peuvent être créés avec des logiciels spécifiques comme Adope Flash ou Adope Illustrator.
Il existe de nombreux formats de fichiers vectoriels, parmi lesquels, on peut citer : .SVG, .DXF ou .DWG.
I-A-2. Image matricielle
Une image matricielle ou bitmap est représentée, comme son nom l'indique, par une matrice de points (un tableau à deux dimensions).Ces points sont appelés des pixels.
Le nom pixel provient du terme anglais PICture ELement qui signifie le plus petit élément de l'image qui peut être manipulé par le matériel et les logiciels d'imageries ou d'impression.
En fait, vos fichiers d'extension .bmp, .jpg, .gif, .png sont des fichiers d'images bitmap.
Lorsque vous zoomez une image bitmap comme le montre la figure ci-dessous, vous pouvez identifier ces points coloriés à cause de l'effet aliasing (ou crénelage) qui apparaitront lors d'une faible résolution.

Chaque pixel (abrégé px) admet une couleur et est approximativement rectangulaire.
Les couleurs de l'image peuvent être codées suivant une palette, comme pour le format GIF, ou peuvent aussi être codées dans l'espace de couleur RVB (ou RGB en anglais pour Red, Green, Blue) où chaque couleur est représentée par trois couleurs fondamentales Rouge (Red) Vert (Green) et Bleu (Blue).
Vous pouvez consulter cette page pour obtenir le codage sur trois octets de vos couleurs préférées.
Les axes de l'image sont orientés de la façon suivante :

Chaque pixel est repéré par ses coordonnées x et y.
Avant d'enregistrer une image, il faut lui attribuer un format de stockage comme le format .jpg ou .bmp.
Chaque format utilise un algorithme de compression qui peut être avec perte ou sans perte.
Parmi ces algorithmes, on peut citer LZW, RLE, etc.
Pour plus d'information sur les images numériques, vous pouvez consulter ce lien.
Le nom pixel provient du terme anglais PICture ELement qui signifie le plus petit élément de l'image qui peut être manipulé par le matériel et les logiciels d'imageries ou d'impression.
En fait, vos fichiers d'extension .bmp, .jpg, .gif, .png sont des fichiers d'images bitmap.
Lorsque vous zoomez une image bitmap comme le montre la figure ci-dessous, vous pouvez identifier ces points coloriés à cause de l'effet aliasing (ou crénelage) qui apparaitront lors d'une faible résolution.

Chaque pixel (abrégé px) admet une couleur et est approximativement rectangulaire.
Les couleurs de l'image peuvent être codées suivant une palette, comme pour le format GIF, ou peuvent aussi être codées dans l'espace de couleur RVB (ou RGB en anglais pour Red, Green, Blue) où chaque couleur est représentée par trois couleurs fondamentales Rouge (Red) Vert (Green) et Bleu (Blue).
Vous pouvez consulter cette page pour obtenir le codage sur trois octets de vos couleurs préférées.
Les axes de l'image sont orientés de la façon suivante :

Chaque pixel est repéré par ses coordonnées x et y.
Avant d'enregistrer une image, il faut lui attribuer un format de stockage comme le format .jpg ou .bmp.
Chaque format utilise un algorithme de compression qui peut être avec perte ou sans perte.
Parmi ces algorithmes, on peut citer LZW, RLE, etc.
Pour plus d'information sur les images numériques, vous pouvez consulter ce lien.
I-B. Traitement d'images
Le traitement d'images est l'ensemble des opérations qui entrainent la modification des images numériques.
Ces modifications portent généralement sur la couleur des pixels.
Plusieurs techniques sont utilisées pour manipuler ces données,
le but étant l'amélioration de ces données afin d'obtenir une plus grande lisibilité de l'image.
On trouve plusieurs logiciels gratuits ou payants spécifiques pour le traitement des images matricielles ou vectorielles. On peut citer Adope Photoshop pour le traitement des images bitmap.
Ces modifications portent généralement sur la couleur des pixels.
Plusieurs techniques sont utilisées pour manipuler ces données,
le but étant l'amélioration de ces données afin d'obtenir une plus grande lisibilité de l'image.
On trouve plusieurs logiciels gratuits ou payants spécifiques pour le traitement des images matricielles ou vectorielles. On peut citer Adope Photoshop pour le traitement des images bitmap.
II. Traitement d'images en Java
II-A. Introduction
Java est un langage de programmation multiplateforme connu par la richesse de ses API et par sa portabilité.
À la base, Java été conçu pour écrire des pages Web dynamiques intégrant des applets, chez le client, qui communiquent avec des Servlets sur les serveurs.
Cette orientation vers le Web a changé grâce à la multitude des classes Java (3780 classes dans Java SE 6). On peut trouver dès lors des applications de bureau ou d'autres destinées pour les PDA écrites en Java.
Java est présent aussi dans le domaine de l'infographie. C'est grâce à son API Java 2D qu'on peut écrire des logiciels de traitement et d'analyse d'images performants commeimageJ qui est riche en fonctionnalités et utilisable sur différents systèmes d'exploitation.

À la base, Java été conçu pour écrire des pages Web dynamiques intégrant des applets, chez le client, qui communiquent avec des Servlets sur les serveurs.
Cette orientation vers le Web a changé grâce à la multitude des classes Java (3780 classes dans Java SE 6). On peut trouver dès lors des applications de bureau ou d'autres destinées pour les PDA écrites en Java.
Java est présent aussi dans le domaine de l'infographie. C'est grâce à son API Java 2D qu'on peut écrire des logiciels de traitement et d'analyse d'images performants commeimageJ qui est riche en fonctionnalités et utilisable sur différents systèmes d'exploitation.

II-B. Java2D
II-B-1. Fonctionnalités
Java 2D est une API composée par un ensemble de classes destinées à l'imagerie et à la création de dessin 2D.
Elle permet de :
Elle permet de :
- dessiner des lignes, des rectangles et toute autre forme géométrique ;
- replisser les formes par des couleurs unies ou en dégradés et lui ajouter des textures ;
- ajouter du texte, lui attribuer différentes polices et contrôler son rendu ;
- dessiner des images, éventuellement en effectuant des opérations de filtrage.
II-B-2. Architecture de l'API
Pour travailler avec les images sous Java 2D, il faut connaitre deux classes importantes de l'API :


- java.awt.Image :
c'est la super classe fournie dans la JDK depuis sa version 1.0. C'est une classe abstraite qui permet de représenter les images sous forme d'un rectangle de pixels.
La restriction de cette classe est qu'elle ne permet pas d'accéder aux pixels. Elle est donc inadaptée pour le traitement d'images. - java.awt.image.BufferedImage :
Ajoutée à la JDK depuis sa version 2. Elle hérite de la classe Image et implémente ses interfaces pour permettre d'examiner l'intérieur des images chargées et travailler directement sur ses données. On peut donc à partir d'un objet BufferedImage, récupérer les couleurs des pixels et changer ces couleurs. Comme son nom l'indique, BufferedImage est une image tampon. Cette classe gère l'image dans la mémoire et fournit des méthodes pour l'interprétation des pixels. Si on veut faire du traitement d'images, il faut donc travailler avec des objets BufferedImage. Comme le montre la figure suivante, un objet BufferedImage est composé de deux parties :
- java.awt.image.ColorModel:
cette classe définit la façon d'interpréter les couleurs. Le ColorModel est capable de traduire les valeurs des données provenant du Raster en objetjava.awt.Color. - java.awt.image.Raster :
elle contient les données de l'image et peut les représenter comme un tableau de valeurs de pixels. Aussi, elle maintient les données de l'image dans la mémoire et fournit des méthodes pour accéder à des pixels spécifiques au sein de l'image. Modifier les données d'une image est en créer d'autres instances.
- java.awt.image.ColorModel:
III. Étude de cas
III-A. Présentation de l'application
Cette application est un logiciel écrit en Java permettant d'appliquer certaines opérations sur les images bitmap.
Elle permet de :
Elle permet de :
- lire des fichiers images et les afficher sur l'écran ;
- enregistrer des images après modification sur le disque ;
- appliquer un ensemble de filtres comme :
- rendre l'image en niveau de gris ou binarisation de l'image ,
- assombrir les couleurs des pixels ,
- ajouter un effet de brillance. Appliquer certain filtres linéaires ,
- zoomer / dézoomer l'image.

protected
void
imageBinaire
()
{
BufferedImage imgBinaire =
new
BufferedImage
(monImage.getWidth
(), monImage.getHeight
(), BufferedImage.TYPE_BYTE_BINARY);
Graphics2D surfaceImg =
imgBinaire.createGraphics
();
surfaceImg.drawImage
(monImage, null
, null
);
monImage =
imgBinaire;
//
Appel
à
repaint
pour
activer
l'affichage
du
panneau
et
visualisation
//
de
l'image
sur
le
panneau
après
changement
repaint
();
}
IV-E-5. Convolution mathématique
Théoriquement, la convolution est le fait d'utiliser une matrice d'entiers appelée aussi masque ou noyau, pour multiplier les valeurs de couleur d'un pixel donné de l'image par ce masque.
Le principe de l'algorithme consiste à amplifier la valeur d'un pixel P et de chacun des n pixels qui l'entourent par la valeur correspondante dans le noyau. Le but est d'additionner l'ensemble des résultats pour que le pixel P prenne la valeur du résultat final.
Je ne suis pas un spécialiste en la matière mais vous pouvez consulter cet article pour plus de détails sur le sujet.
Le masque qu'on va utiliser est de taille 3x3.
Il permet de rendre l'image floue comme le montre la figure suivante :

La méthode utilisée pour ce filtre est imageConvolue() :

L'opération de multiplication ou plus précisément la convolution mathématique est réalisée par un objet de la classe ConvolveOp. Si vous voulez appliquer d'autres opérations comme la segmentation, l'estompage ou le gradient, vous devez tout simplement modifier les coefficients de la matrice de convolution définis par la variable
Par exemple pour obtenir le gradient de Sobel, vous pouvez utiliser le masque ci-dessus :
Le principe de l'algorithme consiste à amplifier la valeur d'un pixel P et de chacun des n pixels qui l'entourent par la valeur correspondante dans le noyau. Le but est d'additionner l'ensemble des résultats pour que le pixel P prenne la valeur du résultat final.
Je ne suis pas un spécialiste en la matière mais vous pouvez consulter cet article pour plus de détails sur le sujet.
Le masque qu'on va utiliser est de taille 3x3.
Sélectionnez
float
[ ] masqueFlou =
{
0
.1f
, 0
.1f
, 0
.1f
,
0
.1f
, 0
.2f
, 0
.1f
,
0
.1f
, 0
.1f
, 0
.1f
}
;

La méthode utilisée pour ce filtre est imageConvolue() :
Sélectionnez
protected
void
imageConvolue
()
{
BufferedImage imageFlou =
new
BufferedImage
(monImage.getWidth
(),monImage.getHeight
(), monImage.getType
());
float
[ ] masqueFlou =
{
0
.1f
, 0
.1f
, 0
.1f
,
0
.1f
, 0
.2f
, 0
.1f
,
0
.1f
, 0
.1f
, 0
.1f
}
;
Kernel masque =
new
Kernel
(3
, 3
, masqueFlou);
ConvolveOp opération =
new
ConvolveOp
(masque);
opération.filter
(monImage, imageFlou);
monImage =
imageFlou;
//
Appel
à
repaint
pour
activer
l'affichage
du
panneau
et
visualisation
//
de
l'image
sur
le
panneau
après
changement
repaint
();
}

L'opération de multiplication ou plus précisément la convolution mathématique est réalisée par un objet de la classe ConvolveOp. Si vous voulez appliquer d'autres opérations comme la segmentation, l'estompage ou le gradient, vous devez tout simplement modifier les coefficients de la matrice de convolution définis par la variable
Sélectionnez
float
[ ] masqueFlou
Sélectionnez
float
[ ] masqueGradientX =
{
-
1f
, 0f
, 1f
,
-
2f
, 0f
, 2f
,
-
1f
, 0f
, 1f
}
;
float
[ ] masqueGradientY =
{
1f
, 2f
, 1f
,
0f
, 0f
, 0f
,
-
1f
, -
2f
, -
1f
}
;
IV-F. Enregistrement d'une image
Finalement, notre image source définie par un objet BufferedImage qu'on a obtenu déjà a partir d'un fichier sur le disque, a subi différentes transformations.
On peut donc conclure que les données de l'image source sont modifiées et que son tableau de pixels (trame) stocké dans la mémoire a changé de valeurs. On obtient alors une image affichée sur la surface du panneau différente de l'image initialement chargée.
Il faut donc penser à enregistrer cette nouvelle image localisée dans la mémoire sur le disque.
Une manière de le faire consiste à créer une image à partir de la surface du panneau (objet de PanDessin) et enregistrer cette image dans un format donné. Pour créer cette image, on utilise la méthode getImagePanneau() :
Par la suite, on fait appel à la méthode enregistrerImage() qui permet d'écrire cette image sur le disque en utilisant la méthode statique write() de la classe ImageIO.
Comme pour la lecture des fichiers images, il faut connaitre les formats supportés en écriture par la classe ImageIO.
Pour récupérer ces formats on peut utiliser la méthode getWriterFormatNames() :
Cette méthode retourne uniquement les formats supportés en standard et aucun des plug-ins supplémentaires ne sera mentionné.
Avec ce dernier paragraphe, qui traite l'enregistrement d'image en Java 2D, on arrive à la fin de la présentation de l'application à travers laquelle j'ai essayé d'exploiter les fonctionnalités de l'API Java 2D fournis pour le traitement d'images. D'autres méthodes dans le code de l'application n'ont pas été mentionnées vu qu'il s'agit des mêmes traitements expliqués plus haut avec une élémentaire modification dans le code.
On peut donc conclure que les données de l'image source sont modifiées et que son tableau de pixels (trame) stocké dans la mémoire a changé de valeurs. On obtient alors une image affichée sur la surface du panneau différente de l'image initialement chargée.
Il faut donc penser à enregistrer cette nouvelle image localisée dans la mémoire sur le disque.
Une manière de le faire consiste à créer une image à partir de la surface du panneau (objet de PanDessin) et enregistrer cette image dans un format donné. Pour créer cette image, on utilise la méthode getImagePanneau() :
protected
BufferedImage getImagePanneau
()
{
//
récupérer
une
image
du
panneau
int
width =
this
.getWidth
();
int
height =
this
.getHeight
();
BufferedImage image =
new
BufferedImage
(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g =
image.createGraphics
();
this
.paintAll
(g);
g.dispose
();
return
image;
}
protected
void
enregistrerImage
(File fichierImage)
{
String format =
"
JPG
"
;
BufferedImage image =
getImagePanneau
();
ImageIO.write
(image, format, fichierImage);
}
Pour récupérer ces formats on peut utiliser la méthode getWriterFormatNames() :
Sélectionnez
String[] names =
ImageIO.getWriterFormatNames
();
for
(int
i =
0
; i <
names.length; +
+
i)
{
System.out.println ("
format
supportée
en
ecriture
:
"
+
names[i]);
}
Avec ce dernier paragraphe, qui traite l'enregistrement d'image en Java 2D, on arrive à la fin de la présentation de l'application à travers laquelle j'ai essayé d'exploiter les fonctionnalités de l'API Java 2D fournis pour le traitement d'images. D'autres méthodes dans le code de l'application n'ont pas été mentionnées vu qu'il s'agit des mêmes traitements expliqués plus haut avec une élémentaire modification dans le code.
V. Liens utiles
VI. Conclusions
Dans cet article, j'ai présenté différentes fonctionnalités de l'API Java 2D pour le traitement d'images.
J'ai expliqué le code d'une application fenêtrée qui permet de faire plusieurs opérations sur les images bitmap comme la lecture, l'enregistrement, l'application de filtres et le dimensionnement.
Pour terminer, il faut dire que le traitement d'images en Java n'est pas limité aux services de l'API Java 2D, mais les dépassent pour utiliser l'API Java Advanced Image ou couramment JAI, qui étend les services offerts par Java 2D tout en étant compatible avec celle-ci.
J'ai expliqué le code d'une application fenêtrée qui permet de faire plusieurs opérations sur les images bitmap comme la lecture, l'enregistrement, l'application de filtres et le dimensionnement.
Pour terminer, il faut dire que le traitement d'images en Java n'est pas limité aux services de l'API Java 2D, mais les dépassent pour utiliser l'API Java Advanced Image ou couramment JAI, qui étend les services offerts par Java 2D tout en étant compatible avec celle-ci.
Aucun commentaire:
Enregistrer un commentaire