Infoforall

Python 04 : Modification d’images via Python

Nous avons vu la dernière fois comment coder la couleur d’un pixel et comment changer le type d’enregistrement d’une image via un logiciel basique comme PAINT.

Nous allons aujourd’hui aller plus loin et voir comment on peut modifier les images de façon automatisée.

1 - Au fait, c’est quoi cette histoire d’intensité comprise entre 0 et 255 …

Nous avons vu que l’intensité des trois couleurs de base (rouge, vert, bleu) est codée par un nombre compris entre 0 et 255. Pourquoi pas 0 et 358 ou 1 et 100 ?

Tout cela est lié à la façon de stocker les chiffres dans un ordinateur.

La plus petite unité mémoire est le bit : une case qui contient 0 ou 1. C’est l’un ou l’autre.

CODAGE DE L’INTENSITE SUR 1 bit :

Si on codait la couleur ROUGE sous cette forme, on aurait :

Cas n°Valeur binaireDonne :Visuel
Cas 00Pas de rouge
Cas 11Rouge(100%)

On obtient donc 2 = 21 cas possibles : de 0 à 1.

CODAGE DE L’INTENSITE SUR 2 bits :

Si on codait la couleur ROUGE sous cette forme, on aurait :

Cas n°Valeur binaireDonne :Visuel
Cas 000Pas de rouge
Cas 101Un peu(33%)
Cas 210Beaucoup(66%)
Cas 311Rouge(100%)

On obtient donc 4 = 22 cas possibles : de 0 à 3.

On voit bien qu'on obtient plus de nuances.

CODAGE DE L’INTENSITE SUR 3 bits :

Si on codait la couleur ROUGE sous cette forme, on aurait :

Cas n°Valeur binaireDonne :Visuel
Cas 0000Pas de rouge
Cas 100114%
Cas 201029%
Cas 301143%
Cas 410057%
Cas 510171%
Cas 611086%
Cas 7111100%

On obtient donc 8 = 23 cas possibles : de 0 à 7.

Bref, ensuite un peu plus de nuances.

01° Combien a-t-on de cas possibles avec 8 bits ? Sur combien de bits est codé l’intensité d’une couleur sachant que sa valeur varie de 0 à 255 ?

Pour vous donner une idée du nombre de nuances que cela représente, voilà les nuances de rouge pour 8 bits :

ValeurVisuelValeurVisuelValeurVisuel
085170
186171
287172
388173
489174
590175
691176
792177
893178
994179
1095180
1196181
1297182
1398183
1499184
15100185
16101186
17102187
18103188
19104189
20105190
21106191
22107192
23108193
24109194
25110195
26111196
27112197
28113198
29114199
30115200
31116201
32117202
33118203
34119204
35120205
36121206
37122207
38123208
39124209
40125210
41126211
42127212
43128213
44129214
45130215
46131216
47132217
48133218
49134219
50135220
51136221
52137222
53138223
54139224
55140225
56141226
57142227
58143228
59144229
60145230
61146231
62147232
63148233
64149234
65150235
66151236
67152237
68153238
69154239
70155240
71156241
72157242
73158243
74159244
75160245
76161246
77162247
78163248
79164249
80165250
81166251
82167252
83168253
84169254
85170255

255 représente donc sur 8 bits l'intensité maximale de rouge.

Mais comment faire le lien entre 255 et les 8 bits ?

Pour transformer un nombre binaire en nombre en base 10 (notre base naturelle puisqu’on a 10 doigts), il faut savoir que :

  • Le bit le plus à droite est nommé le bit de poids faible car il code uniquement 1.
  • Le bit juste d’à côté code 2.
  • Celui d’encore après code 4
  • ...

Voilà un exemple pour un nombre binaire de 8 bits (on appelle cela un octet), M = 1010 1011.

Nombre M =10101011
Les bits codent 1286432168421
On obtient donc12832821

D'ou la valeur de M en base 10 : M = 128 + 32 +8 + 2 + 1 = 171.

02° Que valent les intensités RGB si R = 1111 0000, G = 1011 0010 et B = 0011 1100 ?

