LYCÉE → PRÉPA · L29

Module L29 · Partie K · Ingénierie de l’IA

OCR : lire une image, du pixel au texte structuré.

Lire un document scanné, une plaque, un compteur, un ticket : l’OCR est un pipeline — acquisition, prétraitement (binarisation, redressement), détection des zones de texte, reconnaissance (des caractères isolés aux lignes entières par CTC ou par transformer), post-traitement (dictionnaire, modèle de langue) et compréhension du document (tableaux, formulaires, mise en page). Ce chapitre implémente les briques classiques en NumPy, explique la perte CTC et les modèles modernes (CRNN, TrOCR, LayoutLM, modèles vision-langage), et donne les métriques (CER, WER) et les articles.

Durée : 3 séances · Prérequis : L10, L14, L19, L25. Objectifs : prétraiter une image (Otsu, morphologie, redressement par Hough) ; segmenter en lignes et caractères ; comprendre CTC et l’implémenter ; évaluer par CER/WER ; choisir entre Tesseract, PaddleOCR, TrOCR, un VLM ; lire les articles.

Ce que vous saurez faire à la fin
  • Binariser (Otsu), redresser, segmenter des lignes et des caractères par projections et composantes connexes.
  • Expliquer la perte CTC (alignement sans segmentation) et calculer sa programmation dynamique.
  • Mesurer CER et WER, et lire une matrice de confusion de caractères.
  • Construire un pipeline OCR sur PC avec Tesseract/PaddleOCR et un modèle vision-langage, et les comparer honnêtement.

Fiche de cours · Définitions

Définitions

Définition (OCR, HTR, détection, reconnaissance). OCR : texte imprimé ; HTR (handwritten text recognition) : manuscrit. Détection : localiser les zones de texte (boîtes, polygones). Reconnaissance : transcrire une zone en chaîne. Compréhension de document : structure (titres, tableaux, champs de formulaire), relations clé-valeur.
Définition (binarisation, seuil d’Otsu). Transformer une image en niveaux de gris en noir/blanc. Otsu choisit le seuil qui minimise la variance intra-classe (équivalent : maximise la variance inter-classe) de l’histogramme. Seuillage adaptatif (Sauvola) : seuil local, robuste aux éclairages inégaux.
Définition (morphologie mathématique). Érosion (min sur un voisinage), dilatation (max), ouverture (érosion puis dilatation : supprime les petits objets), fermeture (dilatation puis érosion : bouche les trous). Sur des images binaires : opérations ensemblistes avec un élément structurant.
Définition (redressement, transformée de Hough). Estimer l’angle d’inclinaison (skew) : chaque point de contour vote pour les droites (ρ, θ) qui passent par lui ; les lignes de texte produisent un pic à l’angle dominant. Variante : maximiser la variance du profil de projection horizontale sur les angles candidats.
Définition (segmentation par projection, composantes connexes). Profil de projection horizontale = somme des pixels noirs par ligne : les creux séparent les lignes de texte. Composantes connexes (4- ou 8-connexité) : caractères ou fragments (les lettres accentuées, le i, sont en plusieurs composantes).
Définition (CTC). Connectionist Temporal Classification : perte pour prédire une séquence de caractères à partir d’une séquence de T trames (colonnes de l’image) sans alignement connu, via un symbole « blanc » et une fonction de réduction (fusionner les répétitions, supprimer les blancs).
Définition (CER, WER). Character Error Rate = (substitutions + insertions + suppressions)/longueur de référence, calculé par distance de Levenshtein ; WER : idem sur les mots. Peuvent dépasser 100 %.
Définition (modèles). Tesseract (LSTM, classique) ; CRNN (CNN + BiLSTM + CTC) ; PaddleOCR (détection DB + reconnaissance SVTR) ; TrOCR (encodeur ViT + décodeur texte) ; LayoutLM/Donut (compréhension de document) ; VLM généralistes (GPT-4V, Qwen-VL, PaliGemma) qui « lisent » sans pipeline explicite.

