Reconnaissance d'images : comment l'IA voit un chat
Pixels, convolutions, transfer learning, YOLO et modèles multimodaux : comprendre un réseau de vision et le faire tourner en Python.
Mon premier projet de vision par ordinateur devait trier des photos de pièces détachées. J'avais écrit des règles à la main : "si c'est gris et rond, c'est un roulement". Ça marchait sur mes dix photos de test. Ça s'effondrait dès qu'on changeait l'éclairage.
Trois jours plus tard, un modèle pré-entraîné réajusté sur 300 images faisait mieux que tout mon code. C'est la leçon centrale de ce domaine : on n'écrit plus les règles, on les fait apprendre.
La reconnaissance d'images, c'est la capacité d'un programme à identifier ce que contient une image : un objet, une personne, du texte, une anomalie. Techniquement, c'est une fonction qui prend une grille de pixels en entrée et renvoie une étiquette, une position ou une description. Depuis 2012, cette fonction est presque toujours un réseau de neurones.
Ce que voit vraiment l'ordinateur
Une photo n'est pour la machine qu'un tableau de nombres. Une image couleur de 224 × 224 pixels, c'est trois matrices de 224 × 224 (rouge, vert, bleu), chaque case contenant une valeur entre 0 et 255. Soit 150 528 nombres.
from PIL import Image
import numpy as np
img = np.array(Image.open("chat.jpg").resize((224, 224)))
print(img.shape) # (224, 224, 3)
print(img[0, 0]) # [142 118 97] -> le pixel en haut à gaucheLe problème devient évident. Un chat photographié de face ou de profil, au soleil ou dans l'ombre, produit des tableaux de nombres totalement différents. Aucune règle simple sur les valeurs brutes ne capture "chat". Il faut un système qui construise lui-même des représentations plus abstraites : des bords, puis des formes, puis des oreilles, puis un chat.
Les quatre tâches à ne pas confondre
"Reconnaissance d'images" recouvre plusieurs problèmes. Le choix du modèle dépend entièrement de la question posée.
| Tâche | Question | Sortie | Exemple de modèle |
|---|---|---|---|
| Classification | Qu'y a-t-il sur l'image ? | Une étiquette + un score | ResNet, EfficientNet, ViT |
| Détection d'objets | Quoi, et où ? | Des boîtes + étiquettes | YOLO, DETR |
| Segmentation | Quels pixels appartiennent à quoi ? | Un masque par objet | SAM, Mask R-CNN |
| OCR | Quel texte est écrit ? | Une chaîne de caractères | Tesseract, PaddleOCR |
L'OCR mérite une mention à part : appliqué à des documents entiers, il devient le cœur de l'extraction de PDF par IA open-source, où il faut aussi reconstruire tableaux et ordre de lecture.
S'y ajoutent les modèles multimodaux (Claude, GPT, Gemini) qui répondent en langage naturel à n'importe quelle question sur une image. On y reviendra : ils changent la donne pour beaucoup de projets.
Le réseau de neurones convolutif, sans les maths lourdes
Le CNN (Convolutional Neural Network) a dominé la vision pendant dix ans. Son idée clé est simple : faire glisser de petits filtres sur l'image.
Un filtre est une matrice de 3 × 3 nombres. On le pose sur un coin de l'image, on multiplie case par case, on additionne, et on obtient une valeur. On décale d'un pixel, on recommence. Le résultat est une nouvelle image, appelée carte de caractéristiques.
Image (extrait) Filtre "bord vertical" Résultat
10 10 200 -1 0 1
10 10 200 × -1 0 1 = 570 -> bord détecté
10 10 200 -1 0 1Ce filtre réagit fort là où l'intensité change brusquement de gauche à droite. Autrement dit, il détecte les bords verticaux.
Dans un CNN, personne n'écrit ces filtres. Le réseau en possède des centaines par couche, initialisés au hasard, et l'entraînement ajuste leurs valeurs. Les premières couches finissent par détecter des bords et des couleurs. Les couches du milieu combinent ces bords en textures et en motifs. Les dernières reconnaissent des parties d'objets : un œil, une roue, une poignée.
Entre les couches de convolution, le pooling réduit la taille de l'image en gardant la valeur maximale de chaque bloc 2 × 2. Le réseau perd en précision de position mais gagne en robustesse : un chat décalé de trois pixels reste un chat.
À la fin, une couche dense transforme tout ça en un vecteur de scores, un par classe possible. Une fonction softmax convertit ces scores en probabilités qui somment à 1.
Et les Vision Transformers ?
Depuis 2020, les ViT découpent l'image en carrés de 16 × 16 pixels et les traitent comme des mots dans une phrase, avec le même mécanisme d'attention que les LLMs. Sur de très gros jeux de données, ils battent les CNN. Sur un petit projet avec peu d'images, un CNN pré-entraîné reste souvent plus simple et tout aussi précis. Je commence toujours par un ResNet ou un EfficientNet avant d'aller chercher plus gros.
Comment un modèle apprend
L'entraînement suit une boucle répétée des millions de fois :
- On montre une image au réseau, il produit des probabilités.
- On compare avec la bonne réponse. L'écart s'appelle la perte (loss).
- La rétropropagation calcule comment chaque poids a contribué à l'erreur.
- On ajuste légèrement chaque poids dans la direction qui réduit l'erreur.
Entraîner un modèle depuis zéro demande des centaines de milliers d'images étiquetées et des heures de GPU. ImageNet, le jeu de données de référence, contient 1,2 million d'images réparties en 1000 classes. Personne ne refait ça pour trier des pièces détachées.
C'est là qu'intervient le transfer learning. On part d'un modèle déjà entraîné sur ImageNet. Ses couches profondes savent déjà détecter bords, textures et formes. On remplace uniquement la dernière couche par la nôtre, et on réentraîne sur quelques centaines d'images. C'est ce qui a sauvé mon projet de pièces détachées.
Classer une image en dix lignes avec PyTorch
Commençons par utiliser un modèle tel quel. Torchvision fournit des modèles pré-entraînés avec leurs transformations associées.
pip install torch torchvisionfrom torchvision.io import decode_image
from torchvision.models import resnet50, ResNet50_Weights
weights = ResNet50_Weights.DEFAULT
model = resnet50(weights=weights)
model.eval()
preprocess = weights.transforms()
img = decode_image("chat.jpg")
batch = preprocess(img).unsqueeze(0)
probs = model(batch).squeeze(0).softmax(0)
top5 = probs.topk(5)
for score, idx in zip(top5.values, top5.indices):
print(f"{weights.meta['categories'][idx]:<25} {score.item():.1%}")tabby 61.2%
tiger cat 24.8%
Egyptian cat 9.7%
lynx 0.9%
Persian cat 0.4%Trois détails comptent ici. weights.transforms() applique exactement le redimensionnement et la normalisation utilisés à l'entraînement : les oublier fait chuter la précision. unsqueeze(0) ajoute une dimension de lot, car le modèle attend toujours plusieurs images. Et model.eval() désactive les comportements propres à l'entraînement.
Entraîner sur vos propres images
Le vrai cas d'usage, c'est vos classes à vous. Organisez les images en dossiers, un par classe :
dataset/
├── train/
│ ├── roulement/
│ ├── engrenage/
│ └── vis/
└── val/
├── roulement/
├── engrenage/
└── vis/Puis on gèle le modèle pré-entraîné et on remplace sa tête :
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.models import resnet50, ResNet50_Weights
weights = ResNet50_Weights.DEFAULT
preprocess = weights.transforms()
train_ds = datasets.ImageFolder("dataset/train", transform=preprocess)
val_ds = datasets.ImageFolder("dataset/val", transform=preprocess)
train_dl = DataLoader(train_ds, batch_size=32, shuffle=True)
val_dl = DataLoader(val_ds, batch_size=32)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = resnet50(weights=weights)
# On gèle tout le réseau existant
for param in model.parameters():
param.requires_grad = False
# Nouvelle tête : autant de sorties que de classes
model.fc = nn.Linear(model.fc.in_features, len(train_ds.classes))
model = model.to(device)
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
for epoch in range(5):
model.train()
for images, labels in train_dl:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
loss = loss_fn(model(images), labels)
loss.backward()
optimizer.step()
model.eval()
correct = 0
with torch.no_grad():
for images, labels in val_dl:
preds = model(images.to(device)).argmax(1)
correct += (preds == labels.to(device)).sum().item()
print(f"epoch {epoch + 1} — précision val : {correct / len(val_ds):.1%}")
torch.save(model.state_dict(), "pieces.pt")Seule la dernière couche apprend, donc l'entraînement tourne en quelques minutes, même sur CPU. Avec 100 images par classe, j'obtiens régulièrement plus de 90 % de précision sur des objets bien distincts.
Le jeu de validation n'est pas optionnel. C'est lui qui révèle le surapprentissage : un modèle qui affiche 99 % sur l'entraînement et 60 % en validation a appris vos photos par cœur, pas le concept.
Détecter plusieurs objets avec YOLO
La classification donne une seule réponse pour toute l'image. Pour compter des voitures dans une rue ou repérer des personnes sur une caméra, il faut de la détection. YOLO (You Only Look Once) est le standard pragmatique : rapide, précis, et la librairie Ultralytics le rend trivial à utiliser.
pip install ultralyticsfrom ultralytics import YOLO
model = YOLO("yolo11n.pt") # version "nano", téléchargée au premier lancement
results = model("rue.jpg")
for box in results[0].boxes:
label = model.names[int(box.cls)]
conf = float(box.conf)
x1, y1, x2, y2 = box.xyxy[0].tolist()
print(f"{label:<10} {conf:.0%} ({x1:.0f}, {y1:.0f}) -> ({x2:.0f}, {y2:.0f})")
results[0].save("rue_annotee.jpg")car 91% (412, 288) -> (640, 410)
person 87% (102, 190) -> (168, 395)
bicycle 74% (95, 300) -> (180, 402)Le modèle nano tourne en temps réel sur un laptop. Ultralytics gère aussi l'entraînement sur vos propres classes, à partir d'images annotées avec des boîtes. L'annotation est la partie longue : comptez quelques heures avec un outil comme Label Studio pour un premier jeu de 200 images.
Reconnaissance d'images dans le navigateur
Pas besoin de serveur Python pour tout. Transformers.js fait tourner des modèles de vision directement dans le navigateur ou dans Node.js, via WebAssembly ou WebGPU. L'image ne quitte jamais la machine de l'utilisateur.
npm install @huggingface/transformersimport { pipeline } from '@huggingface/transformers'
const classifier = await pipeline(
'image-classification',
'Xenova/vit-base-patch16-224'
)
const result = await classifier('https://example.com/chat.jpg', { top_k: 3 })
console.log(result)
// [{ label: 'tabby, tabby cat', score: 0.71 }, ...]Le modèle pèse environ 90 Mo au premier chargement, puis il est mis en cache. Pour un outil interne ou une démo, c'est excellent. Pour une page publique sur mobile, ce poids pèse lourd sur le premier affichage.
Les modèles multimodaux : la solution par défaut ?
Pour beaucoup de projets, la meilleure solution en 2026 n'est plus d'entraîner quoi que ce soit. Un modèle multimodal accepte une image et une question en langage naturel. Comme pour l'intégration des LLMs dans une application, tout passe par un simple appel d'API.
import Anthropic from '@anthropic-ai/sdk'
import { readFileSync } from 'node:fs'
const client = new Anthropic()
const data = readFileSync('ticket.jpg').toString('base64')
const message = await client.messages.create({
model: 'claude-sonnet-5-5',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{ type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data } },
{ type: 'text', text: 'Extrais le magasin, la date et le total de ce ticket en JSON.' },
],
},
],
})
console.log(message.content[0].type === 'text' ? message.content[0].text : '')Ce seul appel remplace un pipeline OCR, un parseur et une logique métier. Mon avis tranché : pour un prototype, un volume modéré ou des questions ouvertes, commencez par là.
Le modèle spécialisé reprend l'avantage dans trois cas :
- Volume : classer un million d'images par jour coûte une fortune en API, et presque rien avec un ResNet sur votre serveur.
- Latence : une caméra à 30 images par seconde exige YOLO en local, pas un aller-retour réseau.
- Confidentialité : des images médicales ou des visages ne doivent pas forcément partir chez un fournisseur tiers.
| Critère | Modèle pré-entraîné | Fine-tuning | API multimodale |
|---|---|---|---|
| Données nécessaires | Aucune | 100+ images par classe | Aucune |
| Coût par image | Quasi nul | Quasi nul | Quelques dixièmes de centime |
| Classes personnalisées | Non | Oui | Oui, via le prompt |
| Latence | Millisecondes | Millisecondes | Secondes |
| Données hors de chez vous | Non | Non | Oui |
Ce qui fait échouer un projet de vision
Le modèle n'est presque jamais le problème. Les données le sont.
Un classifieur célèbre distinguait les loups des huskies avec une excellente précision. En réalité, il détectait la neige : toutes les photos de loups avaient été prises en hiver. Votre modèle apprendra le raccourci le plus facile présent dans vos images. Variez les fonds, les éclairages, les angles et les appareils.
Les biais suivent la même logique. Un modèle de reconnaissance faciale entraîné surtout sur des visages clairs se trompe davantage sur les peaux foncées. Ce n'est pas un bug du code, c'est un reflet des données.
Enfin, un visage est une donnée biométrique. En Europe, son traitement est strictement encadré, et les obligations du RGPD face aux systèmes d'IA s'appliquent dès que vous identifiez des personnes. Une détection de "personne" anonyme et une identification nominative ne relèvent pas du tout du même régime.
C'est d'ailleurs ce qui rend l'auto-hébergement intéressant. Un outil comme Immich, l'alternative self-hosted à Google Photos, fait tourner classification et reconnaissance faciale sur votre propre serveur : les mêmes techniques que celles de cet article, sans qu'une seule photo quitte la maison.
Comment une machine voit-elle un chat ? Elle ne le voit pas. Elle empile des filtres qui transforment des pixels en bords, puis en formes, puis en probabilités, et elle a ajusté ces filtres sur des millions d'exemples. La bonne nouvelle, c'est que ce travail est déjà fait : un ResNet téléchargé, un fine-tuning de cinq minutes ou un appel d'API couvrent la plupart des besoins. Le vrai savoir-faire est ailleurs, dans le choix de la tâche et la qualité des images que vous donnez à apprendre.