TP 12 d’informatique - MPSI - 06/02/2024 - Manipulation d’images
Le but de ce TP est d’utiliser Python pour manipuler des images au format jpg ou png. On s’intéresse
ici à des “images matricielles” (par opposition aux “images vectorielles” du format pdf), c’est à dire qu’une
image est composée de “pixels” répartis sur une grille de L colonnes et H lignes.
Dans ce sujet, les couleurs des pixels sont décrites à l’aide du codage RGB (Red Green Blue). Une couleur
est donc un triplet d’entiers (r, g, b) tel que 0 ⩽ r, g, b ⩽ 255, où r correspond à la quantité de rouge, g à la
quantité de vert et b à la quantité de bleu. Par exemple, la couleur (0, 0, 255) est un bleu pur, (255, 255, 0)
est un jaune (mélange de rouge et vert), (0, 0, 0) est le noir et (255, 255, 255) le blanc.
Soient (H, L) ∈ (N∗ )2 . On dit qu’une image est de dimension H × L si les pixels forment une grille de H
lignes et L colonnes. En Python, l’image sera représentée par une liste de listes de triplets d’entiers « mat:
list[list[int,int,int]] » où pour tout i ∈ J0, H – 1K, et tout j ∈ J0, L – 1K, le triplet mat[i][j] est le
codage RGB du pixel de coordonnées (i,j). Ici, l’entier i est l’indice de la ligne (i = 0 correspond à la
ligne du haut et i = H-1 à la ligne du bas) et l’entier j est l’indice de la colonne (j = 0 correspond à la
colonne de gauche et j = L-1 à la colonne de droite). Voici un exemple d’image de dimension 2 × 3 :
Rouge Vert Bleu
mat = [
[(255,0,0),(0,255,0),(0,0,255)], Jaune Noir Blanc
[(255,255,0),(0,0,0),(255,255,255)]
]
Récupérez l’archive zip sur la page web du cours et décompressez la dans le dossier de votre choix (si
vous êtes sur un ordinateur du lycée, utilisez comme d’habitude votre répertoire personnel U:/) :
https://informatique-lhp.fr/itc-mpsi.html
Vérifiez que le dossier TP12_images contient bien quatre images, puis exécutez le fichier TP12.py et
observez le résultat. Le fichier source TP12.py contient plusieurs fonctions permettant de manipuler des
images à l’aide du module PIL.Image :
→ La fonction de signature « get_mat(file_in: str) -> list[list[int,int,int]] » qui prend en
entrée le chemin vers un fichier png ou jpg et renvoie la matrice RGB correspondante.
→ La fonction de signature « save(mat: list[list[int,int,int]], file_out: str) -> NoneType »
qui enregistre l’image associée à une matrice RGB dans le fichier dont le chemin est file_out.
→ La fonction de signature « copy(file_in: str, file_out: str) -> NoneType » qui fait une copie
de l’image contenue dans le fichier file_in vers le fichier file_out. Notez qu’il est possible de modifier
l’extension du fichier copié (png vers jpg ou inversement). Cette fonction ne sera pas utile dans la
suite du TP, elle sert à illustrer l’utilisation des deux fonctions précédentes.
Dans tout le TP, vous devez utiliser les fonctions get_mat et save, mais pas directement le module
PIL.Image. Même lorsque ce n’est pas précisé, les fonctions que vous allez écrire prennent en entrée les
chaînes de caractères file_in et file_out. Le fichier file_in contient l’image à modifier et le résultat
devra être enregistré dans le fichier file_out. On pourra supposer que toutes les images contiennent au
moins une ligne et une colonne de pixels. L’image est_premier.jpg sera utilisée uniquement dans l’exercice
4
1
Exercice 1. Modification d’une image pixel par pixel
Étant donnée une couleur codée par un triplet (r,g,b), la couleur inverse est codée par le triplet
(255-r, 255-g, 255-b). Le négatif d’une image, est l’image dans laquelle chaque couleur est remplacée
par son inverse.
1. Écrire une fonction negatif qui créé le négatif d’une image. Vérifiez que vous obtenez la même image
lorsque vous appliquez deux fois la fonction negatif.
De manière plus générale, on peut appliquer sur chaque pixel une fonction
f(r: int, g: int, b: int) -> (int, int, int).
2. Écrire une fonction filtre qui prend en entrée une fonction f et applique f sur chacun des pixels
de l’image. Tester avec une fonction f permettant d’obtenir le négatif d’une image ainsi qu’avec la
fonction f : (r, g, b) 7→ (g, b, r).
Exercice 2. Réduction et agrandissement
Pour réduire une image d’un facteur d ∈ N∗ , la manière la plus simple est de ne conserver que les pixels
de coordonnées (i,j) tels que i et j sont divisibles par d.
1. (a) Si l’image initiale est de dimension H × L, quelle est la dimension de l’image finale ? (Attention :
une simple division entière par d n’est pas suffisant)
(b) Écrire une fonction reduction qui réduit l’image donnée en entrée d’un facteur d.
Pour agrandir une image d’un facteur d ∈ N∗ , on copie chaque ligne et chaque colonne d fois.
2. Écrire une fonction agrandissement qui agrandit l’image donnée en entrée d’un facteur d.
Exercice 3. Symétrie et rotation
Une symétrie axiale verticale consiste à appliquer une symétrie par rapport à la droite verticale qui passe
au milieu de l’image. Par exemple à partir du 1er pingouin du début de l’énoncé, on obtient le 2ème pingouin.
1. Écrire une fonction sym_vert qui effectue une symétrie axiale verticale sur l’image donnée en entrée.
Si besoin, on pourra lire les indications ci-dessous.
On souhaite maintenant faire une rotation d’un quart de tour dans le sens des aiguilles d’une montre.
Par exemple à partir du 1er pingouin du début de l’énoncé, on obtient le 3ème pingouin.
2. Écrire une fonction rotation1 qui effectue la rotation sur l’image donnée en entrée. Vérifiez que le
pingouin obtenu regarde vers le haut. Si besoin, on pourra lire les indications ci-dessous.
Indications (essayez de faire l’exercice sans lire ce qui suit). On répondra d’abord aux questions
intermédiaires ci-dessous dans le cas de la symétrie puis dans le cas de la rotation.
(a) Supposons que l’image initiale soit de dimension H × L. Quelle est la dimension de l’image finale ?
(b) On considère un pixel de coordonnées (i,j) sur l’image initiale. Donner les coordonnées de ce pixel
dans l’image finale dans les cas suivants :
(i) Le pixel est l’un des quatre pixels aux coins de l’image initiale.
(ii) Les coordonnées du pixel sont quelconques.
Exercice 4. Image en niveaux de gris
Dans le codage RGB, les différentes teintes de gris correspondent à des valeurs de la forme (r, g, b) où
r = g = b. Une première méthode pour convertir une image en niveaux de gris, consiste à fixer trois flottants
c1, c2, c3 tels que c1 + c2 + c3 = 1, puis à remplacer chaque pixel de couleur (r,g,b) par (m,m,m) où
m = c1 × r + c2 × g + c3 × b.
2
1. Écrire une fonction gris1 qui prend en entrée c1, c2, c3 et convertit une image en niveaux de gris
en utilisant la méthode décrite ci-dessus. On testera dans les cas suivants :
→ Les trois composantes RGB ont la même pondération : c1 = c2 = c3 = 1/3.
→ On tient compte uniquement de la composante verte : c1 = c3 = 0 et c2 = 1.
→ On utilise la recommandation 709 : c1 = 0.2126, c2 = 0.7152 et c3 = 0.0722.
Une autre approche consiste à fixer m comme étant la moyenne de la composante la plus forte et de la
composante la plus faible.
2. Écrire une fonction gris2 qui convertit une image en niveaux de gris en utilisant cette deuxième
méthode.
Pour obtenir une image en noir et blanc, on fixe un entier noté seuil et on convertit l’image en niveaux
de gris (en général avec la recommandation 709). Les pixels dont le paramètre m vérifie m ⩽ seuil sont
coloriés en noir et les autres pixels sont coloriés en blanc.
3. (a) Écrire une fonction noir_blanc qui prend en entrée une image en niveaux de gris (c’est à dire
dont les composantes RGB sont de la forme (m,m,m)) et la convertit en noir et blanc.
(b) L’image est_premier.jpg est la photo d’un programme Python. Déterminer un seuil permettant
d’améliorer la lisibilité de cette image.
Exercice 5. Transformation du photomaton
Dans cet exercice, on ne considère que des images dont la hauteur H et la largeur L sont paires. La
transformation du photomaton se définit comme suit :
• On découpe l’image en carrés de dimension 2 × 2.
• On construit quatre nouvelles images de hauteur H//2 et de largeur L//2 :
- L’image en haut à gauche est constituée des pixels qui se trouvent en haut à gauche des carrés
de dimensions 2 × 2.
- Idem en remplaçant les deux “en haut à gauche” par “en haut à droite”, puis “en bas à gauche”
et enfin “en bas à droite”.
(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,0) (0,2) (0,4) (0,6) (0,1) (0,3) (0,5) (0,7)
(1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (2,0) (2,2) (2,4) (2,6) (2,1) (2,3) (2,5) (2,7)
(2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (4,0) (4,2) (4,4) (4,6) (4,1) (4,3) (4,5) (4,7)
(3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (1,0) (1,2) (1,4) (1,6) (1,1) (1,3) (1,5) (1,7)
(4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (3,0) (3,2) (3,4) (3,6) (3,1) (3,3) (3,5) (3,7)
(5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,0) (5,2) (5,4) (5,6) (5,1) (5,3) (5,5) (5,7)
Image initiale Découpage en carrés de Image finale
dimension 2 × 2
1. Écrire une fonction photomaton qui prend en entrée un entier n ∈ N et applique la transformation du
photomaton n fois à une image.
Si on applique la transformation du photomaton suffisamment de fois, on fini par retrouver l’image
initiale. On appelle période de retour le nombre minimal d’itérations nécessaires pour retrouver l’image
initiale.
2. Écrire une fonction periode_retour qui renvoie la période de retour de l’image donnée en entrée. Pour
que les tests s’exécutent en un temps raisonnable, on pourra utiliser des images carrées. Par exemple,
la période de retour d’une image de dimension 400 × 400 est 18.
3
Exercice 6. Anneaux concentriques
Dans cet exercice, on fixe un réel R. Notre objectif est de colorier les
pixels d’une image carrée en noir et en blanc de manière à obtenir une série
d’anneaux ayant pour centre le milieu de l’image. Pour cela, on note d la
distance du pixel au centre de l’image. Puis on suit les règles suivantes :
→ Si d < R, on colorie le pixel en blanc.
√
→ Si R ⩽ d < R 2, on colorie le pixel en noir.
√ √
→ Si R 2 ⩽ d < R 3, on colorie le pixel en blanc.
√ √
→ Si R 3 ⩽ d < R 4, on colorie le pixel en noir . . .
√ √
De manière générale, si R n ⩽ d < R n + 1 avec n un entier pair alors le
pixel est colorié en blanc, sinon il est colorié en noir.
1. Expliquer comment déterminer en temps constant la couleur d’un pixel.
2. En déduire une fonction anneaux qui prend entrée R ainsi que la taille de l’image et créé l’image
décrite ci-dessus. Lorsque R = 50, vous devez observer les anneaux concentriques. En revanche, lorsque
R devient petit, la résolution de l’image n’est plus suffisante pour voir les anneaux qui sont remplacés
par d’autres motifs. On testera notamment avec 0 < R < 1.
4
Exercices à rendre au plus tard le 11/02/2024 à 20h
Exercice 7. Rognage
“Rogner une image” consiste à ne conserver qu’une partie de l’image. Écrire une fonction de signature :
rogner(file_in: str, file_out: str, i0: int, j0: int, H0: int, L0: int) -> NoneType
qui créé une nouvelle image telle que :
→ Le pixel en haut à gauche est celui de coordonnées (i0,j0) dans l’image initiale.
→ Cette nouvelle image est de dimension H0 × L0.
Par exemple, lorsqu’on rogne l’image pinguoin.png avec les paramètres i0 = 178, j0 = 140, H0 = 77 et
L0 = 74, on obtient l’une des pattes palmées du pingouin. On vérifiera que les valeurs de (i0, j0, H0, L0)
sont cohérentes avec la taille de l’image à l’aide d’un ou plusieurs assert.
Exercice 8. Symétrie et rotation bis
Une symétrie centrale est une symétrie par rapport au point se trouvant au centre de l’image. Par
exemple à partir du 1er pingouin du début de l’énoncé, on obtient le 4ème pingouin.
1. Répondre aux questions (a) et (b) des indications de l’exercice 3 dans le cas de la symétrie centrale.
2. Écrire une fonction de signature « sym_cent(file_in: str, file_out: str) -> NoneType » qui
effectue une symétrie centrale sur l’image donnée en entrée.
On souhaite maintenant faire une rotation d’un quart de tour dans le sens inverse des aiguilles d’une
montre. Par exemple à partir du 1er pingouin du début de l’énoncé, on obtient le 5ème pingouin.
3. Répondre aux questions (a) et (b) des indications de l’exercice 3 dans le cas de cette rotation.
4. Écrire une fonction de signature « rotation2(file_in: str, file_out: str) -> NoneType » qui
effectue la rotation sur l’image donnée en entrée.
Exercice 9. Convolution et détection de contours (exercice facultatif)
Convolution. Soient M et N deux matrices (c’est à dire des listes de listes de flottants). Plus tard, la
matrice M représentera l’une des trois composantes RGB d’une image. La matrice N s’appelle le noyau ;
elle contient n sous-listes de taille n où n ∈ N est un entier impair. Le produit de convolution de M et N est
une matrice notée R ayant les mêmes dimensions que M . Pour calculer chaque R[i][j], on imagine superposer
M et N en plaçant l’élément central de N sur M [i][j] (cet élément central existe car n est impair). La valeur
de R[i][j] est alors la somme de tous les produits ab où a est un élément de N et b est l’élément de M qui
lui est superposé. Pour gérer le cas où l’un des a n’est superposé à aucun élément, on duplique les valeurs
en bordure de M . Par exemple, avec :
1 2 3 4 5
0 −1 0 6 7 8 9 10
N = −1 5 −1 et M =
11 12 13 14 15
0 −1 0
16 17 18 19 20
on obtient M ′ la matrice dans laquelle les valeurs en bordure de M ont été dupliquées et R le résultat du
produit de convolution :
1 1 2 3 4 5 5
1
1 2 3 4 5 5 −5 −3 −2 −1 1
6 6 7 8 9 10 10 5 7 8 9 11
′
M = et R=
11 11 12 13 14 15 15 10 12 13 14 16
16 16 17 18 19 20 20 20 22 23 24 26
16 16 17 18 19 20 20
Les coefficients 13, −5 et −2 de R sont obtenus par les calculs :
5
13 = 0×7 + (−1) × 8 + 0×9 +
(−1) × 12 + 5 × 13 + (−1) × 14 +
0 × 17 + (−1) × 18 + 0 × 19.
−5 = 0×1 + (−1) × 1 + 0×2 +
(−1) × 1 + 5×1 + (−1) × 2 +
0×6 + (−1) × 6 + 0 × 7.
−2 = 0×2 + (−1) × 3 + 0×4 +
(−1) × 2 + 5×3 + (−1) × 4 +
0×7 + (−1) × 8 + 0 × 9.
1. Écrire une fonction de signature
convolution(file_in: str, file_out: str, N: list[list[float]]) -> NoneType
qui prend en entrée un noyau N et applique le produit de convolution sur chaque composante RGB
d’une image. Tester avec les noyaux décrits sur la page wikipédia :
https://fr.wikipedia.org/wiki/Noyau_(traitement_d%27image)
Détection de contours avec la méthode de Sobel. Afin de détecter les contours d’une image, on
propose d’utiliser la méthode de Sobel. Dans la suite, la couleur RGB (c, c, c) sera appelée “niveau de gris
c” :
⋆ L’image est d’abord convertie en niveaux de gris.
⋆ On calcule indépendemment deux produits de convolution ayant pour noyaux :
−1 0 1 1 2 1
−2 0 2 et 0 0 0
−1 0 1 −1 −2 −1
⋆ Pour chaque pixel de coordonnées
√ (i, j) dans l’image initiale, l’image finale contient un pixel dont le
niveau de gris est 255 − a2 + b2 où a (resp. b) est le niveau de gris du pixel (i, j) dans le résultat de
la première convolution (resp. deuxième convolution).
2. Écrire une fonction de signature « contours(file_in: str, file_out: str) -> NoneType » qui
prend en entrée une image et lui applique la méthode de Sobel. Par exemple à partir du 1er pingouin
du début de l’énoncé, on obtient le 6ème pingouin.