Fiche de cours · Formules

Formules à connaître

Otsu : σ²inter(t) = ω₀(t)ω₁(t)[μ₀(t) − μ₁(t)]² ; t* = argmaxt σ²inter ; ω = proportions, μ = moyennes des deux classes
Sauvola : T(x, y) = m(x, y)·[1 + k(s(x, y)/R − 1)], m et s moyenne et écart-type locaux, k ≈ 0,2–0,5, R = 128
Hough : un point (x, y) vote pour ρ = x cos θ + y sin θ, θ ∈ [−π/2, π/2] ; accumulateur (ρ, θ) ; angle de skew = θ du pic − 90°
CTC : p(y | X) = Σπ ∈ B⁻¹(y) Πt p(πt | X) ; calcul par programmation dynamique avant-arrière sur la séquence augmentée y′ = (ε, y₁, ε, y₂, …, ε) en O(T·|y|)
Récurrence CTC : αt(s) = [αt−1(s) + αt−1(s−1) + αt−1(s−2)·1y′s ≠ ε, y′s ≠ y′s−2]·pt(y′s)
Levenshtein : D[i][j] = min(D[i−1][j] + 1, D[i][j−1] + 1, D[i−1][j−1] + 1ai ≠ bj) ; CER = D[n][m]/n
Résolution nécessaire : ≈ 20–30 pixels de hauteur de x pour l’imprimé (300 dpi pour du 10 pt) ; en dessous, le CER explose
Décodage CTC glouton : argmax par trame puis B(·) ; par faisceau avec modèle de langue : score = log pCTC + α log pLM + β·|mots|

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire

Théorème 1 (Otsu : minimiser l’intra-classe = maximiser l’inter-classe). Pour un seuil t, σ²totale = σ²intra(t) + σ²inter(t), avec σ²intra = ω₀σ₀² + ω₁σ₁² et σ²inter = ω₀ω₁(μ₀ − μ₁)².
C’est la décomposition de la variance (L11) : Var(X) = E[Var(X | classe)] + Var(E[X | classe]). Le premier terme est ω₀σ₀² + ω₁σ₁². Le second : la moyenne globale est μ = ω₀μ₀ + ω₁μ₁ ; Var(E[X|classe]) = ω₀(μ₀ − μ)² + ω₁(μ₁ − μ)² ; avec μ₀ − μ = ω₁(μ₀ − μ₁) et μ₁ − μ = −ω₀(μ₀ − μ₁), on obtient ω₀ω₁²(μ₀ − μ₁)² + ω₁ω₀²(μ₀ − μ₁)² = ω₀ω₁(μ₀ − μ₁)². Comme σ²totale ne dépend pas de t, minimiser l’un revient à maximiser l’autre ; σ²inter se calcule en O(1) par seuil à partir de sommes cumulées de l’histogramme : Otsu est O(256) après l’histogramme.
Théorème 2 (CTC : la somme sur les alignements se calcule en O(T·|y|)). Le nombre d’alignements π de longueur T qui se réduisent à y est exponentiel, mais p(y | X) = Σs αT(s) pour s ∈ {|y′|, |y′| − 1}, avec α calculé par la récurrence ci-dessus.
Définissons αt(s) = probabilité totale des préfixes d’alignement de longueur t qui se réduisent au préfixe y′1..s (dans la séquence augmentée de blancs). Un alignement qui est en s à l’instant t était, à t − 1, en s (répétition du même symbole, fusionnée par B), en s − 1 (passage du blanc au caractère ou du caractère au blanc), ou en s − 2 (sauter un blanc, autorisé seulement si y′s est un caractère différent de y′s−2 — sinon deux caractères identiques consécutifs seraient fusionnés). Les trois cas sont disjoints et exhaustifs, d’où la récurrence, chaque terme multiplié par la probabilité d’émettre y′s à t. Initialisation : α₁(1) = p₁(ε), α₁(2) = p₁(y₁). Un alignement complet finit sur le dernier caractère ou sur le blanc final. Coût : T × (2|y| + 1) cellules, chacune O(1). Le gradient s’obtient par la passe arrière β symétrique (Graves et al. 2006) — c’est le même schéma que l’algorithme avant-arrière des HMM.
Théorème 3 (CER par Levenshtein est une distance et se calcule en O(nm)).
La récurrence D[i][j] considère la dernière opération d’une transformation optimale de a1..i en b1..j : suppression de ai (D[i−1][j] + 1), insertion de bj (D[i][j−1] + 1), ou substitution/correspondance (D[i−1][j−1] + 1ai≠bj) ; sous-structure optimale (L04). Propriétés de distance : symétrie (inverser les opérations), inégalité triangulaire (concaténer deux suites d’opérations). Deux lignes de mémoire suffisent. Remarque : CER n’est pas borné par 1 (insertions) et dépend de la normalisation (casse, accents, espaces) : la fixer avant toute comparaison.
Théorème 4 (un modèle de langue améliore l’OCR : argument bayésien). Décoder ŷ = argmaxy p(y | X) ∝ p(X | y)p(y) : le modèle de langue p(y) corrige les confusions visuelles (« rn » ↔ « m », « 0 » ↔ « O ») là où le modèle visuel est incertain.
Bayes (L11). Si le reconnaisseur donne p(X | « modem ») ≈ p(X | « rnodem ») (visuellement identiques), le terme p(y) tranche (« rnodem » n’existe pas). En pratique, le score de faisceau log pCTC(y|X) + α log pLM(y) réalise cette combinaison, α réglé sur un jeu de validation. Limite : sur des chaînes sans structure linguistique (numéros de série, plaques), p(y) doit être une grammaire (format) et non un modèle de langue général — sinon il « corrige » des codes valides en mots.

