OCR en 2026 : pipelines classiques, VLMs et Document AI

Traduction automatique

Cet article a été traduit automatiquement depuis la version originale en anglais.

Ce guide s’adresse aux ingénieurs qui choisissent une solution d’OCR et d’extraction de documents pour la recherche, la génération augmentée par la récupération (RAG) ou des agents AI. Il explique en quoi les moteurs traditionnels et les modèles vision-langage diffèrent, comment évaluer le texte, les tableaux et les champs, et comment orienter les documents difficiles vers un autre modèle ou une relecture humaine.

Les modèles vision-langage (VLMs) gèrent la mise en page, l’écriture manuscrite, les tableaux et les images dégradées qui mettent en échec un pipeline de reconnaissance de texte simple. Les moteurs traditionnels restent compétitifs sur l’impression propre, surtout quand la latence sur CPU et le coût d’exploitation comptent. L’instantané daté ci-dessous inclut PaddleOCR-VL 1.6 et dots.mocr, avec des compromis différents en matière de matériel, de confidentialité et de format de sortie. Vérifiez chaque projet avant de faire un choix actuel.

Pour la version courte consacrée au choix de modèle, voir Best OCR Models in 2026: Classical OCR, PaddleOCR-VL, VLMs.

L’OCR fixe désormais la qualité en aval

L’OCR alimente depuis longtemps les archives, les systèmes postaux, les outils d’accessibilité et la gestion documentaire. Le RAG et les agents documentaires ont rendu ses modes de défaillance visibles à un public d’ingénieurs plus large : un modèle en aval ne peut pas retrouver le texte ou la structure de tableau que l’extraction a perdus.

À gauche, une page avec une date, un paragraphe de conditions de paiement, un tableau de trois lignes et un total de 42.50 ; à droite, le texte extrait avec la date mal lue, le paragraphe omis, les lignes du tableau fusionnées en une seule et le total lu comme 42.80 ; en dessous, le découpage, l'embedding, l'indexation, la récupération et la réponse partent du seul texte extraitÀ gauche, une page avec une date, un paragraphe de conditions de paiement, un tableau de trois lignes et un total de 42.50 ; à droite, le texte extrait avec la date mal lue, le paragraphe omis, les lignes du tableau fusionnées en une seule et le total lu comme 42.80 ; en dessous, le découpage, l'embedding, l'indexation, la récupération et la réponse partent du seul texte extrait

Un système RAG ne peut pas récupérer un texte que l’OCR a omis ou mal lu. Si l’extraction brouille un tableau, lit mal une date ou perd un paragraphe, les changements ultérieurs de découpage et d’embedding ne retrouveront pas l’information manquante. Ces erreurs peuvent masquer une clause de contrat, modifier le total d’une facture ou corrompre un dossier médical.

L’OCR fait donc partie de l’infrastructure de récupération et d’agents, au même titre que l’analyse, le découpage, l’embedding et l’indexation. Ses erreurs demandent leur propre évaluation, plutôt que d’être absorbées dans un score de bout en bout unique.

Ce que signifie l’OCR à l’ère des modèles de fondation

L’OCR convertit le texte d’une image en caractères lisibles par une machine. Document AI désigne le système plus large qui l’entoure : analyse de la mise en page, analyse des tableaux et des formules, extraction de champs, raisonnement sémantique, provenance et validation. Les auteurs de GOT-OCR 2.0 appellent « OCR-1.0 » les pipelines modulaires de détection et de reconnaissance, et « OCR-2.0 » les modèles de bout en bout. Cette étiquette ne doit pas effacer la distinction entre reconnaissance et compréhension du document.

La figure compare les deux approches et la façon dont chacune échoue : les trois étapes en série à gauche ne récupèrent jamais une ligne que la détection a manquée, la passe unique du modèle à droite écrit un total plausible qui n’est pas sur la page, et la rastérisation et l’analyse restent en dehors du modèle dans les deux cas.

Pipeline OCR-1.0 modulaire (détection avec CRAFT ou DBNet, reconnaissance avec CRNN, post-traitement) où une ligne manquée à la détection reste perdue, à côté d'une passe unique encodeur-décodeur OCR-2.0 qui écrit du Markdown avec un total de 42.80 absent de la page ; votre code rastérise avant et analyse après dans les deux casPipeline OCR-1.0 modulaire (détection avec CRAFT ou DBNet, reconnaissance avec CRNN, post-traitement) où une ligne manquée à la détection reste perdue, à côté d'une passe unique encodeur-décodeur OCR-2.0 qui écrit du Markdown avec un total de 42.80 absent de la page ; votre code rastérise avant et analyse après dans les deux cas

Le pipeline OCR traditionnel comporte trois étapes principales :

  1. Détection de texte : localiser les régions qui contiennent du texte (par ex. CRAFT, DBNet).
  2. Reconnaissance de texte : convertir les régions détectées en séquences de caractères (par ex. CRNN).
  3. Post-traitement : correction orthographique et correction par modèle de langage.

Cela fonctionne bien sur les documents propres, mais les erreurs de détection, de reconnaissance et de post-traitement peuvent se cumuler. Mesurez à la fois la précision des caractères et la précision des champs en aval, pour qu’une page lisible ne cache pas un total ou un identifiant erroné.

Certains chemins VLM de bout en bout regroupent une plus grande partie de ce pipeline dans un encodeur visuel suivi d’un décodeur de langage. Des modèles comme GOT-OCR 2.0 peuvent produire le texte et la structure ensemble, tandis que les VLMs généralistes peuvent aussi associer les champs à un schéma demandé. Par exemple, un VLM généraliste peut lire « MFG: 10/24 » sur une étiquette comme une date de fabrication plutôt qu’une date de péremption. Les compromis sont une latence et un coût GPU ou API qui dépendent de la charge de travail, et le risque d’un texte plausible mais absent de l’image.

