SNT Photo Filtrage

Identification

Infoforall

1 - Filtrage d'une image


Nous allons voir comment obtenir et modifier les pixels des photos à l'aide de Python et du module Pillow :

Pillow
Logo de Pillow, bibliothèque Python de gestion des images

Documents de cours : open document ou pdf

1 - Gestion des images avec le module Pillow

Intro (préparation du poste)° Ouvrir votre dossier/répertoire personnel et réaliser ces actions :

  • Ouvrir Thonny. Réduire la fenêtre de Thonny et de votre navigateur Web pour que chaque fenêtre occupe la moitié de l'écran.
  • Ouvrir votre dossier/répertoire personnel. Créer dans le dossier SNT un nouveau sous-dossier qu'on nommera photo1.
  • Sur Windows : vérifier via le menu affichage de votre explorateur de dossiers que vous avez bien coché la case "Afficher les extensions".

INSTALLATION DE PILLOW

✔ 01-A° Pour manipuler les images avec Python et Thonny, il faut disposer d'un module Python qui gère les images Pillow, issu d'un ancien projet nommé PIL. Commençons par voir s'il est installé sur votre poste.

  • Ouvrir Thonny.
  • Dans le menu Affichage ou View, sélectionner variables.

  • Tapez ceci dans la console pour voir si cela déclenche une erreur.
  • >>> from PIL import Image

    Si vous avez une erreur, vérifier d'abord que vous avez bien géré les majuscules / minuscules puis si on vous signale qu'il manque le module, lire et utiliser la note ci-dessous.

EN CAS D'ERREUR D'IMPORTATION : Installer un module avec Thonny

Si votre poste n'est pas muni de Pillow, on vous propose deux méthodes d'installation : commencez par la première et passez à la seconde si la première ne fonctionne pas :

1er méthode

  • Ouvrir le menu Tool/Outils en haut vers la droite dans Thonny
  • Sélectionner Manage Packages/Gérer les Paquets
  • Faire une recherche sur Pillow puis installer la bibliothèque. Ca peut être un peu long.
  • Vue Manage Packages

2e méthode

  • Ouvrir le menu Tool/Outils en haut vers la droite dans Thonny
  • Sélectionner Ouvrir la console du système : vous allez voir apparaître un terminal.
  • Dans le terminal, tapez ceci :
  • pip install Pillow

    Le téléchargement devrait se lancer, en espérant qu'il aboutisse...

✔ 01-B° Réaliser les actions suivantes :

  • Sur Windows : si vous n'êtes pas certain que ce soit le cas, vérifiez que la gestion des extensions est bien activée.
  • Avec un clic-droit sur l'image et "Enregistrer l'image sous", enregistrez ces deux images dans votre répertoire photo1 en gardant leurs noms et leurs extensions.
Image contenant beaucoup de rouge
Image de base libre de droit : le Palais des congrès de Montréal
Image avec transparence
Logo Pillow Python avec transparence

✔ 01-C° Enregistrer ce programme dans photo1 sous le nom photopython.py. Vous devriez donc avoir 3 fichiers dans ce répertoire.

📁 SNT

📁 photo1

📄 photopython.py

📄 photo-montreal-palais-des-congres.jpeg

📄 pillow.png

Lancer et vérifier que Pillow fonctionne et interagisse bien avec votre système :

1 2 3 4 5 6 7 8 9
from PIL import Image nom_image = "photo-montreal-palais-des-congres.jpeg" img = Image.open(nom_image) print("Image importée dans Python") img.show() print("Image affichée sur l'écran")

✔ 01-D° Répondre à ces questions :

  1. Comment se nomme la fonction qui permet d'ouvrir et stocker une image dans une variable ?
  2. Comme se nomme la fonction qui permet d'afficher une image à l'écran ?
  3. Qu'est-ce que ces fonctions ont de particulier au niveau de la syntaxe ?

...CORRECTION...

  1. Comment se nomme la fonction qui permet d'ouvrir et stockée une image dans une variable ?
  2. C'est la fonction open() à qui on doit transmettre le nom du fichier image sur le disque dur.

    1 2 3 4 5 6 7 8 9
    from PIL import Image nom_image = "photo-montreal-palais-des-congres.jpeg" img = Image.open(nom_image) print("Image importée dans Python") img.show() print("Image affichée sur l'écran")
  3. Comme se nomme la fonction qui permet d'afficher une image à l'écran ?
  4. C'est la fonction show() à qui on ne transmet rien visiblement.

  5. Qu'est-ce que ces fonctions ont de particulier au niveau de la syntaxe ?
  6. Ce ne sont pas des fonctions normales : on place l'objet sur lequel elles doivent agir devant la fonction. On parle de syntaxe pointée car on utilise un point entre les deux.

    img.show()

    Traduction : agit sur img en l'affichant.

