Analysez images et documents avec GPT-4o Vision en JavaScript : envoi base64, OCR, extraction structuree avec Structured Outputs, comparaison multi-images et maitrise des couts.
La vision multimodale en bref
Avec GPT-4o Vision, le modele ne se contente plus de lire du texte : il voit. Vous lui envoyez une image accompagnee d'une question, et il decrit, analyse, lit le texte present, interprete un graphique ou extrait des donnees structurees.
Cette capacite ouvre des cas d'usage concrets : lecture de factures, analyse de captures d'ecran, moderation visuelle, accessibilite (decrire une image pour un malvoyant), extraction de tableaux. Le tout via la meme API chat completions que vous connaissez deja.
Prerequis et cle API
- Compte OpenAI et cle dans
OPENAI_API_KEY - Node.js 18+ et le SDK
openai - Des images au format PNG, JPEG, WebP ou GIF non anime
npm install openai
echo "OPENAI_API_KEY=sk-..." >> .env
Premiere analyse d'image
Le message utilisateur devient un tableau melant texte et image. Voici l'analyse d'une image accessible via URL.
// vision-url.js — analyser une image depuis une URL
import 'dotenv/config';
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
// Partie texte : la question posee sur l'image
{ type: 'text', text: 'Decris cette image et liste les objets visibles.' },
// Partie image : via une URL publique
{
type: 'image_url',
image_url: { url: 'https://example.com/photo.jpg' },
},
],
}],
});
console.log(response.choices[0].message.content);
Image locale en base64
Pour une image stockee localement (ou recue d'un upload), encodez-la en base64 et passez-la comme data URL — pas besoin de l'heberger publiquement.
// vision-base64.js — analyser une image locale
import { readFile } from 'node:fs/promises';
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Lire et encoder l'image en base64
const buffer = await readFile('./facture.png');
const base64 = buffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Quel est le montant total TTC de cette facture ?' },
{ type: 'image_url', image_url: { url: dataUrl } },
],
}],
});
console.log(response.choices[0].message.content);
OCR et extraction de texte
GPT-4o lit le texte present sur une image, y compris l'ecriture manuscrite, les panneaux, les captures d'ecran. Un prompt precis ameliore la fidelite.
// ocr.js — extraire le texte brut d'une image
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{
type: 'text',
text: 'Transcris fidelement tout le texte present sur cette image, '
+ 'en conservant la mise en forme (sauts de ligne, listes). '
+ 'Ne commente pas, renvoie uniquement le texte.',
},
{ type: 'image_url', image_url: { url: dataUrl } },
],
}],
temperature: 0, // 0 pour une transcription fidele et deterministe
});
console.log(response.choices[0].message.content);
temperature: 0 pour les taches d'extraction : vous voulez une transcription fidele, pas une reformulation creative. Precisez aussi de ne pas commenter pour eviter le texte parasite.
Extraction structuree avec Structured Outputs
Le vrai pouvoir apparait en combinant la vision avec les Structured Outputs : extraire des donnees d'une image directement dans un JSON valide et type.
// vision-structured.js — extraire une facture en JSON garanti
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Schema cible de l'extraction
const Facture = z.object({
numero: z.string(),
date: z.string(),
fournisseur: z.string(),
lignes: z.array(z.object({
description: z.string(),
quantite: z.number(),
prixUnitaire: z.number(),
})),
totalHT: z.number(),
totalTTC: z.number(),
});
const response = await client.chat.completions.parse({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Extrais les donnees de cette facture.' },
{ type: 'image_url', image_url: { url: dataUrl } },
],
}],
// Force une sortie JSON conforme au schema
response_format: zodResponseFormat(Facture, 'facture'),
});
// Objet type, valide, directement exploitable
const facture = response.choices[0].message.parsed;
console.log(`Facture ${facture.numero} — ${facture.totalTTC} EUR TTC`);
facture.lignes.forEach((l) => console.log(` ${l.description}: ${l.quantite} x ${l.prixUnitaire}`));
Comparer plusieurs images
On peut envoyer plusieurs images dans un meme message pour les comparer, detecter des differences ou suivre une evolution.
// vision-compare.js — comparer deux images
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Compare ces deux maquettes UI et liste les differences visuelles.' },
{ type: 'image_url', image_url: { url: dataUrlAvant } },
{ type: 'image_url', image_url: { url: dataUrlApres } },
],
}],
});
console.log(response.choices[0].message.content);
Le parametre detail et les couts
Le cout de la vision depend de la resolution et du parametre detail. Trois valeurs : low (rapide, peu de tokens), high (analyse fine, plus cher), auto (le modele decide).
// Regler detail selon le besoin reel
const content = [
{ type: 'text', text: 'Cette image contient-elle un chat ?' },
{
type: 'image_url',
image_url: {
url: dataUrl,
detail: 'low', // suffisant pour une question simple : economique
},
},
];
// Pour lire un petit texte dans une image complexe :
// detail: 'high' decoupe l'image en tuiles haute resolution (plus cher)
- Utiliser
detail: lowpour les questions simples - Redimensionner les images avant envoi (inutile d'envoyer du 4K)
- Reserver
detail: highaux petits textes ou details fins - Combiner vision + Structured Outputs pour eviter les allers-retours
- Analyser les images individuellement plutot qu'en lot quand possible
Chaine d'upload depuis le navigateur
Dans une vraie application, l'image vient d'un <input type="file">. Deux bonnes pratiques cote front : redimensionner l'image avant l'envoi (inutile d'uploader du 4K pour une analyse) et n'envoyer que le strict necessaire au serveur, qui reste le seul a detenir la cle API.
// front.js — redimensionner l'image cote client avant upload (canvas)
async function resizeImage(file, maxDim = 1024) {
const bitmap = await createImageBitmap(file);
const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height));
const canvas = document.createElement('canvas');
canvas.width = bitmap.width * scale;
canvas.height = bitmap.height * scale;
canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);
// Exporter en JPEG compresse (qualite 0.8) pour reduire le poids
return new Promise((resolve) =>
canvas.toBlob(resolve, 'image/jpeg', 0.8)
);
}
// Envoyer l'image redimensionnee a notre serveur (jamais directement a OpenAI)
async function analyser(file, question) {
const blob = await resizeImage(file);
const form = new FormData();
form.append('image', blob);
form.append('question', question);
const res = await fetch('/api/vision', { method: 'POST', body: form });
return res.json();
}
// server.js — recevoir l'upload et le transmettre a GPT-4o Vision
import express from 'express';
import multer from 'multer';
import OpenAI from 'openai';
const upload = multer({ limits: { fileSize: 5 * 1024 * 1024 } }); // 5 Mo max
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const app = express();
app.post('/api/vision', upload.single('image'), async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'Image manquante' });
// Convertir le buffer recu en data URL
const dataUrl = `data:${req.file.mimetype};base64,${req.file.buffer.toString('base64')}`;
const r = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{ type: 'text', text: String(req.body.question || 'Decris cette image.') },
{ type: 'image_url', image_url: { url: dataUrl, detail: 'low' } },
],
}],
});
res.json({ resultat: r.choices[0].message.content });
});
app.listen(3000);
multer limits) pour eviter les abus.
GPT-4o Vision vs OCR et services dedies
La vision LLM ne remplace pas tout. Selon le volume, le besoin de structuration et le budget, un OCR classique ou un service dedie peut etre plus adapte. Le tableau ci-dessous resume les compromis.
| Critere | GPT-4o Vision | OCR classique (Tesseract) | Service dedie (Document AI) |
|---|---|---|---|
| Comprehension du contexte | Excellente | Nulle (texte brut) | Bonne (sur formats connus) |
| Extraction structuree | Native (Structured Outputs) | Manuelle (regex) | Native (templates) |
| Cout au volume | Eleve | Quasi nul (self-hosted) | Moyen a eleve |
| Vitesse | Moyenne | Tres rapide | Rapide |
| Documents non standard | Tres flexible | Fragile | Rigide |
| Ideal pour | Documents varies, faible volume | Numerisation massive | Pipeline metier stable |
Robustesse et validation en production
La vision hallucine plus volontiers que le texte sur les details ambigus. En production, on n'expose jamais brute la sortie du modele : on la valide et on detecte les cas peu fiables pour les router vers une verification humaine.
// validate.js — controle de coherence sur une extraction de facture
function validerFacture(f) {
const erreurs = [];
// Coherence arithmetique : somme des lignes ~ total HT
const sommeLignes = f.lignes.reduce((s, l) => s + l.quantite * l.prixUnitaire, 0);
if (Math.abs(sommeLignes - f.totalHT) > 0.02) {
erreurs.push('Total HT incoherent avec les lignes');
}
// TTC doit etre superieur ou egal au HT
if (f.totalTTC < f.totalHT) erreurs.push('TTC inferieur au HT');
// Champs obligatoires non vides
if (!f.numero || !f.date) erreurs.push('Numero ou date manquant');
return { valide: erreurs.length === 0, erreurs };
}
const { valide, erreurs } = validerFacture(facture);
if (!valide) {
// Router vers une revue humaine plutot que d'ingerer des donnees fausses
await mettreEnFileRevue(facture, erreurs);
}
Cas d'usage et limites
- Extraction de donnees de documents (factures, formulaires, tickets)
- Description d'images pour l'accessibilite
- Analyse de captures d'ecran et de maquettes UI
- Interpretation de graphiques et tableaux
- Moderation de contenu visuel
- Classification visuelle avec contexte
Erreurs frequentes a l'integration
Erreur de debutant la plus couteuse : envoyer l'image en pleine resolution avec detail: high par defaut. Une photo de smartphone en 12 megapixels est decoupee en de nombreuses tuiles, chacune facturee — alors qu'un redimensionnement a 1024 px et detail: low suffit pour la plupart des questions. Redimensionnez systematiquement cote client, comme dans la chaine d'upload vue plus haut, avant tout appel.
Deuxieme piege : faire confiance aveuglement aux chiffres extraits. La vision excelle pour comprendre une mise en page, mais elle peut se tromper sur un total, inverser deux colonnes d'un tableau ou mal lire un chiffre manuscrit. Pour toute donnee comptable ou juridique, ajoutez un controle de coherence (les sommes correspondent-elles ?) et un parcours de revue humaine sur les cas suspects.
Cote application, deux oublis reviennent souvent : ne pas valider le type MIME et le poids du fichier recu (un PDF ou un fichier de plusieurs Mo envoye comme image fait echouer ou exploser le cout), et laisser fuiter des donnees personnelles presentes sur les documents analyses. Validez le format en amont, plafonnez la taille, et ne conservez l'image que le temps strictement necessaire a l'analyse pour rester conforme au RGPD.
Cas particulier frequent : les documents PDF multi-pages. GPT-4o Vision attend des images, pas des PDF. Il faut donc convertir chaque page en image (via pdf-to-img ou pdfjs cote Node) avant l'envoi, puis decider d'une strategie : analyser page par page (precis mais couteux en appels) ou assembler quelques pages cles en une seule image (economique mais au risque de perdre du detail). Pour un document de dizaines de pages, un pre-filtrage — ne soumettre a la vision que les pages reellement pertinentes — evite de payer l'analyse de pages vides ou hors-sujet.
Conclusion
GPT-4o Vision apporte la comprehension visuelle directement dans l'API que vous utilisez deja. Envoyer une image revient a ajouter un bloc image_url au message — via URL ou base64. La combinaison avec les Structured Outputs est particulierement puissante : elle transforme n'importe quel document visuel en JSON type et valide, sans parsing fragile.
Pilotez les couts avec le parametre detail et en redimensionnant vos images. Gardez en tete les limites du modele sur les comptages et mesures precises, et ajoutez une validation pour les usages critiques. De l'extraction de factures a l'accessibilite, la vision multimodale ouvre une nouvelle classe d'applications.
- Ajouter un bloc
image_url(URL ou base64) au message temperature: 0pour l'OCR et l'extraction fidele- Vision + Structured Outputs = documents vers JSON type
- Regler
detail(low/high) selon le besoin pour le cout - Valider les donnees critiques (comptages, mesures)