Une réserve pratique : un modèle qui n’accepte que des images exige des pages rastérisées, alors qu’un service qui accepte les PDF peut gérer la conversion en interne. Le redressement, le redimensionnement et le nettoyage sont des expériences propres à chaque pipeline, pas des améliorations obligatoires. AWS Textract recommande de conserver les entrées prises en charge plutôt que de les convertir ou de les sous-échantillonner sans discernement. L’analyse et la validation de la sortie restent à la charge de l’application.

Métriques : mesurer ce qui compte

Chaque comparaison de ce guide utilise l’une de ces métriques. Choisissez celle qui correspond au type de sortie :

  • CER et WER pour le texte brut. Le taux d’erreur de caractères et de mots dépend de choix de normalisation comme la casse, les espaces et la ponctuation ; fixez donc le protocole de comparaison avant de comparer des modèles.
  • Correspondance exacte par champ et F1 par champ pour les formulaires et les reçus. La correspondance exacte est binaire pour un champ ; son taux est la fraction des champs évalués qui sont corrects. Rapportez séparément l’exactitude de tous les champs au niveau du document. Pour le F1 par champ, définissez l’appariement clé/valeur, la normalisation, les champs en double ou manquants et l’agrégation ; un montant correct attribué à la mauvaise ligne est une erreur.
  • TEDS pour les tableaux. La similarité fondée sur la distance d’édition d’arbres (Tree-Edit-Distance-based Similarity) compare les arbres HTML prédits et de référence, et détecte les erreurs de structure et de contenu des cellules que le CER masque.
  • Comparaison de l’ordre de lecture quand le consommateur a besoin d’une séquence linéaire. Comparez directement les blocs ou les segments ordonnés, ou utilisez une métrique qui tient compte de l’ordre ; OmniDocBench évalue l’ordre de lecture séparément. Des caractères et des cellules corrects n’établissent pas l’ordre d’une page à plusieurs colonnes.
  • ANLS pour le VQA documentaire. L’Average Normalized Levenshtein Similarity note les réponses par rapport aux références acceptées. La définition originale de ST-VQA attribue 1 − normalized_distance uniquement quand la distance est strictement inférieure à 0,5, et zéro sinon ; elle retient le meilleur score parmi les références acceptées et fait la moyenne sur les questions. DocVQA adopte l’ANLS. Fixez la casse, les espaces et la normalisation de la distance de l’évaluateur quand vous reproduisez un score.

Le CER et le WER comptent les substitutions, suppressions et insertions, divisées par le nombre de caractères ou de mots de la référence. Ils peuvent dépasser un quand les insertions dominent. Indiquez si l’agrégation additionne les erreurs et les longueurs de référence du corpus, ou fait la moyenne des scores par document. Conservez la page, le bloc, la boîte englobante, le texte d’origine et l’historique de normalisation, afin de remonter d’un champ erroné jusqu’à l’image.

Pour la détection de texte, l’intersection sur l’union (IoU) divise l’aire de chevauchement d’une boîte prédite et d’une boîte de référence par l’aire totale couverte par au moins l’une des deux. Le seuil d’IoU détermine si une boîte détectée correspond à la référence avant le calcul de F1.

Le tableau associe chaque tâche à sa métrique et à l’erreur qui rend le plus souvent un score trompeur :

TâcheMétriqueErreur à éviter
Extraction de texte brutCER, WERComparer des scores calculés avec des normalisations différentes
Écriture manuscriteCERAppliquer à des pages manuscrites les cibles de CER du texte imprimé
Détection de texteF1 à un seuil d’IoURapporter un F1 sans le seuil d’IoU utilisé
Formulaires et reçusCorrespondance exacte par champ, F1Compter comme juste une bonne valeur placée dans le mauvais champ ou la mauvaise ligne
TableauxTEDSJuger les tableaux au CER, qui ignore la structure des cellules
VQA documentaireANLSModifier le seuil de 0,5 ou la normalisation des réponses
Conversion de page complèteDistance d’édition + ordre de lectureRapporter la précision du texte sans vérifier l’ordre des blocs

Les langues changent aussi le choix. Le chinois et le japonais s’écrivent sans espaces entre les mots, donc le WER dépend d’un segmenteur de mots ; utilisez le CER pour ces écritures. Incluez dans le jeu de test annoté des pages qui mélangent plusieurs écritures quand elles font partie de votre charge de travail. Une fiche de modèle qui annonce 100 langues ne montre la qualité d’aucune d’entre elles ; placez donc chaque langue cible dans le jeu de test annoté.

Pour les implémentations : jiwer gère le CER et le WER sans configuration, et des implémentations de TEDS se trouvent dans le dépôt OmniDocBench.

Ce que les benchmarks OCR mesurent et ce qu’ils manquent

Des datasets publics appliquent ces métriques à des types de documents précis. Les tailles et les métriques des datasets décrivent la version nommée du dataset ; consultez le classement actuel de chaque projet pour les scores des modèles. La colonne « Teste » indique à quels de vos documents chaque dataset ressemble.

DatasetTesteTaille du testLanguesMétrique principale
FUNSDAssociation clé-valeur sur des formulaires scannés bruités50 docsAnglaisF1
SROIEQuatre champs de reçu (société, date, adresse, total)400 images de testAnglaisF1
CORDLignes d’articles de reçus avec étiquettes hiérarchiques100 reçusIndonésienF1
IAMLignes manuscrites en anglaisSelon le découpageAnglaisCER
OCRBench v2Lecture et raisonnement sur le texte par des VLMs, sur 23 tâches10 000 paires QAEN + CNScore /100
OmniDocBench v1.6Analyse de page complète : texte, tableaux, formules, ordre de lecture1 651 pagesEN + CNComposite

Le nombre de lignes de IAM dépend du découpage par scripteur et du protocole de reconnaissance choisis ; aucun nombre unique de lignes de test n’est supposé ici.

Instantané OmniDocBench v1.6