Révisions : variables, fonctions d'entrée/sortie et if

⚙ 02° Complétez le programme pour qu'il fasse le travail suivant, puis lancez pour vérifier le résultat :

  • L06-L09 : on doit demander à l'utilisateur à l'aide de int(input()) lequel des deux fichiers il veut ouvrir ;
  • L11-L14 : on mémorise le nom du fichier voulu dans la variable nom_image ;
  • L16 : on mémorise les données de l'image dans Python dans la variable data_image en ouvrant l'image avec la fonction open() ;
  • L18-L23 : on affiche quelques informations sur l'image.
  • L25-L28 : On demande à l'utilisateur s'il veut afficher l'image à l'écran. S'il dit O pour oui, on l'affiche avec la fonction show().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) n = ... # ATTENTION, on veut récupérer un int, pas un str if n == 1: nom_image = nom1 elif n == ...: ... = ... img = Image.open(nom_image) nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Voulez-vous afficher l'image ? O/N ") rep = ... # on veut récupérer un str if ... == "O": ...

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) n = int(input()) if n == 1: nom_image = nom1 elif n == 2: nom_image = nom2 img = Image.open(nom_image) nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Voulez-vous afficher l'image ? O/N ") rep = input() if rep == "O": img.show()

Révisions : while, boucle Tant Que

⚙ 03-A° Pour le moment, l'utilisateur peut encore faire planter le programme : on lui demande de taper 1 ou 2 mais il peut taper n'importe quoi. Lancer le programme et lorsqu'on vous demande votre choix, tapez ce que vous voulez SAUF 1 ou 2. Le programme va s'arrêter sur une erreur.

Questions

  1. En regardant le message rouge, quelle ligne provoque l'erreur ?
  2. Expliquer ce qui provoque cette erreur.

...CORRECTION...

>>> %Run tp_photo.py Tapez : 1 pour ouvrir photo-montreal-palais-des-congres.jpeg 2 pour ouvrir pillow.png 5555 Traceback (most recent call last):    "/home/rv/Documents/SNT/photo1/tp_photo.py", line 21, in <module> print("Informations sur le fichier ", nom_image) NameError: name 'nom_image' is not defined
  1. L'erreur est provoquée par la ligne 21 comme l'indique l'interpréteur :
    16 print("Informations sur le fichier ", nom_image)
  2. Un NameError signifie qu'on tente d'utiliser une variable qui n'a pas été initialisée, c'est donc une variable inconnue pour Python. Pourquoi ?
  3. On a tapé 5555 sur l'exemple : on ne réalise donc aucun des blocs du IF : ni la ligne 11, ni la ligne 13 n'est validée à True.

    On arrive donc en ligne 21 sans avoir rencontré la ligne 12 ou la ligne 14 qui crée la variable nommée nom_image.

⚙ 03-B° Pour sécuriser la demande à l'utilisateur nous allons réaliser une boucle TANT QUE :

Seule l'étape 1 va donc changer un peu.

Question

Compléter ce nouveau qui devra intégrer le filtrage de la demande de l'utilisateur.

  • L8 : on crée une fausse valeur dans n contenant 0 par exemple.
  • L9 : tant que n n'est pas dans l'intervalle [0, 1]
    • L10-12 : on affiche la question à l'utilisateur.
    • L13 : on récupère sa réponse dans n (sous forme d'int, attention).
  • Une fois arrivé ici, on reprend le cours du programme précédent mais nous sommes certains
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" # Début du rajout --------------- n = ... # Fausse réponse pour rentrer dans la boucle au départ while n not in [..., ...]: print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) ... # Demande d'un entier à l'utilisateur #Fin du rajout ------------------ # Arrivé ici, c'est que n=1 ou n=2 if n == 1: nom_image = nom1 elif n == 2: nom_image = nom2 img = Image.open(nom_image) nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Voulez-vous afficher l'image ? O/N ") rep = input() if rep == "O": img.show()

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" # Début du rajout --------------- n = 0 # Fausse réponse pour rentrer dans la boucle au départ while n not in [1, 2]: print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) n = int(input()) #Fin du rajout ------------------ # Arrivé ici, c'est que n=1 ou n=2 if n == 1: nom_image = nom1 elif n == 2: nom_image = nom2 img = Image.open(nom_image) nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Voulez-vous afficher l'image ? O/N ") rep = input() if rep == "O": img.show()

