Multimodal · OCR · Extraction · 2026-07-22 · Intermédiaire

Extraire des informations d’un document avec un modèle vision

Analyser une image de facture avec un modèle multimodal et valider les champs extraits avant de les enregistrer.

Un document scanné et un appareil photo conduisent à une fiche de données structurées.
Illustration de couverture du tutoriel.
Note moyenne— / 5

Objectif à réaliser

Produire un JSON de facture contenant fournisseur, date et total, puis refuser une image illisible.

Document de test destiné à l’évaluation de moteurs OCR et de modèles vision.
Fixture de facture utilisée pour tester l’extraction multimodale.
Durée55 minutes
Prérequis
  • Python 3.11 ou plus récent
  • Bases de JSON et des contrôles de données
  • Un modèle vision compatible, ou les fixtures --mock fournies
Mots-clés
multimodalvisionOCRJSON
Environnement vérifiéPython 3.11 et Pillow 11mock · Linux, macOS, Windows WSL

Figures du tutoriel

Schéma d’extraction vision reliant une facture image à un JSON validé par des invariants métier.
Chaîne d’extraction et de validation des champs de facture.
Flux détaillé reliant image de facture, JSON vision, contrôles métier et décision de revue.
La sortie du modèle devient une donnée utilisable seulement après validation des types et invariants.
← Retour aux tutoriels

Objectifs et livrables

Une facture reçue sous forme d’image doit souvent être ressaisie avant de pouvoir être traitée. Nous allons automatiser une partie de ce travail : demander à un modèle vision de proposer les champs utiles, puis vérifier ces valeurs en Python.

Le tutoriel s’appuie sur deux exemples de facture pour suivre le parcours d’une image jusqu’à une sortie structurée. Vous préparerez la consigne d’extraction, récupérerez la réponse du modèle et contrôlerez les champs, les types, la date et le montant. Nous examinerons ensuite les invariants métier et le traitement d’un document difficile à lire.

Vous verrez pourquoi il est utile de conserver l’image et la réponse brute à côté des valeurs validées. Lorsqu’une extraction échoue, ces éléments permettent de retrouver le champ en cause et de décider si une nouvelle image ou une vérification manuelle est nécessaire.

Le programme produira outputs/invoice.json pour une extraction acceptée ou outputs/review.json pour un cas à examiner. Vous disposerez ainsi d’un exemple complet de préremplissage contrôlé, avec un résultat que l’on peut relire avant de l’intégrer à un autre système.

Lire le flux avant de coder

Le schéma place la validation après l’extraction. Le modèle vision propose des champs à partir de l’image, mais il ne connaît pas nécessairement les contraintes de votre système : un montant doit être numérique, une date doit être interprétable et certains champs doivent être présents ensemble. La réponse brute reste donc une preuve de travail, pas le livrable final.

Une extraction acceptable produit invoice.json ; un doute ou une incohérence produit review.json avec assez de contexte pour une reprise humaine. Cette séparation évite de masquer une erreur de lecture en la remplaçant par une valeur par défaut.

1. Préparer l’environnement

Le contrôle Pillow vérifie qu’un fichier image peut être décodé ; il ne confirme ni la lisibilité du texte ni l’exactitude de son contenu. Lancez-le après avoir placé les fixtures dans data/. En cas d’échec, vérifiez d’abord le fichier et son format avant de modifier le prompt ou le schéma : le modèle n’a pas encore été sollicité à cette étape.

Avant tout appel multimodal, vérifiez que Python peut ouvrir l’image de test. Path.is_file contrôle le chemin et Pillow examine le format du fichier ; une image qui passe ces tests peut néanmoins contenir du texte difficile à lire. Ce contrôle technique prépare donc l’extraction sans encore juger la qualité du document.

L’appel image.verify() recherche des problèmes de fichier sans réaliser la lecture des champs de facture. Exécutez-le une fois les images de l’étape suivante placées dans data/. Une erreur à ce stade indique un chemin ou un fichier à corriger, ce qui évite de diagnostiquer à tort un problème du modèle.

mkdir -p extraction-document-vision/{data,src,outputs}
cd extraction-document-vision
python3 -m venv .venv && source .venv/bin/activate
python -m pip install jsonschema pillow

Sortie obtenue

Successfully installed jsonschema pillow

Commande de vérification

python -c "from PIL import Image; print('vision stack ok')"

Code de l’étape

Contrôlez l’existence et le format avant d’appeler le modèle :

from pathlib import Path
from PIL import Image

image_path = Path("data/invoice-clear.jpg")
assert image_path.is_file()
with Image.open(image_path) as image:
    image.verify()
print(image_path, image_path.stat().st_size, "octets")

2. Préparer deux fixtures

Regardez les deux images avant l’extraction et notez manuellement les champs que vous pouvez lire. Cette petite référence permettra de repérer une date plausible mais incorrecte ou un montant confondu avec un autre. L’empreinte affichée sert à reconnaître la fixture utilisée ; un préfixe court de SHA-256 est un repère de lecture, pas un identifiant dont l’unicité serait garantie.

Les deux fixtures servent à comparer des comportements, pas seulement à vérifier que deux fichiers existent. Ouvrez-les visuellement pour repérer le fournisseur, la date et le montant qui devraient être retrouvés. Notez les zones ambiguës : elles expliqueront ensuite pourquoi une extraction doit être relue.

Le hash SHA-256 identifie le contenu du fichier utilisé pour l’essai. Deux images portant le même nom peuvent différer après un recadrage ou une compression ; le hash permet de retrouver cette différence. Les noms et dimensions des sorties illustratives peuvent varier selon les fixtures utilisées : comparez d’abord les fichiers réellement inspectés et leur rôle dans le test.