Le tableau ci-dessous est un instantané versionné : OmniDocBench v1.6_full, README officiel au commit 09ba2b606662695b16aafe5f5e36b7ef020e11a8, consulté le 2026-08-09.

ModèleTailleGlobal ↑Édition du texte ↓TEDS tableaux ↑
GLM-OCR0.9B95.220.04492.83
PaddleOCR-VL-1.50.9B94.930.03891.67
Gemini 3 Flash—92.620.06689.29
dots.ocr3B90.770.04887.18

Le README figé indique PaddleOCR-VL 1.5. PaddleOCR annonce 96,3 pour la version 1.6 sur la même version du benchmark ; ce chiffre provient de l’exécution du fournisseur lui-même et ne fait pas partie de ce tableau.

La conclusion se limite à cette version d’OmniDocBench : la taille du modèle seule ne prédit pas le score d’analyse de documents. Les versions ultérieures de l’évaluateur changent l’appariement ; comparez donc les scores uniquement sous un seul évaluateur figé. Le score composite inclut la distance d’édition du texte, le TEDS des tableaux et Character Detection Matching (CDM) pour les formules. CDM effectue le rendu des formules prédites et de référence, apparie leurs caractères et vérifie leurs positions relatives avant de noter les correspondances avec F1. Il compare les formules rendues plutôt que les chaînes LaTeX brutes. L’ordre de lecture demande son propre résultat.

L’écart « benchmark contre arène »

Les classements de benchmarks automatisés peuvent contredire la préférence humaine.

Dans l’OCR Arena, les utilisateurs votent à l’aveugle sur des sorties comparées deux à deux. Le 2026-08-09, OCR Arena classait Gemini 3 Flash au-dessus de GLM-OCR, alors que le tableau OmniDocBench ci-dessus donne un meilleur score à GLM-OCR (95,22 contre 92,62). L’ordre en direct de l’Arena change à mesure que de nouveaux duels arrivent ; ce n’est donc pas un instantané historique reproductible d’un benchmark. Ne combinez pas les deux classements en un seul score.

La figure montre le désaccord : GLM-OCR et Gemini 3 Flash échangent leurs places entre les deux classements. En dessous figurent les pages que chacun utilise, qui juge, et ce que l’on obtient.

Scores Overall d'OmniDocBench v1.6 sur une échelle de 90 à 96 (GLM-OCR 95.22, PaddleOCR-VL-1.5 94.93, Gemini 3 Flash 92.62, dots.ocr 90.77) à côté de l'ordre de l'OCR Arena, avec Gemini 3 Flash au-dessus de GLM-OCR, reliés par des lignes qui se croisent ; en dessous, OmniDocBench utilise 1 651 pages sélectionnées, des métriques par rapport à un texte de référence et un score fixe par version, tandis qu'OCR Arena utilise les pages de chaque nouveau duel, des votes humains à l'aveugle et un classement en directScores Overall d'OmniDocBench v1.6 sur une échelle de 90 à 96 (GLM-OCR 95.22, PaddleOCR-VL-1.5 94.93, Gemini 3 Flash 92.62, dots.ocr 90.77) à côté de l'ordre de l'OCR Arena, avec Gemini 3 Flash au-dessus de GLM-OCR, reliés par des lignes qui se croisent ; en dessous, OmniDocBench utilise 1 651 pages sélectionnées, des métriques par rapport à un texte de référence et un score fixe par version, tandis qu'OCR Arena utilise les pages de chaque nouveau duel, des votes humains à l'aveugle et un classement en direct

Les causes probables incluent la composition des documents, le format de sortie, la couverture des langues et les critères des juges. Les chiffres publiés servent au tri préalable, mais le choix final exige un jeu de test réservé issu de la charge de travail cible.

Moteurs OCR traditionnels : toujours pertinents

Les moteurs traditionnels sont de bonnes bases de comparaison, car ils peuvent tourner en local sur CPU. Leur latence et leur précision dépendent du modèle choisi, de la résolution de page, de la langue, du prétraitement et du matériel ; évaluez-les donc sur les mêmes pages annotées que celles de l’évaluation des VLMs.

MoteurDéploiementBase de comparaison utile pour
Tesseract 5.5CPU localTexte imprimé propre et écritures établies
EasyOCRPyTorch local sur CPU ou GPUPrototypes et texte de scène
PaddleOCR 3.xCPU et GPU locaux, et variantes mobilesOCR multilingue et chaînes d’outils de déploiement

Tesseract pour l’impression propre

Tesseract (v5.5.x, Apache 2.0) est un moteur mature, principalement CPU, avec plus de 100 packs de langues. La précision sur l’impression propre peut être élevée après une rastérisation et un prétraitement adaptés, mais l’écriture manuscrite, le texte de scène et les mises en page complexes demandent des tests séparés. Son principal avantage est un déploiement local léger sur CPU.

Réglez le mode de segmentation de page (--psm) selon le type de document ; un mauvais mode perd des colonnes ou fusionne des lignes. L’extrait nécessite pip install pytesseract pillow, le binaire Tesseract avec les données de langue anglaise, et un fichier local receipt.jpg.

import pytesseract
from PIL import Image

# --psm 6: treat the image as one uniform block of text, which suits a receipt
text = pytesseract.image_to_string(Image.open("receipt.jpg"), lang="eng", config="--psm 6")

EasyOCR

EasyOCR associe un détecteur CRAFT à un modèle de reconnaissance CRNN. Avec l’accélération GPU complète de PyTorch, c’est une option rapide pour le prototypage et le texte de scène. Il ne renvoie que des lignes et des boîtes, sans mise en page ni structure de tableau.

L’extrait nécessite pip install easyocr, PyTorch, les poids de modèle téléchargés par EasyOCR et un fichier local receipt.jpg.

import easyocr

# Three lines for complete OCR
reader = easyocr.Reader(["en"])
result = reader.readtext("receipt.jpg")
# Returns: [(bbox, text, confidence), ...]

