Traitement d’image¶
Introduction¶
Au cours de ce TP, vous allez réaliser des opérations élementaires sur des images en niveaux de gris, comme par exemple appliquer un filtre pour réduire le bruit ou encore cherche à déterminer les contours des objets présents dans l’image. Les images à traiter vous sont fournies ; elles portent l’extension .pgm. Le flux de traitement sera toujours réalisé dans le même ordre :
Charger l’image dont le nom de fichier est passé en argument sur la ligne de commande. Les valeurs des pixels seront alors contenues dans un tableau 1D de bytes, à raison d’un pixel par élément du tableau.
Créer un tableau 2D imgIn et y transférer les valeurs des pixels de l’image d’entrée.
Créer un second tableau 2D imgOut destiné à recevoir les pixels modifiés (image de sortie).
Réaliser les opérations sur l’image.
Enregister l’image dans un fichier de sortie différent de celui contenant l’image d’entrée.
Pour effectuer les opérations de lecture/enregistrement des fichiers images, vous utiliserez les classes Pixmap et BytePixmap dont un exemple d’emploi est donné ci-dessous.
Utilisation de la classe BytePixmap¶
Exemple
import javax.imageio.IIOException;
import java.io.IOException;
public class DemoPixmap {
public static void main(String[] args) {
BytePixmap imgIn;
BytePixmap imgOut;
try {
imgIn = new BytePixmap(args[0]); // lecture du fichier
} catch (IOException e) {
imgIn = null;
System.exit(0);
}
System.out.println("image lue : "+ imgIn.height+"x"+imgIn.width);
imgOut = new BytePixmap(imgIn.width, imgIn.height);
for ( int i=0; i< imgOut.size; i++) // pour tous les pixels :
imgOut.data[i] = (byte)(255 - imgIn.data[i]);
imgOut.write("neg_"+args[0]); // écriture de l'image en négatif
}
}
Note
Dans cet exemple, l’emploi d’un second tableau pour l’image de sortie n’est pas indispensable.
Compilation - exécution
javac BytePixmap.java
javac Pixmap.java
javac DemoPixmap.java
java DemoPixmap airplane.pgm
Résultat de l’exemple
entrée |
sortie |
|---|---|
|
|
TRAVAIL DEMANDɶ
Mise en place des structures¶
À faire
Télécharger le
Squelette de codede la classe DemoPixmap. Penser à le renommer en DemoPixmap.java.Écrire le traitement équivalent à celui de l’exemple permettant d’obtenir le négatif de l’image d’entrée.
Vérifier l’image de sortie, enregistrée dans le dossier de travail.
- download
Réaliser des tracés dans l’image¶
À faire
Dans la classe DemoPixmap, écrire une méthode traceCadre() qui prend en paramètre le tableau représentant l’image d’entrée, y trace un cadre noir de e pixels d’épaisseur sur ses bords extérieurs, puis retourne l’image modifée.
Appeler cette méthode dans le main() et vérifier le résultat.
Écrire une méthode traceDiagos() qui trace les diagonales de l’image en noir.
Appeler cette méthode dans le main() et vérifier le résultat.
Modifier la méthode traceDiagos() pour permettre de tracer des diagonales d’épaisseur e.
Réaliser un filtre moyenneur¶
Le filtre moyenneur est une opération à fenêtre glissante, c’est à dire qu’elle doit être appliquée à chaque pixel de l’image et qu’elle utilise non seulement le pixel courant, mais aussi un voisinage de celui-ci, par exemple une fenêtre carrée de 5x5 pixels centrée autour de ce pixel courant. Un grand nombre d’opérations de traitement d’image sont à fenêtre glissante.
Le filtre moyenneur est un cas particulier des filtres passe-bas, qui ont pour effet d’atténuer les composantes à hautes fréquences, comme les bruits des capteurs photographique, mais aussi les bords des objets se trouvant dans la scène, ce qui est un handicap.
Le filtre moyenneur de taille \(k \times k\) est aussi un cas particulier d’opération de convolution où tous les coefficients du masque de convolution seraient égaux à \(\frac{1}{k²}\).
Le principe de l’opération de convolution est ilustré par la figure ci-dessous. Chaque pixel \(I_{0,0}\) de l’image d’entrée est remplacé, dans l’image de sortie par un pixel dont la valeur est la somme des valeurs des pixels voisins, multipliées chacune par le coefficient qui lui est associé dans le masque de convolution.
À faire
Écrire une méthode averageFilter2D() qui prend en paramètre l’image à traiter ainsi que la taille du filtre moyenneur à appliquer, puis réalise le filtrage et retourne enfin l’image filtrée.
Réaliser la mesure du temps d’exécution de l’opération de filtrage pour différentes tailles de filtre (3x3, 5x5, 7x7 par exemple). On pourra employer pour cela la méthode System.currentTimeMillis() qui retourne l’heure actuelle en millisecondes.
Commenter les résultats de mesure. Comment évolue le temps de traitement avec la taille de la fenêtre.
Réaliser un opérateur de dérivation¶
Très employé, en particulier pour la détection de bords, l’opérateur de dérivation, en mathématiques discrètes s’apparente à une différence. Il rentre ainsi dans la grande famille des convolutions.
Pour réaliser une dérivation selon l’axe horizontal, on peut par exemple employer un masque de convolution à une seule dimension de coéfficients [-1 0 +1] ou bien un masque 2D comme celui, connu sous le nom de matrice de Sobel :
À faire
Écrire une méthode sobel2D_H() qui prend en paramètre l’image à traiter, puis réalise la dérivation horizontale et retourne enfin l’image dérivée. Utiliser
l'image i25.pgmpour les tests.S’inspirer de la question précédente pour écrire une méthode sobel2D_V() qui prend en paramètre l’image à traiter, puis réalise la dérivation verticale et retourne enfin l’image dérivée. Avant de coder, prendre le temps d’écrire la matrice correspondante sur feuille.
Écrire une méthode sobelFilter() qui prend en paramètre l’image à traiter, puis réalise les dérivations verticale et horizontale (images dH et dV) puis génére enfin une image de sortie où chaque pixel de coordonnées (i,j) prend la valeur x_{i,j} telle que
\[x_{i,j} = \sqrt{dV_{i,j}^2+dH_{i,j}^2}\]Ce traitement réalise-t-il une détection de bords satisfaisante ? Sauriez-vous dire pourquoi et comment l’améliorer ?