Révisions : fonctions

⚙ 04-A° Le programme commence à être long et semble compliqué.

Nous allons le rendre plus court en plaçant chaque tâche ou presque dans une fonction.

Questions

  1. Lancer le programme ci-dessous pour vérifier qu'il agit de la même façon que le précédent.
  2. Chaque tâche un peu complexe est maintenant réalisée par une fonction : le programme principal en lignes 49-52 ne comporte que 4 lignes alors qu'il y avait 5 tâches différentes dans le programme précédent. Quelle est la fonction qui réalise 2 tâches ?
  3. Si les noms des fonctions sont bien choisis, a-t-on vraiment besoin d'aller lire le code interne des fonctions pour comprendre ce que réalise le programme ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
from PIL import Image # Définitions des fonctions def choisir_un_fichier(): """Renvoie le nom du fichier choisi par l'utilisateur""" nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" n = 0 # Fausse réponse pour rentrer dans la boucle au départ while n not in [1, 2]: print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) n = int(input()) if n == 1: nom_image = nom1 elif n == 2: nom_image = nom2 return nom_image def obtenir_caracteristiques(img, nom_image): """Affiche certaines caractéristiques de l'image, aucun renvoi""" nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) def demander_affichage(img): """Affiche l'image si l'utilisateur le veut, aucun renvoi""" print("Voulez-vous afficher l'image ? O/N ") rep = input() if rep == "O": img.show() # Programme principal nom_image = choisir_un_fichier() # Appel à la fonction choisir_un_fichier img = Image.open(nom_image) obtenir_caracteristiques(img, nom_image) demander_affichage(img)

...CORRECTION...

  1. -
  2. La fonction qui réalise 2 tâches est choisir_image() : elle demande et filtre la demande de l'utilisateur puis renvoie le nom du bon fichier.
  3. Non : en lisant uniquement les 4 lignes du programme principal, il est possible de comprendre ce qu'il réalise. C'est tout l'intérêt des fonctions : on peut "cacher" la façon de réaliser des tâches complexes et juste y faire appel.

⚙ 04-B° Regardons maintenant uniquement les appels de fonction et la première ligne des définitions.

Ne modifiez pas votre programme dans Thonny, c'est juste un affichage pour cette question.

6 7 23 27 28 39 40 47 48 49 50 51 52
# Définitions des fonctions def choisir_un_fichier(): """Renvoie le nom du fichier choisi par l'utilisateur""" return nom_image def obtenir_caracteristiques(img, nom_image): """Affiche certaines caractéristiques de l'image, aucun renvoi""" def demander_affichage(img): """Affiche l'image si l'utilisateur le veut, aucun renvoi""" # Programme principal nom_image = choisir_un_fichier() # Appel à la fonction choisir_un_fichier img = Image.open(nom_image) obtenir_caracteristiques(img, nom_image) demander_affichage(img)

Questions

  1. Sur la ligne 49 , on voit qu'on lance un appel à la fonction choisir_un_fichier() sans lui envoyer quoi que ce soit. Où voit-on qu'on n'a rien à lui envoyer ? Où voit-on qu'elle va renvoyer une réponse qu'il faudra stocker ?
  2. Sur la ligne 51, on voit qu'on envoie deux entrées à la fonction. Comment sait-on qu'il faut les fournir dans ce sens précis ?
  3. Sur la ligne 51, on ne stocke pas la réponse de la fonction. Pourquoi ?

...CORRECTION...

  1. Il faut comparer l'appel de la ligne 49 et la définition de la ligne 6 : on voit bien que la fonction n'attend rien en entrée. Ligne 23, on voit que la fonction va renvoyer une réponse. Il faudra donc la stocker.
  2. Il suffit de comparer l'appel de la ligne 51 et la définition de la ligne 27.
  3. On ne stocke pas la réponse de la fonction car elle ne renvoie rien : il n'y a aucun return. Elle agit puis elle rend la main.

⚙ 04-C° Quelqu'un décide de modifier les noms des variables du programme principal : ce nom programme va-t-il encore fonctionner ? Est-ce plus clair ou moins clair ?

6 7 23 27 28 39 40 47 48 49 50 51 52
# Définitions des fonctions def choisir_un_fichier(): """Renvoie le nom du fichier choisi par l'utilisateur""" return nom_image def obtenir_caracteristiques(img, nom_image): """Affiche certaines caractéristiques de l'image, aucun renvoi""" def demander_affichage(img): """Affiche l'image si l'utilisateur le veut, aucun renvoi""" # Programme principal a = choisir_un_fichier() # Appel à la fonction choisir_un_fichier b = Image.open(a) obtenir_caracteristiques(b, a) demander_affichage(b)

