Matrices de pixels et images
Cours · informatique (tronc commun des prépas scientifiques), chapitre 8 · prépas scientifiques, tronc commun · le début du chapitre, la suite sur Adloun
8.1 Introduction et motivation
Une photographie numérique n'est rien d'autre qu'un immense tableau de nombres : chaque case — un pixel — contient l'intensité lumineuse d'un point de l'image. Faire pivoter la photo, l'éclaircir, la flouter, détecter les contours d'un visage : toutes ces opérations, qui semblent relever de la magie des logiciels de retouche, sont des parcours de tableaux à deux dimensions — des doubles boucles du chapitre 3, appliquées à un objet qu'on peut voir.
C'est ce qui fait des images le terrain d'entraînement parfait pour les tableaux bidimensionnels, dont la manipulation est l'objectif technique du chapitre : création (et son piège fameux), parcours par lignes et par colonnes, transformations d'indices (rotations, symétries), changements d'échelle, et l'opération reine du traitement d'image — la convolution, où chaque pixel est recalculé à partir de ses voisins. Le bénéfice pédagogique est immédiat : une erreur d'indice, invisible dans un tableau de nombres, saute aux yeux sur une image — l'image penche, se déchire ou se brouille. Le débogage devient littéralement visuel.
8.2 Tableaux à deux dimensions
8.2.1 Listes de listes
En Python, un tableau à deux dimensions de lignes et colonnes se représente par une liste de listes de longueur : M[i] est la ligne , et M[i][j] l'élément en ligne , colonne — avec et .
M = [[1, 2, 3],
[4, 5, 6]] # n = 2 lignes, p = 3 colonnes
M[1][2] # 6 : ligne 1, colonne 2
len(M) # n = 2 (nombre de lignes)
len(M[0]) # p = 3 (nombre de colonnes)
Convention à fixer une fois pour toutes : premier indice ligne, second colonne — l'ordre des mathématiques (matrices), qui n'est pas celui des coordonnées géométriques : la ligne croît vers le bas.
Pour créer une matrice nulle de , l'écriture suivante est un bogue célèbre :
M = [[0] * p] * n # FAUX : n fois LA MÊME ligne !
M[0][0] = 1
# M == [[1, 0, 0], [1, 0, 0]] : TOUTES les lignes ont changé
[ligne] * n ne copie pas la ligne : il répète fois la même liste — modifier une case les modifie « toutes » (il n'y en a qu'une). La forme correcte crée une ligne neuve à chaque tour :
M = [[0] * p for _ in range(n)] # n lignes indépendantes
([0] * p reste licite : les entiers ne sont pas modifiables, partager des est sans danger — c'est le partage de listes qui mord.)
8.2.2 Les deux parcours
Méthode : Parcours ligne par ligne, colonne par colonne
for i in range(n): # ligne par ligne (l'ordre naturel)
for j in range(p):
... M[i][j] ...
for j in range(p): # colonne par colonne
for i in range(n):
... M[i][j] ...
Les deux visitent les cases — coût , le « linéaire » des images (on dit aussi : linéaire en le nombre de pixels). Le choix de l'ordre suit le problème : sommes par ligne premier parcours ; sommes par colonne second.
def somme_matrice(M: list) -> float:
s = 0
for ligne in M:
for x in ligne:
s += x
return s
def maximum_matrice(M: list) -> float:
"""Précondition : M non vide."""
m = M[0][0]
for ligne in M:
for x in ligne:
if x > m:
m = x
return m
Quand les indices ne servent pas, le parcours par valeurs à deux étages est le plus lisible — exactement la règle du chapitre 2, doublée.