2. Utilisation de la DCT pour la compression JPEG

<< Transformée en cosinus discrète (DCT) | Liste des exercices | Qualité et performance de la compression JPEG >>

Le but de cet exercice est d'utiliser la DCT, implémentée dans l'exercice précédent, pour compresser une image selon l'algorithme JPEG.

Traitement par blocs d'une image

Sur une image de grande taille, le coût de calcul de la transformation DCT devient prohibitif. Au lieu d'appliquer la DCT sur toute l'image, on découpe donc celle-ci en blocs de 8x8 pixels et on applique la DCT sur chaque bloc ; cela représente un compromis entre performance de compression et qualité.

Le moyen le plus simple de traiter ainsi l'image bloc par bloc sous ImageJ est d'utiliser la notion de région d'intérêt (ang. « region of interest », ROI en abrégé). Cela consiste à définir une zone de l'image, le plus souvent rectangulaire, sur laquelle un traitement sera appliqué (le reste de l'image n'étant pas pris en compte). Si aucune région d'intérêt n'est définie pour une image, tous ses pixels sont pris en compte dans le traitement.

Sous ImageJ, la méthode setRoi permet de définir une région d'intérêt sur une image. La méthode getRoi retourne le Rectangle délimitant cette région d'intérêt (si celle-ci n'est pas rectangulaire, la méthode getMask retourne le masque des pixels qui en font partie).

Voici une méthode illustrant l'utilisation de cette notion, une région d'intérêt ayant été préalablement définie sur l'image paramètre (grâce à la méthode setRoi) :


// ---------------------------------------------------------------------------------
/**
* Arrondit les valeurs de l'image paramètre à l'entier le plus proche
* @param f Image d'entrée et de sortie (FloatProcessor)
*/
public static void round(FloatProcessor f) {

    Rectangle roi = f.getRoi();	// Région d'intérêt rectangulaire (classe java.awt.Rectangle)
    int M = roi.width;	// Largeur de la région d'intérêt
    int N = roi.height;	// Hauteur de la région d'intérêt

    // Parcours de la région d'intérêt
    for (int v = 0; v < N; v++) {
        for (int u = 0; u < M; u++) {
            // Arrondi de chaque valeur
            f.putPixelValue(roi.x+u, roi.y+v, Math.round(f.getPixelValue(roi.x+u, roi.y+v)));
        }
    }
}
  1. Modifier la méthode forwardDCT (dans DCT2D.java) pour n'appliquer la DCT que sur la région d'intérêt (supposée définie au préalable).
  2. En y définissant la constante
final static int BLOCK_SIZE = 8;

compléter le plugin pour faire tour à tour de chaque bloc 8x8 la région d'intérêt et lui appliquer la DCT.

  1. Tester le code précédent sur l'image 8 bits ci-dessous (« Lena »). Interpréter le résultat (image de la DCT) et commenter les coefficients DCT obtenus pour 2 ou 3 blocs caractéristiques de l'image.

Quantification

Afin de ne retenir qu'une partie des coefficients DCT, la matrice de quantification JPEG pour la composante de luminance est :

// Matrice de quantification pour la luminance (colonne par colonne)
public final static int[][] QY = {
    {16, 12, 14, 14,  18,  24,  49,  72},
    {11, 12, 13, 17,  22,  35,  64,  92},
    {10, 14, 16, 22,  37,  55,  78,  95},
    {16, 19, 24, 29,  56,  64,  87,  98},
    {24, 26, 40, 51,  68,  81, 103, 112},
    {40, 58, 57, 87, 109, 104, 121, 100},
    {51, 60, 69, 80, 103, 113, 120, 103},
    {61, 55, 56, 62,  77,  92, 101,  99}
};

ImageJ permet de réaliser la division (pixel à pixel) des valeurs d'une image par celles d'une autre, ce qui va simplifier l'implémentation de l'étape de quantification. Il suffit pour cela de stocker la matrice de quantification dans une image (8 bits suffiront ici) :

// déclaration
private static ByteProcessor bpQuantification = new ByteProcessor(BLOCK_SIZE, BLOCK_SIZE);
...
bpQuantification.setIntArray(QY);   // initialisation

puis d'utiliser la méthode copyBits de la classe ImageProcessor pour réaliser la division (de nombreuses autres opérations sont possibles). Le résultat doit être arrondi dans une phase finale.

  1. Implémenter l'étape de quantification et valider cette étape grâce à l'exemple de l'article wikipedia (pour arrondir une valeur réelle à l'entier inférieur, utiliser la méthode Math.round).

Décompression

L'étape de codage entropique ne sera pas réalisée ici faute de temps mais, pour visualiser l'image résultat, il est nécessaire d'implémenter le processus de décompression. Celui-ci consiste simplement en l'inverse de ce qui a été fait précédemment :

  • multiplier les coefficients DCT quantifiés par les éléments de la matrice de quantification ;
  • appliquer la DCT inverse ;
  • ajouter 128 pour visualiser l'image décompressée.
  1. Dans DCT2D.java, implémenter la méthode
public static void inverseDCT(FloatProcessor f)

qui réalise la DCT inverse, toujours en utilisant la propriété de séparabilité.

  1. Dans le plugin, à la suite du processus de compression, implémenter le processus de décompression bloc par bloc puis représenter le résultat. Tester ces étapes de compression-décompression sur l'image de l'article wikipedia et commenter le résultat en le comparant à l'image originale.