...CORRECTION...

Oui, rien n'oblige les variables du programme et des fonctions à porter le même nom.

Par contre, puisque le nom d'une variable doit nous aider à comprendre ce qu'elle contient, ce n'est pas très malin de leur donner des noms différents et encore moins d'utiliser des noms d'une lettre qui n'ont aucune signification.

Conclusion : ce programme fonctionne mais est plus compliqué à suivre pour un humain.

Afficher des informations sur une image

1 Les trois types d'images : 1 couche, 3 couches ou 4 couches
  • 1 couche L si image en nuance de gris : chaque pixel n'est défini que par sa luminosité L comprise entre (0 et 255) (ou 00 et FF)
  • 3 couches RGB si image en couleur : chaque pixel est défini par 3 valeurs R G B comprises entre (0 et 255) ou (00 et FF).
  • 4 couches RGBA si image avec transparence : chaque pixel est défini par 4 valeurs. En plus des valeurs RGB, on trouve une valeur A dite Alpha qui caractérise la transparence du pixel : doit-il être entiérement transparent (valeur 0/00) ou est-il entièrement opaque (valeur 255/FF). Sa valeur réelle peut donc être entre les deux.

⚙ 05° Nous allons rajouter des fonctionnalités à notre programme.

Questions

  1. Remplacer votre ancien programme par celui proposé ci-dessous.
  2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
    from PIL import Image # Définitions des fonctions def choisir_un_fichier(): """Renvoie le nom du fichier choisi par l'utilisateur""" nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" n = 0 # Fausse réponse pour rentrer dans la boucle au départ while n not in [1, 2]: print("Tapez :") print("1 pour ouvrir ", nom1) print("2 pour ouvrir ", nom2) n = int(input()) if n == 1: nom_image = nom1 elif n == 2: nom_image = nom2 return nom_image def obtenir_caracteristiques(img, nom_image): """Affiche certaines caractéristiques de l'image, aucun renvoi""" nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais couches = img.getbands() # Conteneur contenant le nom des bandes nb_cou = ...(couches) # Le nombre de bandes print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Nombre de pixels : ", ...*...) print("Couche(s) : ", couches) print("Nombre de couches : ", ...) return nb_cou def demander_affichage(img): """Affiche l'image si l'utilisateur le veut, aucun renvoi""" print("Voulez-vous afficher l'image ? O/N ") rep = input() if rep == "O": img.show() # Programme principal nom_image = choisir_un_fichier() # Appel à la fonction choisir_un_fichier img = Image.open(nom_image) nb_couches = obtenir_caracteristiques(img, nom_image) demander_affichage(img)
  3. Compléter la fonction obtenir_caracteristiques() pour qu'elle fonctionne comme on le veut maintenant :

    • Elle doit afficher le nombre total de pixels dans l'image en multipliant le nombre de pixels en largeur et en hauteur.
    • Elle doit afficher les couches de l'image avec img.getbands()
    • Elle doit afficher le nombre de couches en appliquant len() sur le résultat précédent
    • Elle doit renvoyer le nombre de couches (elle possède donc un return)
  4. Utiliser le programme pour obtenir le nombre de couches des deux images. Laquelle possède une transparence ?

...CORRECTION...

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
def obtenir_caracteristiques(img, nom_image): """Affiche certaines caractéristiques de l'image, aucun renvoi""" nb_col = img.width # width : largeur en anglais nb_lig = img.height # height : hauteur en anglais couches = img.getbands() # Conteneur contenant le nom des bandes nb_cou = len(couches) # Le nombre de bandes print("Informations sur le fichier ", nom_image) print("Pixels en largeur : ", nb_col) print("Pixels en hauteur : ", nb_lig) print("Nombre de pixels : ", nb_col*nb_lig) print("Couche(s) : ", couches) print("Nombre de couches : ", nb_cou) return nb_cou

Pour le fichier du palais des congrès :

Tapez : 1 pour ouvrir photo-montreal-palais-des-congres.jpeg 2 pour ouvrir pillow.png 1 Informations sur le fichier photo-montreal-palais-des-congres.jpeg Pixels en largeur : 1024 Pixels en hauteur : 771 Nombre de pixels : 789504 Couche(s) : ('R', 'G', 'B') Nombre de couches : 3 Voulez-vous afficher l'image ? O/N

Pour le fichier du logo Pillow :

