Culture scientifique et traitement de l'information S3 (M3201) – TP n° 2

Dans ce TP on va implémenter plusieurs algorithmes simples de traitement d'images.

Introduction à la bibliothèque p5.js

On a besoin de pouvoir ouvrir un fichier d'image, accéder à ses pixels, et modifier leurs valeurs. On peut faire cela avec différents langage, mais on va profiter pour travailler sur JavaScript.

Il est possible de manipuler des images avec JavaScript pur (lire ceci), mais on va utiliser la bibliothèque p5.js. Pourquoi ? Parce qu'avec votre créativité et vos connaissances en programmation, vous pouvez faire des merveilles avec : regardez ces exemples (chez vous).

Exercice 1

Dans cet exercice on va voir un script qui charge et affiche une image sauvegardé sur le web ou sur votre ordinateur

Recopiez ce code dans un fichier HTML et ouvrez-le avec votre navigateur web.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>M3201-TP2</title>
  <style> body {padding: 0; margin: 0;} </style>
  <script src="https://cdn.jsdelivr.net/npm/p5@0.10.2/lib/p5.js"></script>
</head>
<body>
  <script>
    let img; // Déclaration de la variable 'img'.

    function preload() {
      img = loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Signac_-_Portrait_de_F%C3%A9lix_F%C3%A9n%C3%A9on.jpg/601px-Signac_-_Portrait_de_F%C3%A9lix_F%C3%A9n%C3%A9on.jpg'); // Charger l'image
    }

    function setup() {
      createCanvas(img.width, img.height);  // Créer le canvas avec les dimensions de l'image
      image(img, 0, 0); // Afficher l'image (sur le coin haut-gauche)
    }
  </script>
</body>
</html>

Affichez une autre image.

Manipuler les couleurs

Le premier script que vous allez faire calcule l'inverse d'une image. C'est la première fois que vous voyez réellement comment les valeurs des pixels sont encodées dans un tableau, et comment on les parcourt.

Exercice 2

Modifiez le code suivant pour inverser les couleurs de l'image. C'est à vous de trouver la bonne formule.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>M3201-TP2</title>
  <style> body {padding: 0; margin: 0;} </style>
  <script src="https://cdn.jsdelivr.net/npm/p5@0.10.2/lib/p5.js"></script>
</head>
<body>
  <script>
    let img; // Déclaration de la variable 'img'.

    function preload() {
      img = loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Signac_-_Portrait_de_F%C3%A9lix_F%C3%A9n%C3%A9on.jpg/601px-Signac_-_Portrait_de_F%C3%A9lix_F%C3%A9n%C3%A9on.jpg'); // Charger l'image
    }

    function setup() {
      let c = createCanvas(img.width, img.height);  // Créer le canvas avec les dimensions de l'image
      image(img, 0, 0); // Afficher l'image (sur le coin haut-gauche)

      loadPixels(); // obligatoire avant la modification des pixels

      const pd = pixelDensity();
      const n = 4 * (pd * width) * (pd * height); // nombre d'éléments dans le tableau
      for (let i = 0; i < n; i += 4) {
        pixels[i]   = 0;  // canal rouge
        pixels[i+1] = 0;  // canal vert
        pixels[i+2] = 0;  // canal bleu
        pixels[i+3] = 0;  // canal alpha
      }
      updatePixels(); // obligatoire après la modification des pixels

      // saveCanvas(c, 'exo2', 'png'); // sauvegarder l'image comme exo2.png
    }
  </script>
</body>
</html>

Vous allez maintenant transformer une image en niveaux de gris. C'est à vous de trouver une bonne formule.

Exercice 3

Modifiez l'exercice 2 pour transformer l'image en niveau de gris.

Il suffit de calculer une valeur qui combine les trois canaux de chaque pixel, et de l'appliquer sur les trois canaux.

Vous êtes maintenant capables d'inventer vos propres opérations de traitement d'images. Mais notez qu'elles sont très limitées pour l'instant, il s'agit de transformations point par point.

