L'image comme donnée : pixels, couleurs et filtres
Prérequis : MATH 1.1 (tenseurs), MATH 2.1 (produit scalaire) · Notions : pixel, canal, profondeur de bits, espaces couleur RVB et niveaux de gris, histogramme, normalisation par canal, filtre (kernel), flou, détection de contours (Sobel), corrélation croisée
Une image est un tenseur
Une photo numérique est une grille de pixels (picture elements). En niveaux de gris (grayscale), chaque pixel est un nombre : 0 pour noir, 255 pour blanc avec 8 bits par pixel. En couleur, chaque pixel a trois canaux (channels) rouge, vert, bleu (RGB). Une image de hauteur (H, W, 3) dans les bibliothèques d'images (PIL, OpenCV, matplotlib) et (3, H, W) dans PyTorch, qui met les canaux d'abord (channels first). Un lot (batch) d'images a la forme (B, 3, H, W).
La profondeur de bits (bit depth) fixe le nombre de niveaux : 8 bits donnent
Espaces couleur
RVB n'est qu'un repère parmi d'autres. Passer en niveaux de gris est une projection linéaire : la luminance perçue pondère le vert plus fort, car l'œil y est plus sensible :
C'est un produit scalaire entre le vecteur couleur du pixel et un vecteur de poids fixe : exactement ce que fait un neurone, sauf que ces poids-là ont été réglés par des psychophysiciens au lieu d'être appris. D'autres espaces (HSV : teinte, saturation, valeur ; YCbCr pour la compression JPEG) séparent la lumière de la couleur.
Préparer une image pour un réseau
- Convertir en flottants dans
en divisant par 255. - Normaliser par canal : soustraire la moyenne et diviser par l'écart-type de chaque canal, calculés sur le jeu d'entraînement. Les valeurs d'ImageNet, que tout le monde réutilise pour les modèles pré-entraînés, sont
et . - Redimensionner (resize) à la taille attendue, souvent 224×224, en gardant le rapport d'aspect puis en recadrant (crop) au centre.
Un modèle pré-entraîné sur des images normalisées d'une certaine façon attend exactement la même normalisation. L'oublier est le bug le plus fréquent du transfer learning (CV 4.1).
L'histogramme
L'histogramme compte combien de pixels prennent chaque valeur. Il décrit l'exposition : une photo sous-exposée a toute sa masse à gauche. L'égalisation d'histogramme (histogram equalization) applique à chaque pixel la fonction de répartition empirique, ce qui étale les valeurs sur toute la plage et augmente le contraste. C'est la même idée que la transformation par la fonction de répartition en probabilités : si
Filtrer : le geste qui mène à la convolution
Un filtre (kernel) est une petite matrice, typiquement 3×3, qu'on fait glisser sur l'image. En chaque position, on fait le produit scalaire entre le filtre et le patch de pixels sous lui :
Ce calcul s'appelle la corrélation croisée (cross-correlation). Les réseaux de neurones l'appellent « convolution » par abus de langage ; la vraie convolution retourne le filtre, ce qui ne change rien quand le filtre est appris.
- Flou moyen (box blur) : tous les coefficients valent
. Chaque pixel devient la moyenne de son voisinage : le bruit diminue, les détails aussi. - Flou gaussien : coefficients tirés d'une gaussienne 2D, plus de poids au centre. Moins d'artefacts que le flou moyen.
- Sobel :
mesure la variation horizontale, la verticale. La norme donne la force des contours. C'est une dérivée numérique centrée (MATH 6.1), lissée dans la direction perpendiculaire.
Pendant quarante ans, la vision par ordinateur a consisté à concevoir ces filtres à la main (Sobel, Canny, SIFT, HOG). Le tournant de 2012 : un CNN apprend ses filtres par descente de gradient, et ses premières couches retrouvent d'elles-mêmes des détecteurs de contours et de couleurs ressemblant à Sobel et Gabor.
Patch 3×3 de niveaux de gris, à la frontière entre une zone sombre (à gauche) et une zone claire (à droite) :
- Sobel horizontal :
. Fort : il y a un contour vertical. - Sobel vertical :
. Les lignes sont identiques, donc les termes de la ligne du haut annulent les de celle du bas : . Pas de contour horizontal. - Flou moyen :
. La transition nette devient un gris intermédiaire.
Lire une partition photographiée. Tu veux repérer les lignes de portée sur une photo de partition. Elles sont horizontales et fines : le filtre Sobel vertical
- Fabrique une image synthétique 64×64 : un carré clair sur fond sombre, avec du bruit gaussien.
- Écris
correlate2d(img, k)avec deux boucles (sans SciPy), en mode « valid » (pas de bord ajouté). - Applique un flou gaussien, puis Sobel. Vérifie que la norme du gradient est maximale sur le bord du carré.
- Compare ton résultat avec une version vectorisée à base de
np.lib.stride_tricks.sliding_window_view.
Solution commentée
import numpy as np
rng = np.random.default_rng(0)
img = np.full((64, 64), 30.0)
img[20:44, 20:44] = 200.0 # carré clair
img += rng.normal(0, 10, img.shape) # bruit
def correlate2d(img, k):
kh, kw = k.shape
H, W = img.shape[0] - kh + 1, img.shape[1] - kw + 1
out = np.zeros((H, W))
for i in range(H):
for j in range(W):
out[i, j] = np.sum(k * img[i:i + kh, j:j + kw]) # produit scalaire patch . filtre
return out
g = np.array([1, 2, 1.0]); gauss = np.outer(g, g) / 16 # flou gaussien 3x3
Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1.0]]); Ky = Kx.T
lisse = correlate2d(img, gauss)
Gx, Gy = correlate2d(lisse, Kx), correlate2d(lisse, Ky)
G = np.hypot(Gx, Gy)
print("shape :", G.shape) # (60, 60) : 2 pixels perdus par passe
# le maximum de G doit tomber sur un bord du carré (décalé de 2 pixels par les deux passes)
i, j = np.unravel_index(G.argmax(), G.shape)
bord = min(abs(i + 2 - 20), abs(i + 2 - 43), abs(j + 2 - 20), abs(j + 2 - 43))
print("max en", (i, j), "distance au bord :", bord)
assert bord <= 1
# version vectorisée
fen = np.lib.stride_tricks.sliding_window_view(lisse, (3, 3)) # (60, 60, 3, 3)
Gx_vec = np.einsum("ijkl,kl->ij", fen, Kx)
assert np.allclose(Gx_vec, Gx)
# l'exemple du cours
P = np.array([[10, 10, 200]] * 3, dtype=float)
assert np.sum(Kx * P) == 760 and np.sum(Ky * P) == 0
Chaque passe en mode « valid » enlève une couronne de 1 pixel. Les CNN ajoutent souvent un bord de zéros (padding) pour garder la taille ; c'est le paramètre padding du bloc suivant.
À retenir
- Image = tenseur
(C, H, W); on la passe en flottants puis on normalise par canal avec les statistiques du modèle utilisé. - Niveaux de gris = projection linéaire des trois canaux.
- Filtrer = produit scalaire glissant entre un petit noyau et chaque patch (corrélation croisée).
- Les filtres faits main (flou, Sobel) sont ce que les premières couches d'un CNN réapprennent.