Si vous bloquez, vous pouvez faire un tour dans la partie FICHES, vous y trouverez une fiche sur le binaire :

03° Alors, à votre avis, pourquoi dit-on d’un codage RGB sur 8 bits pour chaque couleur qu’il s’agit d’un codage 24 bits ?

...CORRECTION...

Il faut 8 bits pour chaque composante R, G et B.

Au total, on a besoin de 8+8+8 = 24 bits pour coder la couleur.

04° Sachant qu’on a 256 nuances d’intensités pour chaque couleur, combien obtient-on d’assemblage de couleurs possibles ?

...CORRECTION...

Il faut calculer 256*256*256.

Au total, cela donne 16 777 216 possibilités.

En gros, plus de 16 millions de nuances de couleurs !

2 - Installation du module Pillow : gestion des images

Si ce n'est pas déjà fait, il faut installer Pillow. Si vous êtes en salle informatique, vous pouvez passer cette installation, c'est déjà installé.

Pour installer Pillow (chez vous par exemple), il faut :

  1. Avoir installé Python en incluant bien l'option rajout à Path
  2. Ouvrir la console système (pas la console Python)
  3. Peut-être mettre à jour pip en tapant :
  4. > pip install --upgrade pip

  5. Installer le module PILLOW de gestion d’image :
  6. > pip install Pillow

Si ca ne marche pas, je vous laisse aller lire le tutoriel disponible sur notre site :

3 - Première manipulation de Pillow : Manipulation des pixels

Nous allons travailler sur une variante de Tux, le pinguin de Linux avec une image proposée ici : Image qui ne s'affiche pas chez vous !

Nous allons utiliser des chemins relatifs pour enregistrer cette image et en sauvegarder d’autres. Cela veut dire que si on donne juste comme information le nom de l’image (« linux.jpg »), le programme va rechercher le fichier depuis l’endroit où se situe votre fichier Python (.py). Attention donc à bien enregistrer les images au bon endroit sur le disque dur ou sur le réseau.

05° Faire fonctionner le programme suivant en plaçant correctement le fichier image au même endroit que le programme.

from PIL import Image as Img


ref_image = Img.open("linux.jpg")


largeur = ref_image.width

print("L’image a une largeur en pixels de ", largeur)


hauteur = ref_image.height

print("L’image a une hauteur de ", hauteur)


print(ref_image.format)

print(ref_image.getbands())


print(ref_image.getpixel((98,88)))


ref_image.show()

votre dossier de travail

mon_programme.py

linux.jpg

Que fait ce programme :

from PIL import Image as Img

Depuis le module PIL (Pillow), on importe la bibliothèque nommée Image . Pour ne pas taper trop souvent Image, on pourra utilser un alias Img à la place de Image dans le cadre de notre programme.

ref_image = Img.open("linux.jpg")

On utilise la méthode-constructeur open tirée de la bibliothèque Img. On stocke l’image contenue dans le fichier-image dans un objet-image nommée ref_image.Il ne s’agit ni d’une variable int, str ou float mais bien d’un nouveau type de contenant : un objet-image. On notera la présence du point entre Img et open.

largeur = ref_image.width

On utilise la propriété width s’appliquant aux objets-images pour connaître la largeur de l’image en pixels. On stocke dans une variable integer nommée largeur. L’objet auquel on demande la propriété width doit être un objet-image. On notera la présence du point entre ref_image et width.

print("L’image a une largeur en pixels de ", largeur)

On affiche la largeur sur la console Python.

hauteur = ref_image.height

On utilise la propriété height s’appliquant aux objets-images pour connaître la hauteur de l’image en pixels. On stocke dans une variable integer nommée hauteur. On notera la présence du point entre ref_image et width.

print("L’image a une hauteur de ", hauteur)

On affiche la largeur sur la console Python.

print(ref_image.format)

On affiche le format d’enregistrement de l’objet-image ref_image renvoyé via la propriété format

print(ref_image.getbands())

On affiche le nombre de couches de codage des couleurs et les noms des couches, renvoyés par la méthode getbands.

On pourrait avoir

  • trois couches (RGB) pour une image en couleurs OU une seule couche (intensité) pour une image en nuance de gris,
  • une couche de transparence ALPHA éventuellement.