01 / Prétraiter

Une image de texte synthétique, Otsu, morphologie, redressement par projection

01 / Prétraiter

Segmenter : lignes par projection, caractères par composantes connexes, puis reconnaître par gabarits

Ce pipeline « classique » (années 1990) marche sur du texte propre et une police connue ; il casse dès que la police, la taille ou le fond changent. C’est exactement pourquoi la reconnaissance est passée aux modèles appris sur des lignes entières — mais le prétraitement, lui, reste utile.

02 / Apprendre

La perte CTC, implémentée : alignement sans segmentation

CTC est ce qui a rendu possible l’OCR de lignes (CRNN, Tesseract 4) et la reconnaissance vocale de bout en bout (L30) : le réseau émet une distribution par colonne d’image ou par trame audio, et la perte somme sur tous les alignements compatibles avec la transcription — aucune annotation de position n’est nécessaire.

03 / Modèles

Vue informatique : les architectures d’OCR, du CRNN aux modèles vision-langage

ModèleArchitectureForcesLimitesCoût
Tesseract 4/5LSTM + CTC sur lignes, segmentation classiqueLibre, 100+ langues, rapide CPU, sortie hOCR/positionsFragile sur photos, mises en page complexes, manuscrit~0,5 s/page CPU
CRNN (Shi 2015)CNN → BiLSTM → CTCSimple, entraînable sur ses donnéesLignes seulement ; détection à partms/ligne GPU
PaddleOCR / EasyOCRDétection (DBNet) + reconnaissance (SVTR/CRNN)Photos, scènes, multilingue, open sourceCompréhension de structure limitée~50 ms/image GPU
TrOCR (Li 2021)Encodeur ViT + décodeur texte (BART/RoBERTa) pré-entraînésManuscrit, imprimé ; SOTA sur IAM, SROIELignes ; lourd ; pas de détection~100 ms/ligne GPU
LayoutLMv3, Donut, NougatMultimodal texte+position+image ; Donut/Nougat sans OCR (image → JSON/Markdown)Formulaires, tableaux, articles scientifiquesDomaine d’entraînement ; hallucinations de champss/page GPU
VLM (GPT-4V, Claude, Qwen-VL, PaliGemma)Encodeur image + LLMLecture + raisonnement + structure en un prompt ; zero-shotCoût, latence, hallucinations sur les chiffres, pas de positions précises, données qui sortent1–10 s/page, ¢ par page

