Cours 4 sur 7 · AI COURSE

Computer Vision

Voir avec un réseau : convolutions, architectures CNN, transfert, détection, segmentation, Vision Transformer et apprentissage auto-supervisé.

8 chapitres9 blocs5 nouveaux
Un chapitre, plusieurs niveauxChaque chapitre empile les blocs du même sujet, du plus accessible au plus rigoureux.
BasesIntuition, calcul détaillé, premier TP.
Orienté LLMLe même sujet vu depuis les grands modèles.
RigueurÉnoncés, preuves, niveau master.
NouveauBlocs écrits pour combler ce cours.
Le Carnet décideLe planning indique, séance par séance, quels blocs lire.

Partie 1

Convolutions

L'image comme tenseur, et l'opération qui exploite sa structure locale.

Chapitre 1

L'image comme donnée

1 bloc · Nouveau

1.1Nouveaunouveau
1.1

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

Cours
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 et de largeur W est donc un tenseur de forme (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 28=256 valeurs par canal, soit 2563≈16,7 millions de couleurs. Les images médicales ou astronomiques montent à 12 ou 16 bits.

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 :

Y=0,299R+0,587G+0,114B

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
  1. Convertir en flottants dans [0,1] en divisant par 255.
  2. 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 μ=(0,485;0,456;0,406) et σ=(0,229;0,224;0,225).
  3. 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 X a pour répartition F, alors F(X) est uniforme.

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 :

S(i,j)=∑u=−11∑v=−11K(u,v)I(i+u,j+v)

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 1/9. 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 : Kx=(−101−202−101) mesure la variation horizontale, Ky=Kx⊤ la verticale. La norme Gx2+Gy2 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.

Schéma du coursUne image couleur = trois matrices empilées. PyTorch range les canaux en premier : (C, H, W).
21219887 RGB W (largeur) H shape (3, H, W) 1 pixel = 3 nombres uint8 : 0 … 255 float : 0 … 1 puis (x − μ_c) / σ_c
Mon schémaLe filtre comme pochoir lumineux : on le pose sur un patch, on additionne pixel × coefficient, on écrit le résultat, on décale d'un cran.
−10+1 −20+2 −10+1 image d'entrée Σ pixel × coef S carte de réponse
Exemple du cours

Patch 3×3 de niveaux de gris, à la frontière entre une zone sombre (à gauche) et une zone claire (à droite) :

P=(101020010102001010200)
  • Sobel horizontal : Gx=(−1)⋅10+0⋅10+1⋅200+(−2)⋅10+0+2⋅200+(−1)⋅10+0+1⋅200=−40+800=760. Fort : il y a un contour vertical.
  • Sobel vertical : Gy=∑Ky⊙P. Les lignes sont identiques, donc les termes −1,−2,−1 de la ligne du haut annulent les +1,+2,+1 de celle du bas : Gy=0. Pas de contour horizontal.
  • Flou moyen : (6⋅10+3⋅200)/9=660/9≈73,3. La transition nette devient un gris intermédiaire.
Exemple inventé

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 Ky répond fortement sur leurs bords, Kx presque pas. Additionne ensuite la réponse |Gy| le long de chaque ligne de pixels : tu obtiens un profil avec cinq pics réguliers par portée. Si la photo est penchée de 2°, les pics s'étalent ; chercher l'angle qui rend les pics les plus étroits redresse la page. Tout ça sans apprentissage. Un CNN fera mieux sur une photo floue ou froissée, mais ces filtres faits main restent utiles pour prétraiter, et pour comprendre ce que voient les premières couches d'un réseau.

TP · Filtrer une image à la main
  1. Fabrique une image synthétique 64×64 : un carré clair sur fond sombre, avec du bruit gaussien.
  2. Écris correlate2d(img, k) avec deux boucles (sans SciPy), en mode « valid » (pas de bord ajouté).
  3. Applique un flou gaussien, puis Sobel. Vérifie que la norme du gradient est maximale sur le bord du carré.
  4. 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.

Chapitre 2

La convolution

1 bloc · Bases

2.1Basesex-DLZ 28
2.1

La convolution

Prérequis : blocs MATH 3.1 et DL 10.2 · Notions : limites du MLP sur les images, corrélation croisée 1D et 2D, noyau (filtre), carte d'activation, pas (stride), remplissage (padding), taille de sortie, canaux d'entrée et de sortie, partage des poids, localité, équivariance par translation, champ récepteur, convolution 1×1

Cours
Pourquoi pas un MLP sur des images ?

Une image 224×224×3 aplatie fait 150 528 entrées. Une couche dense vers 1 000 neurones : 150 millions de poids, pour une seule couche. Pire : le MLP ignore la géométrie. Un chat en haut à gauche et le même chat en bas à droite activent des poids complètement différents ; il faudrait réapprendre le chat à chaque position.

Deux constats sur les images : (1) localité : un pixel est surtout lié à ses voisins ; (2) stationnarité : un motif (un bord, un œil) a le même sens partout. La convolution encode exactement ces deux hypothèses.

La convolution 1D

Un petit vecteur de poids w=(w0,…,wk−1), le noyau, glisse le long du signal ; à chaque position on fait un produit scalaire :

yi=∑m=0k−1wmxi+m+b

(En toute rigueur c'est une corrélation croisée ; la convolution mathématique retourne le noyau. Comme le noyau est appris, la différence est sans importance et les bibliothèques font la corrélation.) En traitement du signal, c'est un filtre à réponse impulsionnelle finie : un égaliseur est une convolution.

La convolution 2D
Yi,j=∑c=1Cin∑u=0k−1∑v=0k−1Wc,u,vXc,i+u,j+v+b

Le noyau a la forme (Cin,k,k) : il couvre tous les canaux d'entrée. Chaque noyau produit une carte d'activation (feature map) qui indique où son motif apparaît. Une couche a Cout noyaux, donc des poids de forme (Cout,Cin,k,k) et Cout biais.

paramètres=Cout⋅Cin⋅k2+Cout(indépendant de la taille de l’image !)
Pas, remplissage et taille de sortie
  • Pas (stride) s : de combien le noyau avance. s=2 divise la résolution par 2.
  • Remplissage (padding) p : des zéros ajoutés autour de l'image. Avec k=3, p=1, s=1, la taille est conservée (« same »).
nout=⌊nin+2p−ks⌋+1
Propriétés
  • Partage des poids : le même noyau sert partout. Énorme économie, et généralisation à toutes les positions.
  • Équivariance par translation : décaler l'entrée décale la sortie d'autant. Le détecteur de chat marche partout.
  • Champ récepteur : la zone de l'entrée qui influence un neurone. Une conv 3×3 voit 3×3 ; deux empilées voient 5×5 ; trois, 7×7. En profondeur, les neurones voient de plus en plus large et détectent des motifs de plus en plus grands.
  • Convolution 1×1 : un MLP appliqué à chaque pixel, qui mélange les canaux sans regarder les voisins. Sert à changer le nombre de canaux à faible coût.

Une convolution est un cas particulier de couche linéaire : une matrice creuse, dont beaucoup de coefficients sont partagés. On a donc réduit drastiquement l'espace des fonctions possibles, en échange d'un a priori juste sur les images. C'est un biais inductif.

Fin de l'aperçu

Débloquer la suite