On peut donc avoir entre 1 et 4 couches au total.

print( ref_image.getpixel((98,88)) )

On affiche la composition de la couleur du pixel (98,88), soit 98 en largeur et 88 en hauteur, renvoyée par la méthode getpixel. Remarquez bien la présence de parenthèses autour des coordonnées : (98,88). On transmet un tuple, tuple qui contient lui-même deux variables.

ref_image.show()

On utilise la méthode show s’appliquant aux objets-images pour afficher à l’écran l’objet-image ref_image.

06° Que vaut la largeur et la hauteur de l’image ? Sous quel format d’enregistrement est-elle sauvegardée ? Possède-t-elle 3 niveaux de couleurs ?

...Hauteur Largeur...

La variable largeur est calculée en appliquant la propriété width sur l'objet-image ref_image : largeur = ref_image.width.

A l'aide du print, on voit que la largeur (ref_image.width) est de 204 pixels.

De la même façon, avec ref_image.height, on voit que la hauteur de l'objet-image ref_image est de 204 pixels.

...Format et couleur...

Grace à la propriété format, on voit que l'objet-image ref_image provient d'une image JPEG : ref_image.format

On voit avec la méthode getbands() qui l'image possède trois couches, nommées 'R', 'G', 'B'. On obtient la réponse via un tuple : ('R', 'G', 'B').

07° Que valent les intensités des trois couleurs RGB sur le pixel (98,88) ?

...CORRECTION...

La méthode getpixel permet de voir les intensités RGB d'un pixel (de coordonnées 98,88 ici). Elles valent :

(237, 205, 0) : soit 237 pour l'intensité Rouge, 205 pour l'intensité Verte et 0 pour l'intensité Bleue.

Les tuples

On notera donc que les coordonnées ou les intensités lumineuses sont données entre parenthèses ( ) : il s’agit d’un type particulier de variables qu’on nomme tuples ou n-uplets :

(98,88) est un 2-uplets qui contient deux valeurs ordonnées : 98 et 88.

(237, 205, 0) est un 3-uplets qui contient les trois intensités d'un pixel dans l'ordre : R = 237, G = 205 et B = 0.

Si on veut récupérer le contenu d’un n-uplets, on doit lui affecter autant de variables qu’il contient de grandeurs. Ainsi la méthode getpixel renvoie un tupel qui contient les trois intensités RGB. Si on veut stocker les valeurs dans des variables, il faut donc placer trois variables.

rouge, vert, bleu = ref_image.getpixel((98,88))

Comme ref_image.getpixel((98,88)) renvoie ici (237, 205, 0), on en déduit que :

  • La variable rouge est affectée à 237
  • La variable vert est affectée à 205
  • La variable bleu est affectée à 0

C'est comme si on avait noté ceci : rouge, vert, bleu = (237, 205, 0)

Si on veut changer les couleurs d’un pixel, il faut utiliser la méthode putpixel qui s’applique aux objets-image. Regardons l'exempe ci-dessous :

ref_image.putpixel( (0,0) , (rouge,vert,bleu) )

Ici, on colorie le pixel (0,0) de l’objet-image ref_image avec de nouvelles intensités RGB contenues dans les variables rouge, vert, bleu.

ref_image.putpixel( (0,0) , (237,205,0) )

Affecte directement les valeurs RGB 237, 205 et 0.

Remarque : on peut gagner quelques lignes en utilisant la propriété size de l’image qui stocke la largeur et la hauteur dans un 2-uplets (width,height) :

Voici une commande en deux lignes :

largeur = ref_image.width

hauteur = ref_image.height

Ces instructions sont remplacables par ceci :

largeur,hauteur = ref_image.size