Tapez : 1 pour ouvrir photo-montreal-palais-des-congres.jpeg 2 pour ouvrir pillow.png 2 Informations sur le fichier pillow.png Pixels en largeur : 248 Pixels en hauteur : 250 Nombre de pixels : 62000 Couche(s) : ('R', 'G', 'B', 'A') Nombre de couches : 4 Voulez-vous afficher l'image ? O/N

2 - Gestion des pixels avec Python

Nous allons maintenant changer les valeurs RGB des différents pixels.

Si vous n'avez pas fait la première partie, il faut impérativement récupérer les images et faire quelques réglages. Voir ci-dessous.

A vérifier (préparation du poste)° Ouvrir votre dossier/répertoire personnel et réaliser ces actions :

  • Ouvrir Thonny. Réduire la fenêtre de Thonny et de votre navigateur Web pour que chaque fenêtre occupe la moitié de l'écran.
  • Ouvrir votre dossier/répertoire personnel. Créer dans le dossier SNT un nouveau sous-dossier qu'on nommera photo1.
  • Sur Windows : vérifier via le menu affichage de votre explorateur de dossiers que vous avez bien coché la case "Afficher les extensions".
  • Téléchargez les deux images suivantes (clic droit, télécharger sous) et placez là dans le répertoire photo1 :

    Image contenant beaucoup de rouge
    Image de base libre de droit : le Palais des congrès de Montréal
    Image avec transparence
    Logo Pillow Python avec transparence

OBTENIR LES COULEURS RGB D'UN PIXEL

✔ 06° Utiliser la syntaxe img.getpixel( (x, y) ) en notation pointée sur l'image stockée dans img permet d'obtenir les 3 ou 4 valeurs qui définissent un pixel sur cette image.

Lancer ce programme pour demander les couleurs du pixel de coordonnées 50, 100 puis celui de coordonnées 200, 30 par exemple. Lisez bien ce qu'on vous indique sur la console pour savoir quoi répondre.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" nom_image = nom1 # Choisissez directement l'image, pas de input(). img = Image.open(nom_image) # Ouverture de l'image dans Python nb_couches = len( img.getbands() ) # Recherche du nombre de couches print("Ouverture dans Python des données de ", nom_image) while True: print("\nDonner les coordonnées x puis ENTREE puis y puis ENTREE") x = int(input()) y = int(input()) if nb_couches == 3: # RGB r, g, b = img.getpixel( (x, y) ) print( (r, g, b) ) elif nb_couches == 4: r, g, b, a = img.getpixel( (x, y) ) print( (r, g, b, a) )

4 choses à noter :

  1. Ligne 12 : nous créons une boucle infinie avec while True qui revient à dire "Tant que vrai est vrai, réalise un tour de boucle".
  2. Ligne 5 : on choisit le fichier voulu directement dans le code, plus d'interrogation de l'utilisateur.
  3. Lignes 17 et 20 : on récupère les valeurs d'un pixel avec la syntaxe pointée img.getpixel( (x, y) )
  4. Lignes 17 et 20 : il faut mettre à gauche le bon nombre de variables pour récupérer les 3 ou 4 valeurs du pixel, d'où la nécessité de connaître le nombre de couches.

MODIFIER LES COULEURS RGB D'UN PIXEL

✔ 07° La syntaxe img.putpixel( (x, x), (0, 255, 0, 255) ) en notation pointée sur l'image voulue permet de modifier les 4 valeurs RGBA du pixel de coordonnées (x, y). Ici, on applique la couleur (0, 255, 0) équivalente à #00FF00. Il s'agit donc de vert vif. La valeur A vaut 255, ce qui indique que le pixel est totalement opaque.

A la fin du programme, l'affichage de l'image vous permettra de voir une droite verte sur l'image : explication lors de la question suivante.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
from PIL import Image nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" nom_image = nom2 # Choississez directement l'image au démarrage. img = Image.open(nom_image) # Ouverture de l'image dans Python nb_couches = len( img.getbands() ) # Recherche du nombre de couches print("Ouverture dans Python des données de ", nom_image) if nb_couches == 3: # RGB for k in range(200): img.putpixel( (k, k), (0, 255, 0) ) elif nb_couches == 4: for k in range(200): img.putpixel( (k, k), (0, 255, 0, 255) ) # A sur 255 : opaque img.show()

L'image n'est pas modifiée sur le disque dur pour le moment : seule les données de l'image dans Python (en RAM) sont modifiées.

✔ 08° Comprendre ce qui suit, c'est important pour la suite. Si ce n'est pas clair, demandez de l'aide.

