Traiter une image en Python
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 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 :
- Lire ses trois nombres
r,g,b. - Calculer trois nouveaux nombres à partir de
r,g,b. - 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 :
En Python, on écrit cette transformation comme une fonction qui prend les
trois nombres r, g, b et renvoie les trois nouveaux nombres :
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).def negatif(r, g, b):— on définit une fonctionnegatifqui attend trois nombresr,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 :
Le pixel transformé devient (moy, moy, moy).
// (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 / 3vaut2.333…(un nombre à virgule),7 // 3vaut2(on garde uniquement la partie entière).
// qu'il
nous faut ici.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.
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-5ou300, c'est qu'elle est mal pensée — une jauge ne peut ni être négative, ni dépasser le maximum. - Une fonction
negatifne 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.