cours1 min de lecture

Traiter une image en Python

Modifier les composantes RGB d'un pixel pour obtenir négatif, gris ou filtre — un pixel à la fois.
programme

Introduction

Un filtre Instagram, une retouche dans l'application Photos de votre téléphone, un effet « noir et blanc » dans Snapchat : derrière chacun de ces effets, un programme modifie les couleurs des pixels un par un. Ce n'est pas magique — c'est trois petits calculs sur trois petits nombres, répétés très vite.

Dans ce cours, vous allez écrire vous-même les calculs qui transforment une couleur. On reste à l'échelle d'un seul pixel à la fois : c'est tout ce qu'il faut pour comprendre comment fonctionne une retouche.

Une image, c'est une grille de jauges

Avant d'écrire du code, posons l'image mentale.

Une image numérique est une grille de petites cases — les pixels. Chaque case porte trois jauges réglables : une rouge, une verte, une bleue. Chaque jauge va de 0 (éteinte) à 255 (à fond).Modifier une image, c'est tourner les jauges, case par case.

Une couleur de pixel s'écrit alors comme un trio de nombres. Par exemple (255, 0, 0) signifie « jauge rouge à fond, vert à zéro, bleu à zéro » — c'est du rouge pur. (0, 0, 0) est noir (toutes les jauges éteintes), (255, 255, 255) est blanc (toutes à fond).

Dans tout ce cours, un pixel = trois nombres que l'on appelle r, g, b (pour rouge, vert, bleu). Aucune autre notation à connaître.

Que représente le pixel (0, 255, 0) ?

Transformer un pixel — l'idée générale

Un programme de retouche fait toujours la même chose pour un pixel :

  1. Lire ses trois nombres r, g, b.
  2. Calculer trois nouveaux nombres à partir de r, g, b.
  3. Renvoyer ces trois nouveaux nombres : ce sera la nouvelle couleur du pixel.

Toute la créativité d'une retouche tient dans l'étape 2 — la formule de calcul. Une formule = un effet. Voyons trois exemples au programme.

Exemple 1 — Le négatif

L'effet négatif inverse l'intensité de chaque jauge : ce qui était à fond passe à zéro, ce qui était à zéro passe à fond. La formule est très simple, et c'est la même pour les trois jauges :

nouvelle valeur=255ancienne valeur\text{nouvelle valeur} = 255 - \text{ancienne valeur}

En Python, on écrit cette transformation comme une fonction qui prend les trois nombres r, g, b et renvoie les trois nouveaux nombres :

Renvoyer plusieurs valeurs. Une fonction Python peut renvoyer plusieurs valeurs en les séparant par des virgules dans le return. À l'appel, Python les regroupe en un trio affiché entre parenthèses, par exemple (245, 55, 205). C'est une notation pratique pour un pixel (r, g, b).
⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.
Lecture du code, ligne par ligne :
  • def negatif(r, g, b): — on définit une fonction negatif qui attend trois nombres r, g, b.
  • return 255 - r, 255 - g, 255 - b — la fonction renvoie les trois nouvelles valeurs, dans l'ordre rouge, vert, bleu.
  • print(negatif(10, 200, 50)) — on appelle la fonction avec un pixel rougeâtre. Python répond (245, 55, 205).

Exemple 2 — Les niveaux de gris

Pour obtenir une nuance de gris, les trois jauges doivent être réglées sur la même valeur. Plus la valeur est basse, plus le gris est sombre ; plus elle est haute, plus il est clair.

Quelle valeur commune choisir ? La plus simple est la moyenne des trois composantes du pixel d'origine :

moy=r+g+b3\text{moy} = \frac{r + g + b}{3}

Le pixel transformé devient (moy, moy, moy).

⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.
On utilise // (deux barres obliques) pour la division entière : on veut un nombre entier entre 0 et 255, pas un nombre à virgule. La différence avec / (une seule barre, division classique) :
  • 7 / 3 vaut 2.333… (un nombre à virgule),
  • 7 // 3 vaut 2 (on garde uniquement la partie entière).
Comme une jauge RGB ne peut être qu'un entier de 0 à 255, c'est // qu'il nous faut ici.
La moyenne arithmétique est l'approche la plus simple. Des formules plus fidèles à la perception humaine pondèrent davantage le vert (notre œil y est plus sensible). À ce niveau, la version simple suffit.

Exemple 3 — Garder uniquement le rouge

Un troisième effet classique : filtrer une couche en éteignant deux des trois jauges. Pour ne garder que le rouge, on remet g et b à zéro et on laisse r tel quel.

⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.

Sur une image entière, l'effet ressemble à une « lampe rouge » qui ne laisse passer que cette couleur — un peu comme la lumière rouge d'un laboratoire de photographie argentique.

Par symétrie, on obtient un filtre vert en gardant seulement g (formule (0, g, 0)) et un filtre bleu en gardant seulement b (formule (0, 0, b)).

Vérification de compréhension

Quelle opération transforme une composante en sa version « négatif » ?

Dans un pixel en niveau de gris, les trois composantes RGB sont :

Quel pixel obtient-on en appliquant filtre_rouge à (120, 200, 80) ?

Et sur une image entière ?

Tout ce que vous venez d'écrire travaille sur un seul pixel. Pour appliquer la transformation à une image complète, le principe est toujours le même : le programme parcourt toutes les cases de la grille et applique la fonction à chacune.

Démo enseignante

Le code ci-dessous (à observer, pas à reproduire) montre comment appliquer negatif à toute une image avec la bibliothèque Python PIL/Pillow. Vous n'êtes pas censés savoir l'écrire : retenez seulement que la fonction negatif reste exactement la même, et que le reste sert juste à parcourir l'image et à enregistrer le résultat.

from PIL import Image

def negatif(r, g, b):
    return 255 - r, 255 - g, 255 - b

source = Image.open("photo.jpg")
largeur, hauteur = source.size
resultat = Image.new("RGB", (largeur, hauteur))

for x in range(largeur):
    for y in range(hauteur):
        r, g, b = source.getpixel((x, y))
        resultat.putpixel((x, y), negatif(r, g, b))

resultat.save("negatif.jpg")

L'idée à retenir : toute l'intelligence de la retouche tient dans la fonction qui transforme un pixel. Le reste est de la mécanique de parcours.

Pièges courants

  • Toujours dans l'ordre R, G, B. Le trio est dans cet ordre : rouge, vert, bleu. Pas dans un autre ordre.
  • Les valeurs restent dans [0, 255]. Si votre formule produit -5 ou 300, c'est qu'elle est mal pensée — une jauge ne peut ni être négative, ni dépasser le maximum.
  • Une fonction negatif ne modifie pas le pixel d'origine : elle renvoie un nouveau trio. Le pixel d'origine, lui, ne change pas.

Pour aller plus loin

Les effets que vous avez écrits sont les plus simples qui soient. On peut en imaginer beaucoup d'autres avec d'autres formules : éclaircir (ajouter à chaque composante), augmenter le contraste (étirer les valeurs vers les extrêmes), virer en sépia (mélanger les composantes selon des coefficients fixes). Tous ces effets ne sont rien d'autre qu'une formule différente sur les trois jauges, appliquée à chaque pixel de l'image.