Choisir (L34) : documents propres et volumineux → Tesseract/Paddle (coût ~0) ; photos et scènes → Paddle/EasyOCR ; manuscrit → TrOCR affiné ; formulaires et tableaux avec structure → LayoutLM/Donut ou VLM ; besoin de raisonner sur le contenu → VLM, avec vérification des nombres par un OCR classique (deux systèmes qui s’accordent = confiance).

04 / Évaluer

CER, WER, matrice de confusion, et les pièges de normalisation

Sur des documents à valeur (factures, relevés), le CER moyen cache l’essentiel : une erreur sur un chiffre d’un montant est grave, une sur une lettre d’un mot ne l’est pas. Mesurer aussi l’exactitude par champ (montant exact ? date exacte ?) et le taux de documents entièrement corrects — c’est ce que le métier verra.

05 / Articles

Les articles à lire

ArticleContributionÀ retenir
Otsu, A Threshold Selection Method from Gray-Level Histograms, IEEE SMC 1979Seuil optimal par variance inter-classeThéorème 1 ; global, donc sensible à l’éclairage → Sauvola (2000)
Graves et al., Connectionist Temporal Classification, ICML 2006Perte CTC pour séquences non alignéesThéorème 2 ; base de l’OCR et de l’ASR de bout en bout
Shi et al., An End-to-End Trainable Neural Network for Image-based Sequence Recognition (CRNN), 2015 — 1507.05717CNN + BiLSTM + CTC sur lignesL’architecture de référence pendant 5 ans
Smith, An Overview of the Tesseract OCR Engine, ICDAR 2007 ; Tesseract 4 (LSTM) 2018Pipeline complet libreLire pour comprendre ce qu’un pipeline « classique » contient
Baek et al., What Is Wrong With Scene Text Recognition Model Comparisons?, ICCV 2019 — 1904.01906Comparaison honnête : mêmes données, mêmes protocolesLes écarts publiés venaient souvent des données d’entraînement, pas des modèles (L24)
Liao et al., Real-time Scene Text Detection with Differentiable Binarization (DBNet), AAAI 2020 — 1911.08947Détection de texte par segmentation + binarisation appriseDétecteur de PaddleOCR
Li et al., TrOCR, 2021 — 2109.10282Transformer image→texte pré-entraîné, sans CNN ni CTCLe pré-entraînement (synthétique + réel) fait la différence
Xu et al., LayoutLM, KDD 2020 — 1912.13318 ; Huang et al., LayoutLMv3, 2022 — 2204.08387Texte + position 2D + image pour la compréhension de documentsExtraction clé-valeur, classification de documents
Kim et al., Donut: OCR-free Document Understanding, ECCV 2022 — 2111.15664Image → JSON directementPas d’erreurs d’OCR en cascade, mais hallucinations possibles
Blecher et al., Nougat, 2023 — 2308.13418PDF scientifique → Markdown avec formulesUtile pour alimenter un RAG (L26)
Liu et al., OCRBench, 2023 — 2305.07895Benchmark des VLM sur l’OCRLes VLM lisent bien mais comptent et alignent mal (tableaux, chiffres)

TP guidé

TP — Trois OCR sur vos documents, comparés (5 h)

Exercices

Exercices auto-corrigés

Exercice 1 — Otsu et sa propriété

Réimplémentez otsu_seuil(pixels) sur des valeurs entières 0–255 (histogramme, sommes cumulées, argmax de la variance inter-classe) et vérifiez le Théorème 1 : σ²intra + σ²inter = σ²totale pour tout seuil, et le seuil sépare deux modes.