Ou encore ceci (on rajoute clairement les parenthèses pour exprimer qu'on a un tuple en réception) :

(largeur,hauteur) = ref_image.size

08° Utiliser le code ci-dessous pour changer le pixel (0,0). Où se situe-t-il ? En haut à gauche ou en bas à gauche? Pensez à utiliser le zoom.

from PIL import Image as Img


ref_image = Img.open("linux.jpg")


largeur,hauteur = ref_image.size

rouge,vert,bleu = ref_image.getpixel( (98,88) )


ref_image.putpixel( (0,0) , (rouge,vert,bleu) )

ref_image.putpixel( (10,10) , (0,0,0) )

ref_image.show()

Le codage du 2-uplet de coordonnées est donc le suivant :

position des pixels

Pour l’instant, il est donc un peu long de modifier beaucoup de pixels à la fois. Il existe néanmoins des moyens de le faire ne vous inquiétez pas.

09° Sauvegardons notre modification. Pour cela , on rajoute la méthode save à la suite de notre programme :

ref_image.save("pixels_1.jpg")

Allez voir dans le dossier où se trouve votre fichier code : vous devriez avoir une image en plus maintenant.

Qu'est-ce qu'une variable ? (1)

Et ref_image qui contient l'objet-image, c'est une variable ou non alors ?

Voyez dans un premier temps, la variable comme un alias permettant d'accéder à un contenu mémoire.

Sur la ligne ci-dessous, l'ordinateur va créer une zone mémoire et va y stocker 100. On nomme cela la déclaration de la variable.

monScore = 100

On pourra alors accéder à la valeur stockée simplement en tapant monScore. L'ordinateur remplacera alors cela par le contenu : 100.

C'est pareil mais plus compliqué avec ceci :

ref_image = Img.open("linux.jpg")

On déclare une variable ref_image qui fait référence à une image aux données multiples et nombreuses. La variable ref_image permettra alors d'agir sur l'objet créé en utilisant un point. Par exemple :

ref_image.show()

Une variable contenant un objet sert donc à retrouver la référence mémoire des données de cet objet. C'est pourquoi, dans cette activité, j'ai choisi de nommer la majorité des variables de ce type en commençant par ref pour dire référence.

4 - Gestion des couleurs

On peut décomposer les images selon les trois couches : on peut ainsi obtenir l’allure des intensités de rouge, de vert et de bleu utilisées pour obtenir l’image finale.

Image qui ne s'affiche pas chez vous !
Image de base en trois couches 'RGB'
couche rouge en couleur couche verte en couleur couche bleue en couleur
Les couches rouge-vert-bleu

Comme votre oeil reçoit les trois images à la fois, on interpréte cela comme la première image, l'image multicolore. On constate même l'impression de jaune alors qu'il est totalement absent de la décomposition élémentaire en trois images purement rouge, verte et bleue.

En réalité, si on fait la décomposition, l'ordinateur se moque de connaitre la nature de la couleur unique qu'il code. L'objet ne contient qu'une unique couleur et donc seule l'intensité est importante. On obtient alors en réalité trois couches de type 'L' pour Light, qui s'affiche en nuances de gris si on veux les afficher.

couche rouge en couleur couche verte en couleur couche bleue en couleur
Les vraies couches 'L' rouge-vert-bleu

Méthode split

Si un objet-image ref_image possède trois couches RGB, on peut obtenir la décomposition suivante :

Pour cela, on utilise la ligne de code suivante : ref_image_r, ref_image_g, ref_image_b = ref_image.split().

Encore une fois, vous remarquerez qu'on place un point entre le nom de l'objet-image (ref_image) et le nom de la méthode utilisée (split).

from PIL import Image as Img


# - - - -

# A Création de l'objet image ref_image

# - - - -


ref_image = Img.open("linux.jpg")


largeur,hauteur = ref_image.size


print("-- Objet-Image ref_image de base --")


print("Format d'enregistrement :")

print(ref_image.format)


print("Nombre et type de couches : ")

print(ref_image.getbands())


print("Couleur codée du pixel (98,88) : ")

print(ref_image.getpixel((98,88)))


# - - - -

# B Création de l'objet image de la couche rouge

# - - - -


ref_image_r, ref_image_g, ref_image_b = ref_image.split()

ref_image_r.show()


print("")

print("-- Objet-image ref_image_r avant sauvegarde --")


print("Format d'enregistrement :")

print(ref_image_r.format)


print("Nombre et type de couches : ")

print(ref_image_r.getbands())


print("Couleur codée du pixel (98,88) : ")

print(ref_image_r.getpixel((98,88)))


# - - - -

# C Sauvegarde dans un fichier de l'objet image de la couche rouge

# - - - -


ref_image_r.save("linux_rouge.jpg")


print("")

print("-- Objet-image x_r après sauvegarde --")


print("Format d'enregistrement :")

print(ref_image_r.format)


print("Nombre et type de couches : ")

print(ref_image_r.getbands())


print("Couleur codée du pixel (98,88) : ")

print(ref_image_r.getpixel((98,88)))


# - - - -

# D Création d'un objet-image z à partir du nouveau fichier

# - - - -


# Nous allons maintenant créer un nouvel objet-image à partir du fichier image créé

z = Img.open("linux_rouge.jpg")


print("")

print("-- Objet-image z issu de la sauvegarde de ref_image_r")


print("Format d'enregistrement :")

print(z.format)


print("Nombre et type de couches : ")

print(z.getbands())


print("Couleur codée du pixel (98,88) : ")

print(z.getpixel((98,88)))


input("Appuyer sur ENTREE")

Quelques indications sur ces lignes de code :

from PIL import Image as Img


# - - - -

# A Création de l'objet image ref_image

# - - - -


ref_image = Img.open("linux.jpg")

largeur,hauteur = ref_image.size

print("-- Objet-Image x de base --")

print("Format d'enregistrement :")

print(ref_image.format)

print("Nombre et type de couches : ")

print(ref_image.getbands())

print("Couleur codée du pixel (98,88) : ")

print(ref_image.getpixel((98,88)))

Ici, on importe la bibliothèque Image, on crée un objet-image ref_image à partir du fichier-image "linux.jpg" et on affiche quelques propriétés de cette image.

# - - - -

# B Création de l'objet image de la couche rouge

# - - - -


ref_image_r, ref_image_g, ref_image_b = ref_image.split()

ref_image_r.show()

print("")

print("-- Objet-image x_r avant sauvegarde --")

print("Format d'enregistrement :")

print(ref_image_r.format)


print("Nombre et type de couches : ")

print(ref_image_r.getbands())


print("Couleur codée du pixel (98,88) : ")

print(ref_image_r.getpixel((98,88)))

On décompose l'objet-image ref_image (qui a 3 couches RBG) en trois images (ayant une seule couche L) nommées ref_image_r, ref_image_g et ref_image_b.

On affiche ref_image_r et ensuite quelques propriétés de cette objet-image ref_image_r.

# - - - -

# C Sauvegarde dans un fichier de l'objet image de la couche rouge

# - - - -


ref_image_r.save("linux_rouge.jpg")

On sauvegarde cette couche ref_image_r dans un fichier-image nommmé "linux_rouge.jpg".

On tente dans la suite du code d'afficher quelques propriétés de l'objet-image situé en mémoire vive après que ses données soient enregistrées dans un fichier situé sur le disque dur.

10° Lancer le programme. L’image issue de la couche rouge est-elle rouge ? La couleur de son pixel est-elle codée avec un 3-uplet ou s’agit-il d’une valeur unique ? Comparer à l’intensité du rouge de ce pixel sur l'objet-image ref_image de base.

...CORRECTION...

Non l'objet-image ref_image_r est en nuance de gris. Par contre, blanc vaut dire que l'intensité R est à 255 (max) et noir veut dire que l'intensité R est à 0 (min).

Chaque pixel est donc uniquement caractérisé par une valeur, et plus trois.

237 est bien l'intensité du rouge du pixel initial de ref_image : (237, 205, 0) (voir votre console ou ci-dessous).

Résultat pour l'objet-image ref_image initial qui possède bien 3 couches ('R', 'G', 'B') :

-- Objet-Image ref_image de base --

Format d'enregistrement :

JPEG

Nombre et type de couches :

('R', 'G', 'B')

Couleur codée du pixel (98,88) :

(237, 205, 0)

On notera que ref_image possède un format JPEG car il a été créé à partir d'un fichier-image JPG.

Résultat pour l'objet-image ref_image_r qui ne possède qu'une couche issue de la couche 'R' de ref_image :

-- Objet-image ref_image_r avant sauvegarde --

Format d'enregistrement :

None

Nombre et type de couches :

('L',)

Couleur codée du pixel (98,88) :

237

On remarquera que l'objet ref_image_r n'a pas de format d'enregistement (None). C'est normal : cet objet n'a pas été créé à partir d'un fichier-image mais à partir de la décomposition RGB d'un autre objet-image.

11° Pourquoi la couche rouge n’a-t-elle besoin que de l’information intensité, codée de 0 à 255 ?

...CORRECTION...

C'est "simple" : la méthode getbands() renvoie via l’information 'L' que cette image ne possède qu’une seule couche et donc une seule couleur 'grise'.

Chaque pixel est donc uniquement caractérisé par une valeur (comprise entre 0 et 255) et pas un 3-uplet des trois valeurs.

Pour le reste du code, je ne donnerai que quelques indications :

Résultat pour l'objet-image ref_image_r après enregistrement et création de l'image "linux_rouge.jpg":

-- Objet-image ref_image_r après sauvegarde --

Format d'enregistrement :

None

Nombre et type de couches :

('L',)

Couleur codée du pixel (98,88) :

237

Aucune modification ! Conclusion : l'objet-image n'est pas modifié en mémoire si on l'utilise pour créer un fichier-image ensuite. La propriété format contient donc le format au moment de la création de l'objet-image.

Résultat pour l'objet-image z issu de la lecture du fichier-image "linux_rouge.jpg":

-- Objet-image z issu de la sauvegarde de ref_image_r

Format d'enregistrement :

JPEG

Nombre et type de couches :

('L',)

Couleur codée du pixel (98,88) :

241

On voit que cette fois, z a un format JPEG. Comme ref_image_r, elle ne possède qu'une couche 'grise'. On voit ici par contre un défaut du format JPEG : l'intensité est légérement différente de l'intensité initiale : le format JPEG compresse l'image en créant une perte d'informations qui se traduit par une modification légère des intensités.

Le pixel initialement à 237 est passé à 241.

Attention donc au format JPEG : il prend peu de place mais si vous modifiez l'image, les intensités des pixels non modifiés normalement ne gardent pas forcément leurs valeurs exactes.

Méthode merge

Si on veut modifier la couche bleue, on peut changer la valeur des pixels : on va mettre quelques pixels de la couche bleu à 0.

Pour lire l'intensité d'un pixel, nous avons utilisé la méthode getpixel.

Pour modifier le pixel on utilise la méthode putpixel.

Pour décomposer une image RGB en trois couches, on avait utilisé la méthode split.

Pour reconstituer une image RBG à partir de trois images L notées ref_image_r, ref_image_g et ref_image_b, on utilise la méthode-constructeur merge :

from PIL import Image as Img


ref_image = Img.open("linux.jpg")

ref_image_r, ref_image_g, ref_image_b = ref_image.split()


ref_image_b.putpixel((10,10),0)

ref_image_b.putpixel((11,10),0)

ref_image_b.putpixel((12,10),0)

ref_image_b.putpixel((12,11),0)

ref_image_b.putpixel((10,11),0)

ref_image_b.putpixel((11,11),0)

ref_image_b.show()


z = Img.merge("RGB",(ref_image_r,ref_image_g,ref_image_b))

z.show()

12° Voit-on une tâche noire apparaître sur la couche bleue ? Justifier ensuite la couleur de la tâche sur l’image reconstituée z en utilisant la nouvelle couche B.

La couche B La nouvelle image

...CORRECTION...

La tache noire correspond aux pixels qu'on a mis à 0 sur la couche bleue.

Lorsqu'on recrée l'image, les pixels ont donc une intensité R=255,G=255 et B=0. Or du rouge et du vert donne du jaune en synthèse additive.

13° Inverser deux couches dans l’avant dernière ligne : mettez la couche bleue à la place de la rouge et la rouge à la place de la bleue. Vous nommerez z2 l’objet-image obtenu. Que constatez-vous ?

...CORRECTION...

z2 = Img.merge( "RGB" , (ref_image_b,ref_image_g,ref_image_r) )

14° Rajouter une ligne de code pour inverser encore deux couches lors de la création d’un objet-image nommé cette fois z3. Afficher z3.

15° Sauvegarder

...CORRECTION...

ref_image_b.save("couche_rouge.jpg") # idem pour la couche bleue et verte

z2.save("resultat_2.jpg") # idem pour l'objet-image z3

Nous allons travailler avec cette image mm.jpg :

16° Appliquer votre programme précédent sur le fichier-image "mm.jpg" (il faudra donc modifier le nom du fichier initial dans votre programme). Sauvegarder votre programme final dans un emplacement stable de façon à pouvoir le réutiliser plus tard.

Votre programme devrait vous avoir créer plusieurs fichiers-image se basant sur votre image initiale.

...CORRECTION...

from PIL import Image as Img


ref_image = Img.open("mm.jpg")


ref_image_r, ref_image_g, ref_image_b = ref_image.split()


z2 = Img.merge("RGB",(ref_image_g,ref_image_r,ref_image_b))

z3 = Img.merge("RGB",(ref_image_g,ref_image_b,ref_image_r))


ref_image_r.save("couche_rouge.jpg")

ref_image_g.save("couche_verte.jpg")

ref_image_b.save("couche_bleue.jpg")

z2.save("resultat_2.jpg")

z3.save("resultat_3.jpg")

5 - Modifier la taille d’une image

On utilise la méthode resize( (width,height) ). Elle s’applique aux objets-images de la bibliothèque PIL.

Remarquez bien qu'on doit lui transmettre un tuple contenant les nouvelles dimensions et pas directement les deux dimensions. Les concepteurs de la bibliothèque ont préféré faire comme ça. A vous de respecter ce qu'on doit envoyer en n'oubliant pas les parenthèses du tuple.

Si on veut modifier ref_image par exemple, on peut utiliser

ref_image = ref_image.resize( (1000,1000) )

La méthode ne modifie pas l'image : elle crée simplement une copie modifiée. Si vous voulez sauvegarder la modification, il faut donc la réaffecter dans la même variable. D'où la présence du signe =. Je crée ici un nouvel-objet image à partir de l'objet initial mais je détruis l'ancien contenu en plaçant le nouveau contenu dans cet alias.

Attention : on ne garde pas les proportions si on place n'importes quelles valeurs.

17° Appliquer un changement de dimensions en (1000,1000) puis en (700,1000). Que constate-t-on en terme de pixelisation lorsqu’on veut trop agrandir une image ?

6 - Création d’une image

Modifier une image, c’est bien mais on veut parfois en créer une. On utilise alors la méthode-constructeur nommée … new. C’est original, vous en conviendrez.

refNouvelleImage = Img.new("RGB",(largeur_voulue, hauteur_voulue),(R,G,B))

Ainsi refNouvelleImage = Img.new("RGB",(400,200), (255,0,0)) va créer un objet-image de 400 pixels de largeur, 200 de hauteur et entièrement rouge.

Nous allons travailler avec cette image :

18° Puisque mm.jpg est initialement à la taille (160,160) , créer un objet-image gris de taille (160*3,160). On le nommera image_affiche.

...CORRECTION...

image_affiche = Img.new("RGB",(160*3, 160),(100,100,100))

fond gris

7 - Coller une image sur une autre image

Cette fois, on utilise la méthode paste qu'on doit utilisée de cette façon :

affiche.paste(x,(largeur_voulue,hauteur_voulue))

On va rajouter l’objet-image x en orange sur l'exemple ci-dessous) sur l’objet-image affiche (en bleu sur l'exemple). Attention : Les deux images doivent être de même type : L, RGB.... Les coordonnées correspondent à l’endroit où on placera le premier pixel (0,0) de l’image qu’on vient coller sur l’autre.