PaddleOCR 3

PaddleOCR 3 regroupe des pipelines maintenus d’OCR, d’analyse de documents et de déploiement. Le démarrage rapide actuel utilise l’API predict() et des réglages d’orientation explicites. Figez la version du paquet paddleocr et du pipeline choisi, car les exemples 2.x qui utilisent .ocr(..., cls=True) ne correspondent pas à l’API 3.x.

Il nécessite le runtime PaddlePaddle, ce qui ajoute un second framework à une pile PyTorch. La reconnaissance de texte de PaddleOCR est un pipeline différent de PaddleOCR-VL, l’analyseur de documents de la section suivante.

L’extrait nécessite pip install "paddleocr>=3,<4", un runtime PaddlePaddle compatible, les poids de modèle téléchargés et un fichier local receipt.jpg.

from paddleocr import PaddleOCR

ocr = PaddleOCR(
    use_doc_orientation_classify=False,
    use_doc_unwarping=False,
    use_textline_orientation=False,
)

for result in ocr.predict("receipt.jpg"):
    result.print()
    result.save_to_json("output")

Chaque résultat contient rec_texts, rec_scores et rec_boxes. La section sur le routage utilise les deux derniers pour noter une page.

Options de VLMs spécialisés et généralistes

Les reçus de travers, les étiquettes de produits inclinées, l’écriture manuscrite et les mises en page denses sont les cas où les VLMs spécialisés ou généralistes valent la peine d’être testés face aux moteurs traditionnels.

La figure montre ce que chaque famille de modèles renvoie pour la même page, son principal compromis, et où elle peut s’exécuter. Le mode de déploiement dépend du produit ; la figure le liste donc séparément de l’étendue des tâches.

Trois cartes classées par étendue des tâches : les moteurs traditionnels renvoient des lignes de texte avec des cadres et des scores, les modèles de documents spécialisés renvoient du Markdown avec des tableaux et l'ordre de lecture, et les VLM généralistes renvoient des champs dans votre schéma ; chaque carte liste ses compromis et où elle peut s'exécuter, du CPU local à l'API hébergée ou aux poids ouvertsTrois cartes classées par étendue des tâches : les moteurs traditionnels renvoient des lignes de texte avec des cadres et des scores, les modèles de documents spécialisés renvoient du Markdown avec des tableaux et l'ordre de lecture, et les VLM généralistes renvoient des champs dans votre schéma ; chaque carte liste ses compromis et où elle peut s'exécuter, du CPU local à l'API hébergée ou aux poids ouverts

Modèles de documents spécialisés

Les modèles de cette section ont été vérifiés le 2026-10-02. La liste n’est pas un classement actuel ; elle indique sur quel problème de document chaque modèle vaut la peine d’être essayé.

ModèleSortieOù il tourneÀ essayer quand
PaddleOCR-VL 1.6Analyse de la mise en page, puis un VLM de 0.9B lit chaque régionAuto-hébergéPages multilingues avec mise en page ; le fournisseur annonce 109 langues
GOT-OCR 2.0Texte brut, Markdown, LaTeXAuto-hébergé, 580M de paramètresFormules et texte mixte sur une même page
dots.mocrMise en page et texte ; graphiques et figures en code SVGAuto-hébergé, 3B de paramètresPages avec des graphiques ou des diagrammes dont vous avez besoin sous forme structurée
DeepSeek-OCR2Markdown avec boîtes de repérageAuto-hébergé, vLLM ou TransformersLongs documents à fort volume ; une page utilise de 256 à 1 120 tokens visuels
Mistral OCR 4.1Markdown, boîtes de paragraphes, étiquettes de blocs, confiance par blocAPI hébergée (mistral-ocr-4-1)Vous voulez une API et un score de confiance par bloc
Granite-Docling 258MDocTags, convertis en DoclingDocumentAuto-hébergé, dans DoclingUn petit modèle local pour les conversions Docling
olmOCRTexte brut ou Markdown dans l’ordre de lectureAuto-hébergé, VLM de 7B sur GPULinéariser des PDF en texte pour le RAG ou des données d’entraînement

Considérez les chiffres de débit et de mémoire comme propres au matériel et au dataset ; mesurez la mémoire de pointe avec votre runtime, votre précision, la taille de vos images et votre limite de sortie. Calibrez la confiance par bloc de Mistral par rapport à l’exactitude des champs avant d’accepter des valeurs automatiquement. Figez une version de modèle explicite pour les comparaisons, car un alias latest peut changer.

VLMs de pointe

Les VLMs généralistes sont une autre option quand la tâche combine extraction et raisonnement visuel ou sémantique. Ces candidats ont été vérifiés le 2026-10-02 et sont utilisés dans l’exemple de passerelle ci-dessous. Ils forment une courte liste de départ, pas un classement OCR :

  • Gemini 3.8 Flash (Google) : un modèle multimodal en disponibilité générale, avec entrée image et structured outputs. Utilisez son identifiant stable au début d’une nouvelle comparaison.
  • Claude Sonnet 5 (Anthropic) : une génération de Sonnet sortie le 2026-06-30, pour une comparaison hébergée. Testez la fidélité de transcription et l’extraction de champs sur vos propres documents ; des progrès de raisonnement général n’établissent pas la précision OCR.
  • Qwen3.8-27B (Alibaba) : un modèle vision-langage à poids ouverts sous Apache 2.0, qui peut être testé via une passerelle ou auto-hébergé. Sa taille de 27B en fait un choix de déploiement différent du Qwen3-VL 8B précédent, qui reste une base de comparaison plus petite utile quand la mémoire est limitée.

Ajoutez toute nouvelle version à l’ensemble de test. Ne l’utilisez en production qu’après qu’elle a passé la même comparaison : exactitude des champs, abstention, latence et coût par document accepté, sous le même protocole.

Frameworks de conversion de documents

