TP — Images et matrices de pixels¶
Travaux pratiques · MPSI (tronc commun) · 2021-2022 · Python
Dans ce TP, on va manipuler des images sous forme de matrices de pixels. Pour cela, on commence par définir des fonctions qui nous seront utiles dans la suite. Évaluez la cellule suivante !
Noir et blanc¶
Une image en noir et blanc est une matrice de 0 ou de 1. C'est-à-dire un tableau dont les éléments sont des tableaux de 0 et de 1 représentant chaque ligne de l'image, de haut en bas.
Ainsi pour faire un échiquier, on peut écrire :
def echiquier(w,h):
m = []
for j in range(h):
l = [] # la jeme ligne
for i in range(w):
l.append( (i+j)%2 )
m.append(l)
return m
affiche_image_nb(echiquier(10,20))

affiche_image_nb(racoon_nb)

Les deux fonctions suivantes sont pratiques. taille renvoie la taille
(largeur, hauteur) d'une image et image_nb_vide renvoie une image avec
uniquement des 0.
def taille(m):
return (len(m[0]), len(m))
def image_nb_vide(w,h):
m = []
for j in range(h):
l = []
for i in range(w):
l.append(0)
m.append(l)
return m
On peut alors réécrire la fonction précédente de manière plus naturelle.
Attention pour accéder au pixel de coordonnées \((i,j)\) comme il est sur
la \(j\)-ème ligne et la \(i\)-ème colonne, il faut écrire m[j][i].
m = image_nb_vide(20,10)
x1, x2, y = 3, 8, 5
for x in range(x1, x2+1):
m[y][x] = 1
affiche_image_nb(m)

>>> list(range(3,8))
[3, 4, 5, 6, 7]
Les résultats attendus pour les trois fonctions suivantes sont

Question 1
def ligne_horiz(m, x1, x2, y):
'''Trace une ligne horizontale de pixels à 1
allant de (x1,y) à (x2,y)'''
Corrigé
def ligne_horiz(m, x1, x2, y):
'''Trace une ligne horizontale de pixels à 1
allant de (x1,y) à (x2,y)'''
for x in range(x1, x2+1):
m[y][x] = 1
Tests :
m = image_nb_vide(15,10)
ligne_horiz(m, 3, 12, 3)
ligne_horiz(m, 5, 10, 6)
affiche_image_nb(m)
m
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Question 2
def ligne_vert(m, x, y1, y2):
'''Trace une ligne verticale de pixels à 1
allant de (x,y1) à (x,y2)'''
Corrigé
def ligne_vert(m, x, y1, y2):
'''Trace une ligne verticale de pixels à 1
allant de (x,y1) à (x,y2)'''
for y in range(y1, y2+1):
m[y][x] = 1
Tests :
m = image_nb_vide(15,10)
ligne_vert(m, 3, 2, 8)
affiche_image_nb(m)
Question 3
def rectangle(m, x1, y1, x2, y2):
'''Trace un rectangle de (x1,y1) à (x2,y2)'''
Corrigé
def rectangle(m, x1, y1, x2, y2):
'''Trace un rectangle de (x1,y1) à (x2,y2)'''
ligne_horiz(m, x1, x2, y1)
ligne_horiz(m, x1, x2, y2)
ligne_vert(m, x1, y1, y2)
ligne_vert(m, x2, y1, y2)
Tests :
m = image_nb_vide(15,10)
rectangle(m, 3, 2, 12, 8)
affiche_image_nb(m)
Pour la prochaine fonction vous devez créer une image de dimension \((2n+1, 2n+1)\) présentant des carrés concentriques comme ici

