Comprendre quelques opérations de manipulation d'images.

Objectif.

Le but de ce tutoriel est de comprendre comment un code manipule une image pixellique.

Introduction.

  1. Une image est un tableau à deux dimensions de points lumineux, c'est à dire de «pixels». Chaque pixel a une valeur entre 0 (pour le "noir") et 255 (pour le "blanc"). Bien lire le document sur l'information en particulier la section "Décrire tous les dessins du monde".

Travail proposé.

  1. Découvrir un code qui modifie chaque pixel de l'image.

    Considérons le code suivant :

    // Fonction appliquée à chaque pixel

    int f(int x) {
        return 255 - x;
    }

    // Boucle sur tous les pixels

    void filtre() {
        for(int j = -getHeight(); j <= getHeight(); j = j + 1) {
            for(int i = -getWidth(); i <= getWidth(); i = i + 1) {
                setPixel(i, j, f(getPixel(i, j)));
            }
        }
    }
    void main() {
        load("http://javascool.gforge.inria.fr/documents/sketchbook/codagePixels/doisneaubuffon.jpg");
    }
    • Le programme principal main charge une belle photo noir et blanc de Doisneau.
    • Les deux premières lignes de la fonction filtre bouclent sur tous les pixels en vertical et en horizontal.
    • Expliquez en quelques mots le rôle des variables i et j.
    • Que fait la construction setPixel(i, j, f(getPixel(i, j))); pour un pixel de coordonnée (i, j) ?.
    • Essayez ce code, puis le code en ajoutant la fonction filtre(); après load() dans le programme principal : que se passe t'il ?
    • Expliquez en quelques mots ce phénomène d'«inversion vidéo».
    • A la place de la construction return 255 - x;, mettre la construction returnx;: que se passe t'il et pourquoi ?
    • Augmenter ou baisser la luminosité d'une image revient à ajouter ou soustraire une valeur constante à la valeur de chaque pixel : essayez en modifiant la fonction f().
    • Que se passe t'il si on choisit return x + 255; et pourquoi ?.
    • Que se passe t'il si on choisit return x - 255; et pourquoi ?.
    • Augmenter ou baisser le contraste d'une image revient à multiplier ou diviser par un gain constant la valeur de chaque pixel : essayez en modifiant la fonction f().
    • Que se passe t'il si on choisit return 0.5 * x; et pourquoi ?.
    • Que se passe t'il si on choisit return 0 * x; et pourquoi ?.
    • Que se passe t'il si on choisit return 100 * (x - 128); et pourquoi ?.
  2. Ajouter deux images : l'effet de calque.

    Cette partie se fera sans rien programmer. Considérons les deux images issues d'une célèbre base de donnée, mise à disposition de la communauté scientifique internationale pour étudier les logiciels de traitement des séquences d'images contenant des mouvements humains.
    • Si l'on considère les deux images suivantes:


      que se passera t'il si on ``additionne´´ ces deux images ? C'est à dire si l'on construit une nouvelle image dont la valeur de chaque pixel est la somme des valeurs des pixels de chaque image ? Expliquer en une phrase.
    • Si chaque image était dessinée sur une feuille de papier calque, à quoi correspondrait la valeur ``transparente´´ par rapport à la couleur de ces images ?
    • Quelle est la différence entre poser une image dessinée sur un calque et ajouter la valeur des pixels comme proposé ici ?
    • Ecrire en langage naturel (avec vos mots à vous, sans vous soucier de le programmer) un algorithme qui simule le fait de poser une image dessinée sur un calque sur une autre.
  3. Soustraire deux images : détecter des changements.

    Cette partie se fera sans rien programmer, comme la précédente.
    • Si l'on considère les deux images suivantes (cliquer sur l'image de gauche pour visualiser la séquence): que s'est il passé entre les deux images ? Si l'on ``soustrait´´ les deux images de manière similaire à ce qui a été fait pour les ``additionner´´ que va t'il se passer, pour les pixels correspondants à des points immobiles entre les deux images ? Et les pixels correspondants à des points en mouvement ? Expliquer en deux/trois phrases.
    • A quoi correspondent les valeurs positives si l'on soustrait les pixels de l'image de gauche à ceux de l'image de droite ? Et les valeurs négatives ?
    • Utiliser l'idée de soustraire deux images pour décrire en quelques lignes un système qui permette de détecter les mouvements d'un objet avec une caméra.
  4. Découvrir un code qui filtre les pixels de l'image.

    Considérons le code suivant :

    // Fonction appliquée à chaque pixel

    int filtrePixel(int i, int j) {
        return (getPixel(i, j) + getPixel(i + 1, j)+ getPixel(i, j + 1)+ getPixel(i - 1, j)+ getPixel(i, j - 1)) / 5;
    }

    // Boucle sur tous les pixels

    void filtre() {
        for(int j = -getHeight() + 1; j <= getHeight() - 1; j = j + 1) {
            for(int i = -getWidth() + 1; i <= getWidth() - 1; i = i + 1) {
                setPixel(i, j, filtrePixel(i, j));
            }
        }
    }
    void main() {
        load("http://javascool.gforge.inria.fr/documents/sketchbook/codagePixels/doisneaubuffon.jpg");
        for(int n = 0; n < 5; n = n + 1) {
            filtre();
        }
    }
    • Le programme principal main charge la photo et appelle la nouvelle fonction filtre cinq fois de suite (pour n = 0 et n = 1).
    • La nouvelle fonction filtre appelle la fonction filtrePixel sur chaque pixel.
    • Expliquez ce que fait cette fonction filtrePixel : entre quelles 5 valeurs calcule t'elle la moyenne (faire un dessin) ?
    • Essayez le code : que se passe t'il visuellement ? Pourquoi ce filtre a t'il un tel effet ?
    • Essayez le code en appliquant non pas le filtre 5 mais 10 fois, puis 20 fois, dans le programme principal : que se passe t'il ?
    • Essayez aussi avec d'autres images de votre choix vues sur internet.
  5. D'autres filtres d'images.

    Considérons le code suivant, avec une autre fonction de filtre :

    // Fonction appliquée à chaque pixel

    int filtrePixel(int i, int j) {
        return 128 + (getPixel(i, j) - getPixel(i + 1, j));
    }

    // Boucle sur tous les pixels

    void filtre() {
        for(int j = -getHeight() + 1; j <= getHeight() - 1; j = j + 1) {
            for(int i = -getWidth() + 1; i <= getWidth() - 1; i = i + 1) {
                setPixel(i, j, filtrePixel(i, j));
            }
        }
    }
    void main() {
        load("http://javascool.gforge.inria.fr/documents/sketchbook/codagePixels/doisneaubuffon.jpg");
        filtre();
    }
    • Que se passe t'il dans ce cas ? Décrivez ce que vous observez.
    • Pourquoi avoir ajouté 128 ? Essayez avec les expressions return (getPixel(i, j) - getPixel(i + 1, j)); puis return -(getPixel(i, j) - getPixel(i + 1, j)); pour bien comprendre ce qui se passe.
    • En faisant la différence entre deux pixels horizontaux getPixel(i, j) - getPixel(i + 1, j) va t'on plutôt détecter les contours verticaux ou horizontaux de l'image ? Expliquer.
    • Essayez la construction return (getPixel(i, j) - getPixel(i + 1, j) - getPixel(i, j + 1)); et commentez le résultat.
    • Deux questions très subtiles, pour les «cracks» :
      • Avez vous remarqué que la boucle de la fonction filtre() n'a pas les mêmes bornes d'itération pour ces filtres que lors du 1er cas où on tranformait juste l'intensité d'un pixel ? Sauriez-vous dire pourqoi ?
      • Que se passerait-il si on utilisait return 128 + (getPixel(i, j) - getPixel(i - 1, j)); : essayez et tentez d'expliquer pourquoi.