Un pipeline RAG a généralement besoin d’un document entier converti en Markdown ou en JSON, pas de lignes OCR brutes. Les convertisseurs de documents s’en chargent. Ils analysent le fichier, exécutent un modèle de mise en page et appellent un moteur OCR pour le texte de chaque région. Certains peuvent à la place envoyer chaque page à un VLM qui lit ensemble la mise en page et le texte.

FrameworkCe qu’il produitÀ utiliser quand
DoclingMarkdown, HTML ou JSON sans perte avec tableaux et coordonnées, à partir de PDF, DOCX, PPTX, XLSX, HTML et imagesVous voulez un seul convertisseur pour des formats mixtes et choisir le moteur OCR qu’il utilise
MarkerMarkdown, JSON, HTML ou chunks, à partir de PDF, d’images et de fichiers OfficeVous voulez du Markdown de haute qualité à partir de PDF
MinerUMarkdown et JSON structuré, à partir de PDF, d’images et de fichiers OfficeArticles scientifiques et techniques avec formules et tableaux

Vérifiez les licences avant d’en livrer un dans un produit. Docling est sous licence MIT. Le code de Marker est sous Apache 2.0, mais ses poids de modèle utilisent une licence OpenRAIL-M modifiée qui limite l’usage commercial. La licence de MinerU ajoute des conditions à Apache 2.0 : une licence commerciale distincte au-delà de seuils d’utilisateurs et de revenus, et une attribution pour les services en ligne.

La figure montre ce qu’un convertisseur enveloppe. Docling garde l’entrée et la sortie identiques, et ses options de pipeline choisissent comment le texte est lu : son modèle de mise en page avec Tesseract ou EasyOCR, ou le VLM Granite-Docling de bout en bout.

Un PDF ou un scan devient des images de pages ; les options de pipeline de Docling choisissent le pipeline standard, où un modèle de mise en page repère les régions de texte, de tableau et de figure et où Tesseract ou EasyOCR lit chaque région, ou le pipeline VLM, où Granite-Docling 258M lit la mise en page et le texte en une seule passe et écrit du DocTags ; les deux produisent du Markdown, du HTML ou du JSON avec tableaux et coordonnéesUn PDF ou un scan devient des images de pages ; les options de pipeline de Docling choisissent le pipeline standard, où un modèle de mise en page repère les régions de texte, de tableau et de figure et où Tesseract ou EasyOCR lit chaque région, ou le pipeline VLM, où Granite-Docling 258M lit la mise en page et le texte en une seule passe et écrit du DocTags ; les deux produisent du Markdown, du HTML ou du JSON avec tableaux et coordonnées

La vitesse et la qualité de conversion de Docling dépendent donc du pipeline que vous sélectionnez. Le pipeline VLM de Docling doit être sélectionné explicitement ; un convertisseur par défaut ne prouve pas que Granite-Docling a tourné. La politique de nouvelle tentative relève toujours de l’application ou du service environnant, et le routage a toujours besoin de ses propres annotations de qualité et de ses propres seuils.

Choisir un chemin d’extraction

La carte de décision remet les sections précédentes en ordre : chaque question envoie un document vers le chemin le moins cher capable de produire ce dont vous avez besoin.

Carte de décision : un PDF avec une couche de texte exploitable va vers l'extraction de la couche de texte ; une impression propre avec une mise en page simple va vers un moteur traditionnel ; les pages qui demandent des tableaux, des formules ou un ordre de lecture vont vers un analyseur ou un convertisseur spécialisé ; les champs de schéma ou les réponses sur la page vont vers un VLM généraliste ; tout le reste va vers un modèle spécialisé ; chaque chemin passe ensuite par le même test étiquetéCarte de décision : un PDF avec une couche de texte exploitable va vers l'extraction de la couche de texte ; une impression propre avec une mise en page simple va vers un moteur traditionnel ; les pages qui demandent des tableaux, des formules ou un ordre de lecture vont vers un analyseur ou un convertisseur spécialisé ; les champs de schéma ou les réponses sur la page vont vers un VLM généraliste ; tout le reste va vers un modèle spécialisé ; chaque chemin passe ensuite par le même test étiqueté

Chaque feuille passe quand même par le même test annoté. La carte vous donne un premier candidat, pas un choix final.

Tester des VLMs avec OpenRouter

OpenRouter fournit une passerelle compatible OpenAI vers des modèles de plusieurs fournisseurs. Les identifiants de modèles et les fonctionnalités de requête prises en charge changent ; vérifiez-les dans le catalogue actuel de la passerelle avant d’exécuter l’exemple.

L’extrait nécessite pip install openai, une OPENROUTER_API_KEY, un accès réseau, un fichier local receipt.jpg et des identifiants de modèles toujours pris en charge par OpenRouter.

import base64
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)

def extract_text(
    image_path: str,
    model: str,
    prompt: str = "Extract all text from this image, preserving layout as markdown.",
    **request_args,
) -> str:
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()

    response = client.chat.completions.create(
        model=model,
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
            ],
        }],
        max_tokens=4096,
        **request_args,
    )
    choice = response.choices[0]
    if choice.finish_reason != "stop":
        raise RuntimeError(f"{model} did not finish: {choice.finish_reason}")
    if not choice.message.content:
        raise RuntimeError(f"{model} returned no text (finish_reason={choice.finish_reason})")
    return choice.message.content

# Compare models by changing one string
models = [
    "google/gemini-3.8-flash",
    "anthropic/claude-sonnet-5",
    "qwen/qwen3.8-27b",
]
for model in models:
    print(f"\n--- {model} ---\n{extract_text('receipt.jpg', model)[:200]}...")

Le catalogue de modèles OpenRouter listait google/gemini-3.8-flash, anthropic/claude-sonnet-5 et qwen/qwen3.8-27b avec entrée image et paramètres de structured output le 2026-10-02. La prise en charge dans le catalogue ne prouve pas que cette requête exacte réussit sur chaque point de terminaison routé.