paste

19° Rajouter les objets-images ref_image, z2 et z3 des parties précédentes sur l’image image_finale de façon à voir les trois images à la fois. Sauvegarder le résultat dans un fichier « final.jpg ».

première variation ou 2e variation ou 3e variation
Trois résultats z possibles : avec 3 couches, on obtient 6 possibilités.

Le résultat obtenu qu'on obtient une fois les trois images collées sur le fond noir précédent :

visuel final

...CORRECTION...

Pour coller l'image ref_image, on utilise :

image_affiche.paste(ref_image,(0,0))

A vous de rajouter les autres images sur l'affiche.

20° Modifier le programme pour qu’on puisse choisir le nom du fichier initial via l’interface (input) et qu’il gère seul les dimensions des objets à créer et le placement des images qu’on colle sur l’image finale.

Voilà pour l’instant. Vous aurez l’occasion de rencontrer bien d’autres choses sur la gestion des images au fur et à mesure de la présentation des différents projets. Ceux qui voudront un projet accès sur la gestion des images pourront donc découvrir comment réaliser d’autres actions sur les images, la notion de transparence et la couche alpha par exemple

On parle dans cette activité de méthodes.

Fonction et méthode sont deux entités très proches mais les deux termes ne désignent pas exactement la même chose :

Une fonction peut être vue comme un bout de code stocké, qui ne s'éxécute que si on en lance l'appel, avec son nom.