On va commencer par faire une fonction qui étant donné une image de dimension \((2n+1) \times (2n+1)\) et un entier \(i \le n\) va afficher le carré centré dont le coin supérieur gauche est \((i, i)\).
Question 4
def carres_concentriques(n):
m = image_nb_vide(2*n+1,2*n+1)
return m
Corrigé
def carres_concentriques(n):
m = image_nb_vide(2*n+1,2*n+1)
for i in range(n+1):
if i % 2 == 1:
rectangle(m, i, i, 2*n-i, 2*n-i)
return m
Tests :
affiche_image_nb(carres_concentriques(20))
On vous demande maintenant de faire une fonction qui inverse une image en changeant tous les 0 en 1 et tous les 1 en 0.
ATTENTION Il faudra renvoyer une nouvelle image et non modifier l'image passée en entrée. C'est important : il en va de la survie des ratons-laveurs !
>>> m = carres_concentriques(5)
>>> m
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Question 5
def inverse_nb(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def inverse_nb(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
for i in range(w):
for j in range(h):
m2[j][i] = 1 - m[j][i]
return m2
Tests :
affiche_image_nb(inverse_nb(carres_concentriques(10)))
affiche_image_nb(inverse_nb(racoon_nb))

On va maintenant écrire des fonctions opérant des symétries et des rotations d'image. Les résultats attendus sont



Question 6
def sym_vert(m):
'''Effectue une symétrie verticale de l'image, c'est-à-dire que le haut devient le bas
et réciproquement'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def sym_vert(m):
'''Effectue une symétrie verticale de l'image, c'est-à-dire que le haut devient le bas
et réciproquement'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
for i in range(w):
for j in range(h):
m2[h-j-1][i] = m[j][i]
return m2
Tests :
affiche_image_nb(sym_vert(racoon_nb))
Question 7
def sym_horiz(m):
'''Effectue une symétrie horizontale de l'image, c'est-à-dire que la gauche devient la droite
et réciproquement'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def sym_horiz(m):
'''Effectue une symétrie horizontale de l'image, c'est-à-dire que la gauche devient la droite
et réciproquement'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
for i in range(w):
for j in range(h):
m2[j][w-i-1] = m[j][i]
return m2
Tests :
affiche_image_nb(sym_horiz(racoon_nb))
Question 8
def rot90(m):
'''Effectue une rotation de l'image d'un quart de tour dans le sens trigonométrique'''
w, h = taille(m)
m2 = image_nb_vide(h, w)
return sym_vert(m2)
Corrigé
def rot90(m):
'''Effectue une rotation de l'image d'un quart de tour dans le sens trigonométrique'''
w, h = taille(m)
m2 = image_nb_vide(h, w)
for i in range(w):
for j in range(h):
m2[i][j] = m[j][i]
return sym_vert(m2)
Tests :
affiche_image_nb(rot90(racoon_nb))
Niveaux de gris¶
Les images en niveaux de gris sont des matrices d'entiers où la couleur va de 0 (noir) à 255 (blanc). Ainsi 128 sera un gris médian.
def degradé(w,h):
m = []
for j in range(h):
l = []
for i in range(w):
l.append( (i+j)%256 )
m.append(l)
return m
affiche_image(degradé(200,200))

affiche_image(racoon_g)

Normalement, si vous avez fait ce qu'il faut vos fonctions de symétries et de
rotations continuent de fonctionner. On pourra même directement utiliser
image_nb_vide car 0 est noir ici aussi.
affiche_image(rot90(racoon_g))

Résultat attendu pour la fonction suivante :

Question 9
def inverse_g(m):
'''Inverse le niveau de gris.'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def inverse_g(m):
'''Inverse le niveau de gris.'''
w, h = taille(m)
m2 = image_nb_vide(w, h)
for i in range(w):
for j in range(h):
m2[j][i] = 255 - m[j][i]
return m2
256 - x, qui envoie le noir (0) sur 256, hors
de l'intervalle des niveaux de gris \([0, 255]\).
Tests :
affiche_image(inverse_g(racoon_g))
Pour faire un effet de flou, on peut remplacer chaque valeur par la moyenne des 9 valeurs constituées par le pixel et ses 8 voisins immédiats dans la grille, en ignorant les bords. On vous demande de faire cette fonction. Le résultat attendu est

On pourra laisser ainsi les bords en noir.
Question 10
def flou(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def flou(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
for i in range(1, w-1):
for j in range(1, h-1):
s = 0
for di in range(i-1,i+2):
for dj in range(j-1,j+2):
s += m[dj][di]
m2[j][i] = s // 9
return m2
Tests :
affiche_image(flou(racoon_g))
Pour la fonction suivante, on suppose que l'image a des dimensions toutes paires \((2p, 2q)\), on peut donc regrouper les pixels en carrés 2x2 ainsi :
a11 b11 a12 b12 ... a1p b1p
c11 d11 c12 d12 ... c1p d1p
...
aq1 bq1 ... aqp bqp
cq1 dq1 ... cqp dqp
et on vous demande de les réorganiser ainsi :
a11 a12 ... a1p b11 b12 ... b1p
...
aq1 aq2 ... aqp bq1 bq2 ... bqp
c11 c12 ... c1p d11 d12 ... d1p
...
cq1 cq2 ... cqp dq1 dq2 ... dqp
En quelque sorte, on veut l'image des pixels en haut à gauche, l'image des pixels en haut à droite, ...
Le résultat attendu ici est

Question 11
def split4(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
return m2
Corrigé
def split4(m):
w, h = taille(m)
m2 = image_nb_vide(w, h)
p = w//2
q = h//2
for i in range(w):
for j in range(h):
mi = i % 2
mj = j % 2
m2[j//2 + mj * q][i//2 + mi * p] = m[j][i]
return m2
Tests :
affiche_image(split4(racoon_carre))
Dans la cellule suivante on effectue une boucle d'affichage en itérant la
fonction précédente sur racoon_carre : racoon_carre,
split4(racoon_carre), split4(split4(racoon_carre)), ...
Vous pouvez expliquer ?
m = racoon_carre
for i in range(10):
# Enlevez le commentaire suivant, il empeche de sauvegarder le notebook...
# affiche_image(m)
m = split4(m)
Images en couleur¶
On peut également faire des images en couleurs en rajoutant une dimension :
chaque pixel n'est plus un entier, mais une liste ou un tuple à trois éléments
[r,g,b] donnant les quantités, de 0 à 255, de chaque composante rouge, verte
ou bleue.
def degradé_couleur(w,h):
m = []
for j in range(h):
l = []
for i in range(w):
l.append( [ i%256, (256-j)%256, (i+j)%256 ] )
m.append(l)
return m
affiche_image(degradé_couleur(300,200))

On donne aussi une image pour faire les tests appelée racoon.
affiche_image(racoon)

Reprendre les fonctions précédentes dans le cas de la couleur !
Bonus¶
Allez chercher la définition de la fractale de Mandelbrot et faites un calcul de cette fractale dans notre format d'image.