Pour l’extraction structurée, utilisez response_format avec un JSON Schema quand le modèle choisi et la passerelle le prennent en charge. Cela peut rendre la réponse analysable ; cela ne valide pas les valeurs extraites par rapport à l’image. Avec OpenRouter, provider.require_parameters fait échouer la requête quand aucun point de terminaison routé ne prend en charge tous les paramètres demandés, au lieu de se rabattre sur un point de terminaison qui ne peut pas respecter le schéma.

L’extrait réutilise extract_text du bloc précédent et transmet le schéma par ses arguments de requête supplémentaires. Il nécessite un modèle qui prend en charge JSON Schema.

import json

nullable = {"type": ["string", "null"]}
receipt_schema = {
    "type": "object",
    "properties": {
        "vendor": nullable,
        "date": nullable,
        "items": {
            "type": ["array", "null"],
            "items": {
                "type": "object",
                "properties": {"description": nullable, "amount": nullable},
                "required": ["description", "amount"],
                "additionalProperties": False,
            },
        },
        "total": nullable,
    },
    "required": ["vendor", "date", "items", "total"],
    "additionalProperties": False,
}
prompt = (
    "Extract only visible receipt fields. Copy amounts as source strings. "
    "Use null for absent or unreadable values; do not infer them. "
    "Use null for an unreadable item list, and [] only when no items are present."
)

receipt = json.loads(extract_text(
    "receipt.jpg",
    "google/gemini-3.8-flash",
    prompt=prompt,
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "receipt", "strict": True, "schema": receipt_schema},
    },
    extra_body={"provider": {"require_parameters": True}},
))

Le mode strict de style OpenAI exige que chaque propriété figure dans required et additionalProperties: false ; une valeur qui peut manquer est donc typée comme nullable au lieu d’être omise. Le prompt et les types nullables permettent ensemble au modèle de renvoyer null plutôt que d’inventer une valeur.

Conservez les chaînes de montants copiées à côté des valeurs normalisées. Analysez les montants en aval avec une arithmétique décimale ou en entiers d’unités mineures, sous une devise et des paramètres régionaux explicites ; la validité du schéma ne valide pas le total d’un paiement. Orientez les champs critiques nuls vers la relecture.

Déployer l’OCR en production

Une architecture par paliers peut séparer le prétraitement sur CPU de l’inférence sur GPU ou API, et réserver les chemins coûteux aux documents qui en ont besoin. La figure montre les paliers par ordre de coût, le contrôle qui accepte une page à chaque palier, et l’échec qui l’envoie vers le suivant.

Quatre paliers empilés par coût : un PDF commence à la couche de texte intégrée et un scan ou une photo à un moteur traditionnel ; chaque palier accepte une page quand son contrôle réussit et l'envoie plus bas quand il échoue, via un modèle plus puissant jusqu'à la relecture humaine ; les pages acceptées de chaque palier vont vers le structured outputQuatre paliers empilés par coût : un PDF commence à la couche de texte intégrée et un scan ou une photo à un moteur traditionnel ; chaque palier accepte une page quand son contrôle réussit et l'envoie plus bas quand il échoue, via un modèle plus puissant jusqu'à la relecture humaine ; les pages acceptées de chaque palier vont vers le structured output

Le schéma de repli par paliers

Commencez par le chemin le moins cher qui atteint l’objectif de qualité, puis calibrez le routage sur des pages annotées :

  1. Vérifier le texte intégré (palier 0). Pour les PDF, inspectez la couche de texte avec PyMuPDF ou pdfplumber avant la rastérisation, mais validez que la couche est complète.
  1. Essayer avec un modèle rapide. Utilisez un moteur traditionnel pour les classes de documents sur lesquelles il atteint l’objectif.
  2. Évaluer une confiance calibrée. Combinez la confiance du modèle avec la classe de document, la criticité du champ et les règles de validation.
  3. Passer à un modèle plus puissant. Orientez les pages incertaines vers un VLM spécialisé ou généraliste.
  4. Faire remonter les échecs à haut risque vers un humain. La relecture humaine est un palier distinct pour les valeurs dont le coût d’une erreur dépasse le bénéfice de l’automatisation.

Note sur la confiance : les probabilités brutes de caractères ne sont pas automatiquement calibrées sur l’exactitude des champs. La fonction pondérée par la surface ci-dessous est une base pour l’agrégation au niveau de la page, pas un routeur universel. Calibrez-la sur des pages annotées, et donnez aux champs critiques leurs propres règles, car une moyenne de page peut masquer un identifiant ou un total erroné.

def area_weighted_confidence(page):
    """Compute area-weighted confidence from one PaddleOCR 3.x result."""
    total_area, weighted_sum = 0, 0
    for (x_min, y_min, x_max, y_max), score in zip(
        page["rec_boxes"], page["rec_scores"]
    ):
        width = int(x_max) - int(x_min)
        height = int(y_max) - int(y_min)
        area = width * height
        weighted_sum += score * area
        total_area += area
    return weighted_sum / total_area if total_area > 0 else 0

assert area_weighted_confidence({
    "rec_boxes": [[0, 0, 300, 300]], "rec_scores": [0.5]
}) == 0.5

Pour fixer le seuil, annotez un ensemble de pages d’une même classe de documents et notez si tous les champs critiques de chaque page sont corrects. Choisissez le seuil de confiance le plus bas pour lequel les pages acceptées atteignent votre objectif d’erreur sur les champs critiques, et orientez les pages situées en dessous vers le palier suivant. La fonction ci-dessous fait cela pour une classe de documents.

def pick_threshold(scores, correct, max_error):
    """Lowest threshold whose accepted pages meet the error target, or None."""
    for threshold in sorted(set(scores)):
        accepted = [ok for s, ok in zip(scores, correct) if s >= threshold]
        if accepted and accepted.count(False) / len(accepted) <= max_error:
            return threshold
    return None  # no threshold meets the target: send every page to the next tier