Une méthode est une sorte de fonction mais elle ne peut s'appliquer qu'à un type d'objet bien spécifique. On l'utilise en notant le nom de l'objet utilisé suivi d'un point et du nom de la méthode.

Vous débutez, la différence ne doit pas être évidente. Retenez qu'une méthode s'applique à un objet, là où on peut lancer le nom d'une fonction sans faire appel à aucun objet.

La différence entre les deux sera vue dans l'activité sur les fonctions et surtout dans les activités sur la programmation objet.

8 - FAQ

Question : je suis obligé de nommer les variables des objets-images en commençant par ref_ ?

Absolument pas.

Vous pouvez nommer les variables comme vous voulez. J'ai utilisé ref_ en début d'activité pour bien mettre le doigt sur le fait que les variables-objets sont des variables contenant une référence à l'objet.

Question : comment utiliser un putpixel si l'image possède plusieurs couches ?

Ici, l'image modifiée avec la méthode putpixel était une image n'ayant qu'une seule couche 'L'.

On ne doit donc fournir qu'un seul argument : la nouvelle valeur lumineuse.

ref_image_b.putpixel((11,11),0)

Si l'image possède trois couches ('R','G','B'), il faut fournir un tuple :

ref_image_b.putpixel((11,11),(0,50,100))