On obtient une droite car on demande de modifier les couleurs de quelques pixels : (0,0) puis (1, 1) puis (2, 2) puis (3, 3)...

Comment ? A l'aide d'une boucle.

if nb_couches == 3: # RGB for k in range(200): img.putpixel( (k, k), (0, 255, 0) )

Cette boucle bornée for k in range(200) fournit les valeurs de k de 0 à 199, une par une à chaque tour de boucle. On impose donc que le pixel de coordonnées (k,k) soit un pixel vert, pour toutes les valeurs que prendra k.

Son action est donc équivalente à ce code :

if nb_couches == 3: # RGB img.putpixel( (0, 0), (0, 255, 0) ) img.putpixel( (1, 1), (0, 255, 0) ) img.putpixel( (2, 2), (0, 255, 0) ) ... ... ... img.putpixel( (197, 197), (0, 255, 0) ) img.putpixel( (198, 198), (0, 255, 0) ) img.putpixel( (199, 199), (0, 255, 0) )

La droite se dirige vers le bas car le système d'axes n'est pas le même que celui que vous utilisez en mathématique :

  • Le point (0,0) correspond au point en haut à gauche, et pas au centre l'image ;
  • L'axe x est orienté vers la droite ;
  • Mais l'axe y est orienté vers le bas.
Les axes dans Pillow

Passons maintenant aux modifications globales des couleurs de l'image : le filtrage des couleurs.

3 - Modification et filtrage des couleurs

⚙ 09° Associer mentalement chacun des effets A à E à l'instruction Python 1 à 5 qui correspond.

Effets

  1. Permet de modifier un pixel RGBA.
  2. Permet de récupérer le nombre de couches.
  3. Permet de récupérer la largeur en pixels.
  4. Permet de récupérer la hauteur en pixels.
  5. Permet de récupérer les 3 ou 4 valeurs RGB(A).

Instructions

  1. len( image.getbands() )
  2. img.width
  3. img.height
  4. img.getpixel( (x,y) )
  5. img.putpixel( (x,y), (r,g,b,a) )

...CORRECTION...

Effets

  1. [5] Permet de modifier les 3 ou 4 valeurs RGB(A) : image.putpixel( (x,y), (r,g,b,a) )
  2. [1] Permet de récupérer le nombre de couches de l'image : len( img.getbands() )
  3. [2] Permet de récupérer la largeur en pixels de l'image : img.width
  4. [3] Permet de récupérer la hauteur en pixels de l'image : img.height
  5. [4] Permet de récupérer les 3 ou 4 valeurs RGB(A) : img.getpixel( (x,y) )

⚙ 10° Compléter ce programme qui doit permettre d'afficher un par un tous les pixels de l'image :

L06 : il faudra chercher toutes les valeurs possibles y de lignes.

L07 : il faudra chercher toutes les valeurs possibles x de colonnes.

1 2 3 4 5 6 7 8 9 10
from PIL import Image nom_image = "photo-montreal-palais-des-congres.jpeg" img = Image.open(nom_image) for y in range(...): # Pour chaque ligne y de l'image for ... in range(...): # et chaque colonne x de cette ligne r, g, b = img.getpixel( (x,y) ) # On récupère les 3 valeurs print( (x,y), " : ", (r, g, b) )

Lancer le programme complété : il va afficher un par un toutes les valeurs de pixels. Vous pouvez stopper avant la fin, ça va être long sinon.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10
from PIL import Image nom_image = "photo-montreal-palais-des-congres.jpeg" img = Image.open(nom_image) for y in range(img.height): # Pour chaque ligne y de l'image for x in range(img.width): # et chaque colonne x de cette ligne r, g, b = img.getpixel( (x,y) ) # On récupère les 3 valeurs print( (x,y), " : ", (r, g, b) )

✔ 11° Lancer ce nouveau programme : cette fois, on récupère les valeurs de chaque pixel mais on les modifie : sur la ligne 17 ou 21, on voit qu'on intervertit la valeur rouge et la valeur bleue lors du putpixel().

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
from PIL import Image # Programme principal nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" nom_sauvegarde = "modification.png" nom_image = nom2 # Choississez directement l'image au démarrage. img = Image.open(nom_image) for y in range(img.height): # Pour chaque lignee y de cette image for x in range(img.width): # Pour chaque colonne x de cette ligne if len( img.getbands() ) == 3: r, g, b = img.getpixel( (x,y) ) img.putpixel( (x,y) , (b, g, r) ) elif len( img.getbands() ) == 4: r, g, b, a = img.getpixel( (x,y) ) img.putpixel( (x,y), (b, g, r, a) ) img.show() # On la montre img.save(nom_sauvegarde) # On l'enregistre img.close() # On ferme proprement le lien entre Python et le fichier