scores = [0.95, 0.91, 0.88, 0.80, 0.72, 0.60]
correct = [True, True, True, False, True, False]  # all critical fields right?
assert pick_threshold(scores, correct, max_error=0.0) == 0.88
assert pick_threshold(scores, correct, max_error=0.25) == 0.72

La figure place les six pages étiquetées des asserts sur l’échelle de confiance et marque le seuil que choisit chaque objectif d’erreur.

Six pages étiquetées avec une confiance de 0.95, 0.91, 0.88, 0.80, 0.72 et 0.60, dont 0.80 et 0.60 sont erronées ; un objectif d'erreur de 0 choisit le seuil 0.88, accepte trois pages et en envoie trois au palier suivant ; un objectif de 25 % choisit 0.72, accepte cinq pages dont la page erronée à 0.80, et en envoie une au palier suivantSix pages étiquetées avec une confiance de 0.95, 0.91, 0.88, 0.80, 0.72 et 0.60, dont 0.80 et 0.60 sont erronées ; un objectif d'erreur de 0 choisit le seuil 0.88, accepte trois pages et en envoie trois au palier suivant ; un objectif de 25 % choisit 0.72, accepte cinq pages dont la page erronée à 0.80, et en envoie une au palier suivant

Un objectif d’erreur plus strict relève le seuil et envoie plus de pages vers le modèle plus puissant. Répétez la calibration pour chaque classe de documents, car le même score peut correspondre à des précisions différentes sur des reçus et sur des contrats.

Modèles de service

La façon d’exécuter chaque palier dépend de la manière dont les pages arrivent :

  • Traitements par lots (conversion d’archives, rapports mensuels) : lisez les pages depuis une file d’attente et exécutez-les sur des workers GPU qui descendent à zéro entre deux traitements.
  • Requêtes interactives : gardez un GPU chaud ou utilisez une API hébergée, car un démarrage à froid du modèle ajoute son temps de chargement à l’attente de l’utilisateur.
  • Rastérisation et nettoyage d’image : exécutez-les sur des workers CPU dont la mise à l’échelle est indépendante de celle de l’inférence GPU.
  • VLMs à poids ouverts : servez-les avec vLLM ou SGLang, qui regroupent en lots les requêtes concurrentes sur un seul GPU.

Mesurer les résultats

Pour chaque paire moteur/document prévue, conservez un résultat avec un statut de réussite, d’erreur ou d’omission. Les sorties en échec, vides et tronquées restent dans le dénominateur des documents tentés, avec le coût dépensé. Rapportez le taux d’achèvement, les erreurs sur les champs critiques parmi les acceptations automatiques, la fraction relue, la latence de bout en bout p50/p95 et le coût par document traité avec succès. Mesurez la latence par page avec la résolution, la taille de lot, le matériel ou la région du fournisseur et la longueur de sortie réels. Gardez le prétraitement et les nouvelles tentatives dans le total ; un temps mesuré sur le modèle seul ne permet pas de chiffrer une page réussie. Réutilisez les objets convertisseurs pour les temps à chaud et enregistrez l’initialisation séparément. Le dépôt OCR Gauntlet enregistre les résultats dans ce format ; la dernière section montre comment l’exécuter.

Analyse des coûts à grande échelle

Il n’existe pas de seuil de rentabilité universel, en volume de pages, entre une API et l’auto-hébergement. Construisez la comparaison à partir de la même charge de travail :

Poste de coûtChemin APIChemin auto-hébergé
InférencePrix actuel à la page ou au tokenHeures de GPU au débit mesuré en pages/heure
Capacité inutiliséeGénéralement absorbée par le fournisseurTaux d’utilisation et marge de capacité
IngénierieIntégration et supervision du fournisseurDéploiement, mises à jour, observabilité et astreinte
Gestion des donnéesTransfert, conservation et conditions régionalesStockage, réseau et contrôles de conformité
Défauts de qualitéNouvelles tentatives et relecture humaineNouvelles tentatives et relecture humaine

Utilisez une formule commune : monthly pages × cost per successful page + review cost + fixed operating cost. Une « page réussie » doit satisfaire les mêmes critères de texte, de tableaux et de champs sur les deux chemins. Les prix des fournisseurs et les locations de GPU changent trop vite pour figurer ici comme estimation d’achat durable.

Gestion des erreurs : le problème des hallucinations

Les erreurs des VLMs peuvent être plausibles dans le contexte et fausses dans les faits. Le total d’un reçu, « $42.50 », peut revenir comme « $42.80 » : un montant valide qu’aucun correcteur orthographique ne signale.

Exemple d’échec synthétique : l’image d’un reçu montre trois lignes, 12.00, 18.50 et 12.00, et un total de 42.50. Un VLM lit la deuxième ligne comme 18.80 et le total comme 42.80. Les lignes s’additionnent toujours au total, donc l’arithmétique interne passe, alors que les deux valeurs sont fausses. C’est pourquoi la validation exige des annotations ancrées dans l’image ou une voie de relecture indépendante, et pas seulement des contrôles de cohérence.

La figure exécute les trois contrôles sur ce reçu : le contrôle de schéma et le contrôle de somme réussissent, et seul le second modèle détecte les deux erreurs de lecture.

Le reçu sur l'image (lignes 12.00, 18.50, 12.00, total 42.50), la sortie du VLM avec 18.80 et 42.80 erronés, et la lecture d'un second modèle ; contrôle 1, tous les champs présents, réussi ; contrôle 2, 12.00 + 18.80 + 12.00 = 42.80, réussi alors que les deux valeurs sont fausses ; contrôle 3, le second modèle n'est pas d'accord et envoie le reçu en relecture humaineLe reçu sur l'image (lignes 12.00, 18.50, 12.00, total 42.50), la sortie du VLM avec 18.80 et 42.80 erronés, et la lecture d'un second modèle ; contrôle 1, tous les champs présents, réussi ; contrôle 2, 12.00 + 18.80 + 12.00 = 42.80, réussi alors que les deux valeurs sont fausses ; contrôle 3, le second modèle n'est pas d'accord et envoie le reçu en relecture humaine