Correction
def otsu_seuil(pixels):
    h = np.bincount(pixels, minlength=256).astype(float); p = h / h.sum(); w0 = np.cumsum(p); m = np.cumsum(p * np.arange(256))
    mt = m[-1]; inter = (mt * w0 - m) ** 2 / (w0 * (1 - w0) + 1e-12)
    return int(np.argmax(inter))

Exercice 2 — Décodage CTC par faisceau (préfixes)

Implémentez ctc_faisceau(logp, largeur, blanc=0) : recherche en faisceau sur les préfixes réduits, en maintenant pour chaque préfixe deux probabilités (se terminant par blanc / par non-blanc) — l’algorithme de Graves. Vérifiez qu’il trouve la séquence de probabilité totale maximale sur un petit cas où le glouton se trompe.

Correction
def ctc_faisceau(logp, largeur=4, blanc=0):
    from collections import defaultdict
    T, C = logp.shape; P = np.exp(logp)
    faisceau = {(): (1.0, 0.0)}                     # préfixe → (p_blanc, p_nonblanc)
    for t in range(T):
        nouveau = defaultdict(lambda: [0.0, 0.0])
        for pref, (pb, pnb) in faisceau.items():
            nouveau[pref][0] += (pb + pnb) * P[t, blanc]                       # émettre un blanc
            if pref: nouveau[pref][1] += pnb * P[t, pref[-1]]                  # répéter le dernier caractère (fusionné)
            for c in range(C):
                if c == blanc: continue
                ext = pref + (c,)
                if pref and c == pref[-1]: nouveau[ext][1] += pb * P[t, c]      # même caractère : seulement après un blanc
                else: nouveau[ext][1] += (pb + pnb) * P[t, c]
        faisceau = dict(sorted(nouveau.items(), key=lambda kv: -(kv[1][0] + kv[1][1]))[:largeur])
        faisceau = {k: (v[0], v[1]) for k, v in faisceau.items()}
    return list(max(faisceau, key=lambda k: sum(faisceau[k])))

Exercices

Exercices auto-corrigés (suite)

Exercice 3 — Exactitude par champ et taux de documents parfaits

Écrivez evaluer_champs(refs, hyps) : listes de dicts {champ: valeur} ; renvoyer (exactitude par champ : dict champ → fraction exacte après normalisation espaces/casse, taux de documents entièrement corrects, CER moyen sur les valeurs concaténées).

Correction
def evaluer_champs(refs, hyps):
    norm = lambda s: re.sub(r"\s+", " ", str(s)).strip().lower()
    champs = list(refs[0]); exact = {c: np.mean([norm(r[c]) == norm(h.get(c, "")) for r, h in zip(refs, hyps)]) for c in champs}
    parfaits = np.mean([all(norm(r[c]) == norm(h.get(c, "")) for c in champs) for r, h in zip(refs, hyps)])
    cers = [levenshtein(norm("|".join(h.get(c, "") for c in champs)), norm("|".join(r[c] for c in champs))) / len("|".join(r[c] for c in champs)) for r, h in zip(refs, hyps)]
    return exact, float(parfaits), float(np.mean(cers))

Exercice 4 — Correction par grammaire de champ

Écrivez corriger_montant(s) qui applique les confusions OCR classiques dans un contexte numérique (O→0, l/I→1, S→5, B→8, « . » ou « , » décimal → « , »), et corriger_date(s) (même idée + format JJ/MM/AAAA avec validation : mois 1–12, jour 1–31). Les chaînes déjà valides ne changent pas ; une chaîne irrécupérable renvoie None.

Correction
TABLE = str.maketrans({"O": "0", "o": "0", "l": "1", "I": "1", "S": "5", "B": "8"})
def corriger_montant(s):
    t = s.strip().translate(TABLE).replace(".", ",")
    return t if re.fullmatch(r"\d+(,\d{2})?", t) else None
def corriger_date(s):
    t = s.strip().translate(TABLE); m = re.fullmatch(r"(\d{2})/(\d{2})/(\d{4})", t)
    if not m: return None
    j, mo, a = map(int, m.groups()); return t if 1 <= j <= 31 and 1 <= mo <= 12 else None