Placez data/invoice-clear.jpg et data/invoice-review.jpg. La première est un document de test OCR sous licence CC0 ; la seconde est une fixture de type document nécessitant une revue humaine.

Sortie obtenue

Les deux images sont présentes et leur taille est enregistrée.

invoice-clear.jpg width=1600 height=1100
invoice-blurry.jpg width=640 height=440

Commande de vérification

python src/inspect.py data/*.jpg

Code de l’étape

import hashlib
from pathlib import Path

for image_path in Path("data").glob("*.jpg"):
    digest = hashlib.sha256(image_path.read_bytes()).hexdigest()
    print(image_path.name, image_path.stat().st_size, digest[:12])

3. Demander puis valider l’extraction

La conversion en base64 transporte les octets de l’image dans la requête ; elle n’améliore pas sa qualité et ne constitue pas une reconnaissance de caractères. Le type annoncé doit correspondre au fichier transmis. Après la réponse, examinez la structure puis les valeurs : un document correctement sérialisé en JSON peut encore contenir une date inventée ou un chiffre mal lu.

Le texte de la demande et l’image sont envoyés dans un même message. Le Base64 transporte les octets de l’image sous forme de texte ; le préfixe de la data URL indique ici un contenu JPEG. Cette conversion ne corrige ni le flou ni les valeurs mal lisibles.

Après l’appel, le programme doit d’abord décoder le JSON, puis vérifier les champs et leurs types. L’assertion de l’extrait vérifie uniquement la présence des clés : elle ne remplace pas le schéma complet. Enfin, la valeur confidence est une estimation produite par le modèle ; le seuil de démonstration doit être confronté à des documents annotés avant de servir de règle d’acceptation réelle.

Encodez l’image, demandez uniquement supplier, date, total et confidence, puis validez la sortie avec un schéma. Si confidence < 0.85, écrivez une revue plutôt qu’une facture acceptée.

Sortie obtenue

La facture nette produit un JSON valide ; la facture floue est orientée vers la revue.

invoice-clear.json schema=valid confidence=0.97
invoice-review.json schema=valid confidence=0.62 action=human_review

Le mode réel encode l’image en data URL et appelle un modèle vision compatible avec python src/extract.py --real data/invoice-clear.jpg. Vérifiez toujours le schéma avant import.

Code de l’étape

import base64, json
from pathlib import Path
from llm_client import chat

encoded = base64.b64encode(Path(image_path).read_bytes()).decode()
raw = chat([{"role": "user", "content": [
    {"type": "text", "text": "Extrait supplier, date, total et confidence en JSON uniquement."},
    {"type": "image_url", "image_url": {
        "url": f"data:image/jpeg;base64,{encoded}"
    }},
]}])
payload = json.loads(raw)
assert {"supplier", "date", "total", "confidence"} <= set(payload)

Commande de vérification

python src/extract.py --mock data/invoice-clear.jpg
python src/extract.py --mock data/invoice-review.jpg

4. Vérifier les invariants métier

Les règles métier complètent la validation de forme : une date analysable et un total positif restent à comparer au document. Vérifiez les types avant les comparaisons numériques et lisez le montant dans son contexte, notamment les séparateurs décimaux. Le seuil de confiance trie ici les sorties pour la revue ; il ne remplace pas cette confrontation avec l’image et ne prouve pas la calibration du modèle.

Les contrôles métier portent sur les valeurs extraites. date.fromisoformat vérifie qu’une chaîne représente une date lisible au format attendu, mais ne prouve pas que cette date correspond à la facture. De même, un montant positif peut avoir été mal lu. Confrontez les sorties acceptées aux champs visibles sur l’image.

Le choix entre invoice.json et review.json rend la décision explicite pour le traitement suivant. Vérifiez la validité des types avant les comparaisons numériques, puis examinez la raison d’un refus. Pour un document envoyé en revue, conservez assez d’éléments pour permettre la correction sans devoir relancer aveuglément l’extraction.

Contrôlez la date ISO, le total positif et la présence du fournisseur. Conservez l’image, le hash et la réponse brute dans un dossier non publié.

Sortie obtenue

Le JSON accepté passe les invariants et la réponse de revue ne peut pas être importée par erreur.

invoice-clear status=accepted total=128.40 currency=EUR
invoice-blurry status=review reason="confidence below threshold"

Commande de vérification

python src/quality.py outputs
python -m json.tool outputs/invoice.json >/dev/null
python -m json.tool outputs/review.json >/dev/null

Code de l’étape

from datetime import date

confidence = float(payload["confidence"])
valid_date = date.fromisoformat(payload["date"])
accepted = bool(payload["supplier"]) and payload["total"] > 0 and confidence >= 0.85
output_name = "outputs/invoice.json" if accepted else "outputs/review.json"
print(output_name, valid_date.isoformat())

Exemples de sorties

Une image est acceptée et une image est envoyée en revue. Le mode réel peut varier selon le modèle et la qualité du scan ; comparez toujours avec la vérité terrain.

Dépannage et diagnostics

Réduisez la taille de l’image si l’API refuse le payload. Ne stockez pas de factures réelles dans le dépôt et masquez les données personnelles dans les exemples.

Vérification finale

  • champs structurés ;
  • confiance contrôlée ;
  • image floue refusée ;
  • réponse brute et hash conservés.

Limites et prolongements

Le modèle peut lire une valeur erronée avec assurance. Ajoutez une double validation humaine pour les montants et une mesure par type de document.

Références

Livrables du tutoriel

Téléchargements

Retrouvez les codes sources et les fichiers nécessaires pour reproduire le résultat présenté.