Extraction de Données Structurées depuis des Images
Mis à jour le 29 juillet 2026
Transformer des images en données exploitables
La leçon précédente restituait du texte ; celle-ci va plus loin en produisant des données. Extraire du structuré, c’est transformer le contenu visuel d’une image — tableau, formulaire, carte de visite, étiquette — en JSON, CSV ou tout autre format que votre code manipule directement, sans relecture humaine intermédiaire.
Le changement d’exigence est réel. Une transcription approximative reste utile à un lecteur ; un JSON dont une clé manque fait planter le programme qui le consomme. Cette leçon présente donc les deux garanties à mettre en place : le mode JSON de l’API pour la validité syntaxique, et la validation par schéma pour la conformité métier.
Le mode JSON de l’API
Mistral propose un paramètre response_format qui contraint le modèle à retourner du JSON valide. Sans lui, vous passeriez votre temps à nettoyer des réponses enveloppées dans des blocs markdown ou précédées d’une phrase d’introduction polie.
from mistralai import Mistral
import os
import json
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "Retournez toujours un objet JSON valide avec les données extraites de l'image."
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Extrayez les données de cette carte de visite en JSON : nom, titre, entreprise, email, téléphone, adresse."
},
{"type": "image_url", "image_url": data_uri_carte}
]
}
]
)
# Le résultat est directement parsable
donnees = json.loads(response.choices[0].message.content)
print(json.dumps(donnees, indent=2, ensure_ascii=False))
Le json.loads s’exécute sans précaution particulière : response_format={"type": "json_object"} garantit un JSON syntaxiquement valide en sortie. Il ne garantit en revanche rien sur les clés présentes — d’où l’importance de décrire le format attendu dans le prompt lui-même, comme dans les exemples qui suivent.
Extraire un tableau depuis une image
Les tableaux photographiés ou capturés en copie d’écran sont un cas d’usage fréquent, et celui où le schéma explicite rapporte le plus : sans consigne, le modèle inventera sa propre structure, différente à chaque appel.
prompt_tableau = """Extrayez le contenu de ce tableau en JSON.
Format attendu :
{
"colonnes": ["Colonne1", "Colonne2", ...],
"lignes": [
{"Colonne1": "valeur", "Colonne2": "valeur"},
...
]
}
Lisez toutes les lignes visibles. Si une cellule est vide, utilisez null."""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt_tableau},
{"type": "image_url", "image_url": encoder_image("captures/tableau-excel.png")}
]
}
]
)
donnees = json.loads(response.choices[0].message.content)
print(f"Colonnes : {donnees['colonnes']}")
print(f"Nombre de lignes : {len(donnees['lignes'])}")
La consigne sur les cellules vides mérite qu’on s’y arrête : sans elle, le modèle décale les valeurs pour combler les trous et le tableau ressort faussé. L’affichage du nombre de lignes sert de contrôle immédiat — si l’image en montre douze et que le script en annonce huit, la lecture est incomplète.
Formulaires et documents administratifs
Un formulaire rempli ajoute deux difficultés : l’écriture manuscrite et les cases à cocher, qui n’ont pas de valeur textuelle. Le schéma prévoit donc des champs dédiés.
prompt_formulaire = """Analysez ce formulaire rempli et extrayez tous les champs et leurs valeurs.
Format JSON attendu :
{
"type_formulaire": "...",
"champs": {
"nom_du_champ": "valeur_renseignee",
...
},
"cases_cochees": ["champ1", "champ2"],
"signature_presente": true/false
}
Si un champ est vide ou illisible, utilisez null."""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "Vous êtes un extracteur de formulaires précis. Retournez du JSON structuré."
},
{
"role": "user",
"content": [
{"type": "text", "text": prompt_formulaire},
{"type": "image_url", "image_url": encoder_image("formulaires/cerfa-12345.jpg")}
]
}
]
)
Extraire des données d’étiquettes produit
Sur une étiquette alimentaire, la structure est normée : le tableau nutritionnel pour 100 g, la liste d’ingrédients, les allergènes. Le schéma peut donc être détaillé jusqu’au nom d’unité, ce qui évite d’avoir à deviner ensuite si « 12 » désignait des grammes ou des kilocalories.
prompt_etiquette = """Extrayez les informations nutritionnelles de cette étiquette.
Format JSON :
{
"nom_produit": "...",
"poids_net": "...",
"valeurs_nutritionnelles_pour_100g": {
"energie_kcal": ...,
"matieres_grasses_g": ...,
"dont_satures_g": ...,
"glucides_g": ...,
"dont_sucres_g": ...,
"proteines_g": ...,
"sel_g": ...
},
"ingredients": "...",
"allergenes": ["..."]
}"""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt_etiquette},
{"type": "image_url", "image_url": encoder_image("produits/etiquette.jpg")}
]
}
]
)
produit = json.loads(response.choices[0].message.content)
Pipeline d’extraction avec validation
En production, la garantie syntaxique ne suffit plus. Le modèle peut retourner un JSON parfaitement valide où email est absent, ou bien où telephone contient un entier au lieu d’une chaîne. Pydantic transforme ces cas en exceptions explicites, au moment de l’extraction plutôt que trois étapes plus loin dans votre pipeline.
from pydantic import BaseModel, Field
from typing import Optional
import json
class CarteDeVisite(BaseModel):
nom: str
prenom: str
titre: Optional[str] = None
entreprise: Optional[str] = None
email: Optional[str] = None
telephone: Optional[str] = None
adresse: Optional[str] = None
site_web: Optional[str] = None
def extraire_carte_visite(chemin_image):
"""Extrait et valide les données d'une carte de visite."""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "Extrayez les données de cette carte de visite. Champs : nom, prenom, titre, entreprise, email, telephone, adresse, site_web. Retournez du JSON."
},
{
"role": "user",
"content": [
{"type": "text", "text": "Extrayez les informations de cette carte de visite."},
{"type": "image_url", "image_url": encoder_image(chemin_image)}
]
}
]
)
# Parser et valider avec Pydantic
donnees_brutes = json.loads(response.choices[0].message.content)
carte = CarteDeVisite(**donnees_brutes)
return carte
# Utilisation
carte = extraire_carte_visite("cartes/contact-dupont.jpg")
print(f"Contact : {carte.prenom} {carte.nom} - {carte.email}")
Observez la distinction entre nom: str et titre: Optional[str] = None : une carte sans nom est une extraction ratée, une carte sans intitulé de poste est un cas normal. C’est vous qui tracez cette ligne, et Pydantic la fait respecter.
Convertir en CSV ou DataFrame
Dernière étape, l’entrée dans votre chaîne de traitement de données. Le JSON produit plus haut se convertit sans intermédiaire :
import pandas as pd
# Depuis un tableau extrait
donnees = json.loads(response.choices[0].message.content)
# Créer un DataFrame pandas
df = pd.DataFrame(donnees["lignes"])
print(df.to_string())
# Exporter en CSV
df.to_csv("export/tableau_extrait.csv", index=False, encoding="utf-8")
Le encoding="utf-8" à l’export n’est pas facultatif dès que vos données contiennent des accents ou des symboles monétaires.
Points clés à retenir
- Le paramètre
response_format={"type": "json_object"}garantit un JSON valide en sortie - Fournissez toujours un schéma JSON attendu dans votre prompt pour guider l’extraction
- Utilisez Pydantic pour valider les données extraites en production
- Les tableaux, formulaires, étiquettes et cartes de visite sont des cibles idéales
- Combinez l’extraction avec pandas pour une intégration directe dans vos workflows data