Transformations locales

On va faire maintenant une transformation qui depend des pixels voisins. Un des examples le plus simple est un filtre moyen.

Exercice 4

Implementez un filtre (une transformation) locale, qui remplace la couleur de chaque pixels par la moyenne de lui et ses 4 voisins.

Comme ça commence à être penible de manipuler directement le tableau de l'image, je vous offre une paire de fonctions pour manipuler le pixels selon ses coordonnées.

Un autre problème est qu'on doit faire les calculs « en parallèle » : on fait les calculs de chaque pixels et on les affecte dans une autre image. Sinon, il y aurait une propagation des calculs.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>M3201-TP2</title>
  <style> body {padding: 0; margin: 0;} </style>
  <script src="https://cdn.jsdelivr.net/npm/p5@0.10.2/lib/p5.js"></script>
</head>
<body>
  <script>
    let input_img, output_img;

    function preload() {
      input_img = loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Talpa_europaea_MHNT.jpg/640px-Talpa_europaea_MHNT.jpg'); // Charger l'image
    }

    function setup() {
      let c = createCanvas(input_img.width, input_img.height);  // Créer le canvas avec les dimensions de l'image
      output_img = createImage(input_img.width, input_img.height);

      input_img.loadPixels();
      output_img.loadPixels();
      const pd = pixelDensity();
      for (let y = 0; y < pd * height; y++) {
        for (let x = 0; x < pd * width; x++) {
          let couleur = copier_pixel(x, y); // copier les valeurs de RGBA du pixel (x,y)
          console.log('rouge: ', couleur[0]); // afficher la valeur de rouge
          // remplir ici
          ecrire_pixel(x, y, r, g, b);
        }
      }
      output_img.updatePixels(); // obligatoire après la modification des pixels

      image(output_img, 0, 0);

      saveCanvas(c, 'exo5', 'png'); // sauvegarder l'image comme exo4.png
    }

    /* Renvoie un tableau avec les valeurs du pixel (x,y) */
    function copier_pixel(x, y) {
      const i = 4 * (y * width + x);
      return [
        input_img.pixels[i], 
        input_img.pixels[i+1], 
        input_img.pixels[i+2], 
        input_img.pixels[i+3]
      ];
    }

    /* Modifie les valeurs du pixel (x,y) */
    function ecrire_pixel(x, y, r, g, b, a = 255) {
      const i = 4 * (y * width + x);
      if (r < 0) r = 0; else if (r >= 256) r = 255;
      if (g < 0) g = 0; else if (g >= 256) g = 255;
      if (b < 0) b = 0; else if (b >= 256) b = 255;
      output_img.pixels[i]   = r;
      output_img.pixels[i+1] = g;
      output_img.pixels[i+2] = b;
      output_img.pixels[i+3] = a;
    }
  </script>
</body>
</html>

Que se passe-t-il sur les pixels du bord de l'image ? Que peut-on faire ?

Le filtre qu'on vient de créer applique un effet de flou. On va créer un algorithme qui fait le contraire, il améliore la netété.

Exercice 5

Modifiez l'exercice 4 pour appliquer l'operation suivante : pour chaque pixel, on multiplie la valeur de chaque canal par 5 et puis on soustrait la valeur de chacun de ses quatre voisins.

Regardez l'effet de cette opération et pensez pourquoi elle marche comme ça.

Pour aller plus loin

Si vous avez le temps, vous pouvez faire les algorithmes suivants :

  • La posterisation. Modifiez votre image pour qu'elle n'utilise que 128, 64, … 2 couleurs différentes par cannal.
  • L'algorithme de tramage de Floyd-Steinberg, très beau.
  • La rotation d'une image. Étant donné la position d'un pixel et un angle, faites une rotation de l'image. Ça vous permettra de mettre en pratique ce qu'on a vu en TD.
  • Utilisez cette fonction pour créer un effet de Moiré.