Nouveautés

Ligne 24 : on sauvegarde l'image sur le disque dur, avec un autre nom pour éviter d'écraser l'image de base.

Ligne 25 : on ferme la liaison entre Python et le fichier sur disque dur, pour éviter les conflits d'écriture.

Exemple avec notre image contenant beaucoup de rouge et d'orange

Image contenant beaucoup de rouge
Image de base libre de droit : le Palais des congrès de Montréal

Et qui devient une image contenant beaucoup de bleu et de cyan :

Image contenant beaucoup de bleu
Même image en intervertissant Bleu et Rouge

⚙ 12° Cela fonctionne. Mais on voit que nous sommes obligés de taper deux fois l'inversion du rouge et du bleu. En cas de modification, on peut oublier l'une des deux versions. Autant passer par une fonction qui réalisera cette modification.

Nous décidons donc de créer une fonction filtrer() :

  • qui reçoit en entrée 3 valeurs rgb dans old_r, old_g, old_b ;
  • qui renvoie en sortie 3 valeurs rgb new_r, new_g, new_b.

Question de compréhension

Expliquer pourquoi la fonction filtrer() intervertit bien rouge et bleu alors qu'elle renvoie new_r, new_g, new_b en ligne 8.

01 02 03 04 05 06 07 08 09 10 11
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_b new_g = old_g new_b = old_r return (new_r, new_g, new_b) print( filtrer(1, 2, 3) ) print( filtrer(50, 10, 200) )

...CORRECTION...

Il suffit de suivre les affectations des valeurs pour le rouge et le bleu, sans faire attention aux noms des variables.

Vous verrez qu'on reçoit les valeurs dans l'ordre RGB mais qu'on renvoie en réalité les valeurs dans l'ordre BGR.

01 02 03 04 05 06 07 08
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_b # On place la valeur bleue à la place de la rouge new_g = old_g # On laisse la valeur g inchangée new_b = old_r # On place la valeur rouge à la place de la bleue return (new_r, new_g, new_b)

Sur ce pixel, les intensités du bleu et du rouge sont donc inversées.

✔ 13° Utiliser notre version finale du programme qui modifie les valeurs des pixels en utilisant la fonction filtrer() à la fois pour les images 3 couches et les images 4 couches.

L'intérêt ? : si on veut modifier la façon de filtrer, il suffit de modifier la fonction et la modification aura lieu pour les deux types d'images.

Pour alléger le programme, on décide de se passe des variables nb_col nb_lig nb_couches en allant directement chercher ces valeurs dans l'image.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
from PIL import Image def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_b new_g = old_g new_b = old_r return (new_r, new_g, new_b) # Programme principal nom1 = "photo-montreal-palais-des-congres.jpeg" nom2 = "pillow.png" nom_sauvegarde = "modification.png" nom_image = nom1 # Choississez directement l'image au démarrage. img = Image.open(nom_image) for y in range(img.height): # Pour chaque ligne y de cette image for x in range(img.width): # Pour chaque colonne x de cette ligne if len(img.getbands()) == 3: r, g, b = img.getpixel( (x,y) ) r, g, b = filtrer(r, g, b) img.putpixel( (x, y), (r,g,b) ) elif len(img.getbands()) == 4: r, g, b, a = img.getpixel( (x,y) ) r, g, b = filtrer(r, g, b) img.putpixel( (x,y), (r,g,b,a) ) img.show() # On la montre img.save(nom_sauvegarde) # On l'enregistre img.close() # On ferme proprement le lien entre Python et le fichier

Vérifiez que vous obtenez bien le même résultat qu'avant mais avec un seul endroit pour comprendre comment on modifie les pixels : les lignes 4 à 11 de la fonction filtrer().

Filtrer les couleurs veut dire en laisser passer certaines et pas d'autres.

Pour l'instant, la fonction filtrer() ne provoque qu'une interversion de couleurs. La modifier va vous permettre de créer beaucoup d'effets différents.

⚙ 14° Modifiez uniquement le code de la fonction filtrer() pour créer un filtre rouge : on ne veut garder que la valeur rouge du pixel, les autres valeurs sont placées à 0.

Image contenant beaucoup de rouge Image avec uniquement la couche Rouge

...CORRECTION...

04 05 06 07 08 09 10 11
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_r # On laisse le rouge new_g = 0 # On supprime le vert new_b = 0 # On supprime le bleu return (new_r, new_g, new_b)