Question : comment lire un à un les valeurs de pixels ?

Il faut utiliser la boucle FOR. Voici donc un code fonctionnel qui va vous permettre de lire (et d'éventuellement agir) sur les pixels. Si vous avez déjà un peu programmer, vous saurez certainement l'utiliser.

from PIL import Image as Img # On importe la classe Img

monImage = Img.open("linux.jpg") # On crée un objet-image à partir d'un fichier-image

nLargeur = monImage.width # On va chercher la largeur de l'image

nHauteur = monImage.height # On va chercher la hauteur de l'image

for y in range(nHauteur) : # Pour y variant de 0 jusqu'à nHauteur exclus

    for x in range(nLargeur) : # Pour x variant de 0 jusqu'à nLargeur exclus

        r,g,b = monImage.getpixel((x,y)) # Place dans r,g et b les RGB du pixel (x,y) de monImage

        RAJOUTER VOTRE CODE PERSONNEL ICI

RETOUR AU PROGRAMME

Comme vous pouvez le voir, c'est la tabulation (la touche TAB) qui va faire comprendre à l'interpréteur Python que les instructions voulues sont bien à traier dans la boucle.

Lorsque vous voudrez sortir de la boucle FOR, il suffira de revenir à l'indentation normale.

Question : Lorsque je lance mon programme, ça ne fonctionne pas. Python me dit que mon image ne possède pas 3 couches ...

C'est possible. Je n'ai pas parlé du cas des images à 4 couches : 'R','G','B','A'. Le 'A' correspond à la couche ALPHA, la couche de transparence.

Si vous avez une image à 4 couches, il faut simplement utiliser un tuple à 4 valeurs lorsque vous utilisez les méthodes putpixel ou getpixel.