Fiche de cours · Exercices corrigés

Exercices corrigés (rédaction)

Exercice 1. Un OCR donne un CER de 2 % sur des factures. Le métier constate que 30 % des montants sont faux. Expliquer et proposer une évaluation.
Correction. Le CER moyen est dominé par le texte courant (adresses, libellés), abondant et facile ; les montants sont rares (quelques caractères par page) et difficiles (chiffres sans contexte linguistique, polices variées, alignements de colonnes). 2 % de CER global peut donc coexister avec 30 % de montants faux (exercice 3). Évaluation : exactitude par champ, taux de documents parfaits, matrice de confusion restreinte aux chiffres, CER sur les zones numériques ; et un post-traitement par grammaire (exercice 4) + vérification arithmétique (somme des lignes = total) qui détecte la plupart des erreurs restantes.
Exercice 2. Pourquoi CTC impose-t-il un blanc entre deux caractères identiques (« ll » dans « balle ») ? Que se passerait-il sans ?
Correction. La fonction de réduction B fusionne les répétitions consécutives : sans blanc, l’alignement « b a l l e » se réduirait en « bale ». Le blanc sépare : « b a l ε l e » → « balle ». C’est pourquoi la récurrence interdit le saut s−2 quand y′s = y′s−2 (Théorème 2), et pourquoi T doit être ≥ |y| + nombre de doublons consécutifs : une image trop étroite (peu de colonnes) rend certaines transcriptions impossibles — d’où le sous-échantillonnage horizontal limité (facteur 4) dans les CRNN.
Exercice 3. Un VLM lit correctement un tableau de 8 colonnes mais décale parfois les valeurs d’une colonne. Diagnostic et remède.
Correction. Un VLM produit du texte token par token à partir de patchs d’image ; il n’a pas de notion de coordonnées exactes et « devine » l’alignement des colonnes à partir de régularités apprises — il peut donc décaler des cellules de façon plausible (hallucination structurelle), ce que le CER ne mesure pas. Remèdes : (1) détecter la structure du tableau avec un modèle dédié (Table Transformer, PaddleOCR-structure) qui donne les cellules avec positions, puis reconnaître cellule par cellule ; (2) demander au VLM une sortie JSON par ligne avec l’en-tête répété, et vérifier les contraintes (types, sommes) ; (3) évaluer par cellule (exactitude de position + valeur), pas par CER ; (4) accord entre deux systèmes comme confiance, revue humaine sur les désaccords.

Vérification

Quel est l’apport principal de la perte CTC pour l’OCR de lignes ?

Deux questions supplémentaires

1. Pourquoi Otsu échoue-t-il sur un scan mal éclairé ? Le seuil est global ; un gradient d’éclairage mélange les deux modes → seuil local (Sauvola).

2. Pourquoi mesurer l’exactitude par champ en plus du CER ? Le CER est dominé par le texte facile ; les champs critiques (montants) sont rares et durs.

Référence

Les mots à retenir

MotDéfinition
Binarisation (Otsu, Sauvola)Seuil global par variance inter-classe / seuil local par moyenne et écart-type.
MorphologieÉrosion, dilatation, ouverture, fermeture sur images binaires.
SkewInclinaison du document ; Hough ou variance de projection.
CTCPerte de séquence sans alignement, avec blanc et réduction.
CER / WERTaux d’erreur par Levenshtein ; dépend de la normalisation.
CRNN / TrOCRCNN+LSTM+CTC / ViT+décodeur : reconnaissance de lignes.
LayoutLM / DonutCompréhension de documents : structure et champs.
VLMLecture et raisonnement zero-shot ; vérifier chiffres et structure.

Suite

Après l’image, le son.

La reconnaissance vocale partage avec l’OCR la perte CTC et les encodeurs-décodeurs — mais le signal est une onde. Chapitre suivant : du spectrogramme à Whisper.

← L28SommaireL30 : ASR →