⚙ 15° Modifiez uniquement le code de la fonction filtrer() pour créer un filtre rouge plus léger : on veut garder la valeur rouge du pixel, les autres valeurs sont divisées par 3.

Image contenant beaucoup de rouge Image avec une réduction de vert et du bleu

...CORRECTION...

04 05 06 07 08 09 10 11
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_r # On laisse le rouge new_g = old_g // 3 # On diminue le vert new_b = old_b // 3 # On diminue le bleu return (new_r, new_g, new_b)

⚙ 16° Créer un filtre bleu pour qu'il ne laisse passer que le bleu.

Image contenant beaucoup de rouge Image avec uniquement la couche Bleue

...CORRECTION...

04 05 06 07 08 09 10 11
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = 0 # On supprime le rouge new_g = 0 # On supprime le vert new_b = old_b # On laisse le bleu return (new_r, new_g, new_b)

⚙ 17° Créer un filtre jaune pour qu'il ne laisse passer que le jaune : donc le vert et le rouge. Il ne bloque que le bleu

Image contenant beaucoup de rouge Image où on bloque le bleu

...CORRECTION...

04 05 06 07 08 09 10 11
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_r # On laisse le rouge new_g = old_g # On laisse le vert new_b = 0 # On supprime le bleu return (new_r, new_g, new_b)

⚙ 18° On voit que notre filtre "jaune" fournit une image un peu plus foncée. C'est normal : on supprime l'intensité bleue donc on reçoit moins de lumière.

Nous allons l'améliorer : on supprime toujours le bleu mais on rajoute la moitié de l'intensité bleu au rouge et l'autre moitié au vert.

Bien entendu, il faudra veiller à ce que les intensités ne dépassent pas 255. Si c'est le cas, on limite à 255.

Compléter la fonction pour qu'elle fasse son travail correctement.

1 2 3 4 5 6 7 8 9 10 11 12
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_r + ... # On augmente le rouge if ... > 255: ... = ... new_g = old_g + old_b # On augmente le vert if ... > 255: ... = ... new_b = 0 # On supprime le bleu return (new_r, new_g, new_b)

Image contenant beaucoup de rouge Image où on bloque le bleu

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" new_r = old_r + old_b // 2 # On augmente le rouge if new_r > 255: new_r = 255 new_g = old_g + old_b // 2 # On augmente le vert if new_g > 255: new_g = 255 new_b = 0 # On supprime le bleu return (new_r, new_g, new_b)

⚙ 19° IMAGE GRISE : Remplacer les trois intensités R, G et B par la moyenne des trois. On pourra utiliser la division euclidienne en Python en utilisant // plutôt que simplement /.

04 05 06 07 08 09 10 11 12
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" moyenne = = (old_r + old_g + old_b) // 3 new_r = moyenne new_g = moyenne new_b = moyenne return (new_r, new_g, new_b)

Puisque les trois intensités RGB ont la même valeur, votre oeil percevra du gris.

Image contenant beaucoup de rouge Image grise

Si on veut transformer une image en couleur en réelle perception grisée pour un oeil humain, il faut utiliser des formules particulières pour avoir un rendu correct car nos yeux ne sont pas sensibles de la même manière à la lumière rouge, verte ou bleue. ils sont beaucoup plus sensibles à la lumière verte qu'aux lumières bleues et rouges.

⚙ 20° IMAGE GRISE HUMAINE : Remplacer les trois intensités R, G et B par :

moyenne = (21*old_r + 71*old_g + 8*old_b) // 100

La nouvelle version grise à gauche, la moyenne pure à droite :

Image grise version 2 Image grise

...CORRECTION...

04 05 06 07 08 09 10 11 12
def filtrer(old_r, old_g, old_b): """Fonction qui renvoie des valeurs RGB après les avoir modifiées""" moyenne = (21*old_r + 71*old_g + 8*old_b) // 100 new_g = moyenne new_b = moyenne return (new_r, new_g, new_b)

4 - FAQ

Pas de question pour le moment

Comme vous pouvez le voir, il est assez facile de modifier les couleurs d'une image.

Dans d'autres activités, nous verrons qu'on peut en tirer d'autres informations et même qu'il est parfois possible d'y laisser votre nom ou vos coordonnées GPS. C'est dire ...

Prochaine activité : nous allons voir comment trouver les contours d'une image.

Comme une image n'est qu'une suite de nombres, une IA peut assez facilement les générer, les modifier.

Activité publiée le 02 12 2019
Dernière modification : 23 01 2025
Auteur : ows. h.