Quelques mesures pratiques :

  • Rapprochement arithmétique. Quand le schéma les expose, vérifiez subtotal + tax + fees + shipping - discounts, dans la tolérance d’arrondi de la devise, par rapport au total indiqué. Orientez les composantes manquantes ou les écarts vers la relecture.
  • Contrôles de dates, de téléphones et de devises. Analysez les dates avec un analyseur de dates et rejetez les valeurs impossibles. Vérifiez par des motifs le nombre de chiffres des numéros de téléphone et les formats de devise.
  • Second chemin d’extraction. Faites passer les champs critiques par un autre modèle ou un autre moteur OCR et signalez les désaccords. L’accord n’augmente la confiance que si les deux chemins ont des modes de défaillance suffisamment différents ; ce n’est pas une preuve d’exactitude.

Le contrôle de rapprochement prend les chaînes de montants copiées et utilise Decimal, de sorte qu’aucun arrondi en virgule flottante n’entre dans la comparaison. Les asserts rejouent l’exemple de reçu ci-dessus.

from decimal import Decimal

def reconcile(subtotal, tax, fees, shipping, discounts, total, tolerance="0.01"):
    """True when the stated total matches its components within tolerance."""
    parts = [subtotal, tax, fees, shipping, discounts, total]
    if any(p is None for p in parts):
        return False  # a missing component goes to review
    s, t, f, sh, d, tot = (Decimal(p) for p in parts)
    return abs(s + t + f + sh - d - tot) <= Decimal(tolerance)

items = ["12.00", "18.80", "12.00"]  # the image shows 18.50
subtotal = str(sum(Decimal(i) for i in items))
assert reconcile(subtotal, "0", "0", "0", "0", "42.80")  # passes, and both values are wrong
assert not reconcile(subtotal, "0", "0", "0", "0", "42.50")  # one misread value is caught
assert not reconcile(subtotal, None, "0", "0", "0", "42.80")  # missing tax goes to review

Le rapprochement détecte un seul montant mal lu. Il ne peut pas détecter deux erreurs de lecture qui s’accordent entre elles ; les champs critiques ont donc toujours besoin du second chemin d’extraction.

Lancer la comparaison sur vos propres pages

Le dépôt OCR Gauntlet exécute plusieurs moteurs OCR sur les mêmes pages et écrit une ligne de résultat par moteur et par page, échecs compris. Il compare des pages d’images ; il ne couvre pas le contrôle de la couche de texte du palier 0.

Le runner fonctionne en quatre étapes :

  1. Le manifeste liste les pages et les hachages de leur texte de référence.
  2. Le runner envoie chaque page à chaque moteur.
  3. Il écrit une ligne JSONL par moteur et par page, marquée succès, erreur ou omission, avec la sortie brute, le temps et le coût.
  4. Le récapitulatif donne le taux d’achèvement, le CER sur les pages relues et le coût par page réussie. Les paires en échec ou omises restent dans le dénominateur du taux d’achèvement.

La première exécution utilise des pages générées et Tesseract ; elle nécessite donc Python 3.12, uv et une installation système de Tesseract, mais aucune clé d’API :

uv sync --locked --extra tesseract --extra notebooks
uv run python scripts/make_fixture.py data/demo
uv run ocr-gauntlet data/demo/manifest.json --engines tesseract --output results/demo.jsonl

Les pages générées servent uniquement à vérifier que le pipeline fonctionne ; elles ne mesurent pas la qualité de l’OCR. Pour un vrai test sur des reçus, importez des échantillons CORD avec uv run python scripts/download_samples.py --dataset cord --count 2 --output data/cord et préparez une référence relue comme le décrit le README du dépôt. Ajoutez d’autres moteurs avec --engines ; les moteurs hébergés exigent aussi --allow-remote, car ils envoient vos pages.

Dans le récapitulatif, lisez d’abord le taux d’achèvement : un moteur qui échoue sur un tiers des pages n’est pas un candidat, quel que soit son CER. Comparez ensuite le CER sur les pages relues, puis le coût par page réussie, qui inclut les dépenses des tentatives échouées.

Points clés à retenir

  1. Associez le palier de modèle à une classe de documents annotée. Les moteurs traditionnels peuvent suffire pour le texte propre ; les VLMs spécialisés et généralistes doivent justifier leur surcoût sur les pages plus difficiles.
  2. Ne fusionnez pas des classements différents. Les métriques d’OmniDocBench et les préférences d’OCR Arena répondent à des questions différentes.
  3. Calibrez le routage. Les seuils de confiance, les classes de documents, la criticité des champs et la politique de relecture humaine relèvent d’une seule évaluation.
  4. Validez les sorties plausibles. La conformité au schéma et l’arithmétique interne ne peuvent pas prouver qu’une valeur apparaît dans l’image.
  5. Chiffrez les pages réussies. Incluez les nouvelles tentatives, la relecture, l’exploitation fixe et les contrôles de qualité quand vous comparez des API à l’auto-hébergement.

Références

  • OCR Arena Leaderboard - Duels de modèles en tête-à-tête, par la communauté
  • Le dépôt OCR Gauntlet - Comparaison exécutable de moteurs OCR sur les mêmes pages, avec les backends Docling et le coût par page réussie
  • OmniDocBench - Évaluation de l’analyse de documents de bout en bout
  • dots.ocr - Environ 3B de paramètres au total, dont un modèle de langage de 1.7B
  • PaddleOCR - Boîte à outils et modèles d’OCR traditionnels
  • OpenRouter - Passerelle d’accès unifiée pour tester des modèles en A/B