Intégrer l’OCR dans le navigateur avec Tesseract.js
Sélectionnez une image locale, cliquez sur « Recognize text » et copiez son texte sans téléverser l’image. Ce tutoriel propose aux développeurs web un exemple complet avec Tesseract.js : retour visuel pendant le chargement, nouvel essai avec le même fichier et une seule tâche de reconnaissance à la fois. Commencez avec une capture d’écran nette de texte anglais imprimé ; le résultat de l’OCR doit toujours être relu.
Ce qui s’exécute dans le navigateur
Tesseract.js exécute le moteur d’OCR Tesseract via WebAssembly dans un worker. C’est le navigateur qui effectue la reconnaissance : la vitesse et la consommation mémoire dépendent donc de l’appareil du lecteur et de l’image. Aucun résultat instantané n’est promis. Le périmètre du projet exclut également les PDF en entrée directe : effectuez séparément le rendu des pages PDF en images avant de les reconnaître.
Cet exemple fixe Tesseract.js et son core à la version 7.0.0, et les données de langue anglaise à la
version 1.0.0. Il utilise la sortie texte, activée par défaut. L’API du worker
initialise la langue lors de l’appel asynchrone createWorker('eng', 1, options) ; les anciennes étapes
loadLanguage() et initialize() sont inutiles.
Compatibilité des navigateurs et prérequis
Utilisez un navigateur à jour prenant en charge les Web Workers, les workers imbriqués et WebAssembly. L’exemple complet a été testé dans Chromium 145 et 152 sous Linux. Il vous faut aussi Python 3 pour servir les deux fichiers en local, ainsi qu’une connexion réseau pour charger depuis jsDelivr les scripts, le WASM et les données de langue aux versions fixées. Aucun build Node.js ni installation de paquet n’est nécessaire.
Dans cet exemple, l’image reste dans le navigateur, mais le téléchargement des ressources contacte tout de même un CDN. La mise en cache des langues ne suffit pas à faire fonctionner la page hors ligne. Une application hors ligne doit aussi servir ou mettre en cache son HTML, ses scripts, son worker, son WASM et ses ressources de langue ; ce tutoriel n’installe pas de cache hors ligne. Consultez les options d’hébergement des ressources du projet.
Premiers pas avec Tesseract.js
Installation
Créez un nouveau répertoire vide nommé tesseract-browser. Si ce nom existe déjà, choisissez
un autre répertoire plutôt que de remplacer ses fichiers. Enregistrez les deux blocs suivants sous
index.html et ocr-worker.js dans ce répertoire. Ce sont de simples
fichiers pour navigateur, sans framework ni backend.
Exemple de base : reconnaître le texte d’une image
Enregistrez ceci sous index.html. Le sélecteur de fichier et le bouton restent
désactivés pendant qu’une tâche est en cours. Sélectionner ensuite une autre image efface le
résultat précédent ; cliquer de nouveau sur le bouton relance la reconnaissance du fichier
sélectionné sans nécessiter un nouvel événement de sélection de fichier.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Read text from a local image</title>
</head>
<body>
<h1>Read text from a local image</h1>
<form id="ocrForm">
<fieldset id="controls">
<legend>Recognize English text</legend>
<label for="imageInput">Image (JPEG, PNG, or WebP; up to 5 MiB)</label>
<input id="imageInput" type="file" accept="image/jpeg,image/png,image/webp" />
<button type="submit">Recognize text</button>
</fieldset>
</form>
<p id="status" role="status">Choose an image to begin.</p>
<label for="result">Recognized text</label>
<textarea id="result" rows="12" cols="60" readonly></textarea>
<script>
const form = document.getElementById('ocrForm')
const controls = document.getElementById('controls')
const imageInput = document.getElementById('imageInput')
const status = document.getElementById('status')
const result = document.getElementById('result')
let busy = false
async function recognizeImage(file) {
let task
let timer
let timedOut = false
try {
task = new Worker('./ocr-worker.js')
return await new Promise((resolve, reject) => {
const fail = () => reject(new Error('OCR task failed.'))
timer = setTimeout(() => {
timedOut = true
fail()
}, 90_000)
task.onerror = (event) => {
event.preventDefault()
fail()
}
task.onmessage = ({ data }) => {
if (data.type === 'result') resolve(data.text)
else if (data.type === 'error') fail()
else if (data.type === 'progress') {
status.textContent = data.status === 'recognizing text'
? 'Recognizing text… ' + Math.round(data.progress * 100) + '%'
: 'Loading OCR assets…'
}
}
task.postMessage(file)
})
} catch {
throw new Error(timedOut
? 'OCR timed out after 90 seconds. Try a smaller image or retry.'
: 'OCR failed. Check your connection and image, then try again.')
} finally {
clearTimeout(timer)
task?.terminate()
}
}
async function validateAndPerformOCR(file) {
if (!file || !['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
throw new Error('Choose a JPEG, PNG, or WebP image.')
}
if (file.size === 0 || file.size > 5 * 1024 * 1024) {
throw new Error('Choose a nonempty image of 5 MiB or smaller.')
}
return recognizeImage(file)
}
imageInput.addEventListener('change', () => {
if (busy) return
result.value = ''
status.textContent = 'Selection changed. Click Recognize text.'
})
form.addEventListener('submit', async (event) => {
event.preventDefault()
if (busy || controls.disabled) return
const file = imageInput.files[0]
busy = true
controls.disabled = true
result.value = ''
status.textContent = 'Loading OCR assets…'
try {
const text = await validateAndPerformOCR(file)
result.value = text
status.textContent = text.trim()
? 'Finished: ' + file.name + '. You can copy the text below.'
: 'No text found. Try a clearer image of printed text.'
} catch (error) {
status.textContent = error instanceof Error
? error.message
: 'OCR failed. Try another image.'
} finally {
busy = false
controls.disabled = false
}
})
if (typeof Worker === 'undefined' || typeof WebAssembly === 'undefined') {
controls.disabled = true
status.textContent = 'Use a browser with Web Workers and WebAssembly.'
}
</script>
</body>
</html>
Enregistrez ceci sous ocr-worker.js. La page possède ce worker externe et peut l’arrêter
même si Tesseract ne termine jamais son initialisation. C’est important, car un échec de
téléchargement de langue peut laisser createWorker() en attente dans la
version 7.0.0.
Son errorHandler signale directement l’échec à la page ; le délai de 90 secondes couvre
aussi un téléchargement bloqué. Ce délai est un choix propre à la démo, pas une durée de
reconnaissance attendue.
importScripts('https://cdn.jsdelivr.net/npm/tesseract.js@7.0.0/dist/tesseract.min.js')
async function performOCR(file) {
const worker = await Tesseract.createWorker('eng', 1, {
workerPath: 'https://cdn.jsdelivr.net/npm/tesseract.js@7.0.0/dist/worker.min.js',
corePath: 'https://cdn.jsdelivr.net/npm/tesseract.js-core@7.0.0',
langPath: 'https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng@1.0.0/4.0.0_best_int',
logger: ({ status, progress }) => postMessage({ type: 'progress', status, progress }),
errorHandler: () => postMessage({ type: 'error' }),
})
try {
const { data: { text } } = await worker.recognize(file)
return text
} finally {
await worker.terminate()
}
}
self.onmessage = async ({ data: file }) => {
try {
const text = await performOCR(file)
postMessage({ type: 'result', text })
} catch {
postMessage({ type: 'error' })
}
}
Depuis le répertoire parent de tesseract-browser, lancez un serveur local dans un terminal :
(cd tesseract-browser && python3 -m http.server --bind 127.0.0.1 0)
Le port 0 demande au système d’exploitation un port disponible. Ouvrez
l’adresse http://127.0.0.1:PORT/ affichée par le serveur. Passez par HTTP au lieu de
double-cliquer sur le fichier HTML, car le chargement du worker dépend de l’origine de la page.
Arrêtez le serveur avec Ctrl+C une fois terminé. Le relancer sert les mêmes fichiers sans les
écraser.
Choisissez une petite capture d’écran contenant « BROWSER OCR TEST », puis cliquez sur « Recognize text ». Vous devriez voir un retour visuel de chargement, la progression de la reconnaissance, puis les mots extraits dans « Recognized text ». Une image vide devrait plutôt afficher « No text found. » Le fichier d’origine n’est jamais modifié, et la page n’enregistre ni les images ni le texte reconnu dans un stockage.
Gestion des erreurs et validation
La limite de cinq MiB est la politique d’entrée de cette démo, pas le maximum de Tesseract. La
taille du fichier compressé ne borne pas la mémoire des pixels décodés : commencez donc avec de
petites images sur les appareils mobiles. La liste des types MIME autorisés aide à repérer une
mauvaise sélection ; un fichier corrompu étiqueté image/png doit encore échouer
pendant la reconnaissance. Ni un type MIME d’image ni
l’attribut accept du sélecteur ne prouvent
la validité du contenu.
Si l’OCR échoue, vérifiez l’image ainsi que le panneau Réseau du navigateur pour repérer des requêtes de script, de WASM ou de langue en échec, puis cliquez de nouveau sur « Recognize text ». Vous pouvez réessayer avec le même fichier. Un résultat vide n’est pas une erreur du worker, et il ne prouve pas que l’image source ne contient aucun texte. Un faible contraste, des lettres minuscules et une mauvaise langue de reconnaissance peuvent aussi produire un résultat vide ou inexact.
Gérer plusieurs langues
Pour un texte mêlant anglais et allemand, ajoutez cette fonction à ocr-worker.js et
remplacez l’appel performOCR(file) du gestionnaire par performMultilingualOCR(file). Le
tableau de langues sélectionne des modèles ; il ne traduit pas leur résultat. Cette variante
utilise les URL de langue par défaut de Tesseract afin que chaque langue puisse charger ses propres
données, au lieu du chemin fixé réservé à l’anglais de l’exemple principal.
async function performMultilingualOCR(file, languages = ['eng', 'deu']) {
const worker = await Tesseract.createWorker(languages, 1, {
workerPath: 'https://cdn.jsdelivr.net/npm/tesseract.js@7.0.0/dist/worker.min.js',
corePath: 'https://cdn.jsdelivr.net/npm/tesseract.js-core@7.0.0',
logger: ({ status, progress }) => postMessage({ type: 'progress', status, progress }),
errorHandler: () => postMessage({ type: 'error' }),
})
try {
const { data: { text } } = await worker.recognize(file)
return text
} finally {
await worker.terminate()
}
}
Optimisation des performances
Prétraitement des images
Comparez d’abord les résultats sur vos images réelles. Rogner les bordures excessives ou corriger la rotation peut aider ; réduire la taille des lettres ou augmenter le contraste peut supprimer des détails utiles. La fonction utilitaire facultative ci-dessous limite la largeur à 1 000 pixels comme compromis sur la mémoire, et non comme recommandation pour la précision.
Ajoutez-la dans le script index.html et remplacez return recognizeImage(file) dans
validateAndPerformOCR par return optimizedOCR(file). Conservez la validation avant le
prétraitement.
async function preprocessImage(file) {
const url = URL.createObjectURL(file)
try {
const img = new Image()
await new Promise((resolve, reject) => {
img.onload = resolve
img.onerror = () => reject(new Error('Unable to decode image.'))
img.src = url
})
const canvas = document.createElement('canvas')
const maxWidth = 1000
const scale = img.width > maxWidth ? maxWidth / img.width : 1
canvas.width = Math.max(1, Math.round(img.width * scale))
canvas.height = Math.max(1, Math.round(img.height * scale))
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Canvas processing is unavailable.')
ctx.filter = 'grayscale(100%) contrast(150%)'
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
return await new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob)
else reject(new Error('Unable to encode processed image.'))
}, 'image/png')
})
} finally {
URL.revokeObjectURL(url)
}
}
async function optimizedOCR(file) {
const processedImage = await preprocessImage(file)
return recognizeImage(processedImage)
}
Gestion de la mémoire
La démo à image unique crée un nouveau worker à chaque tentative pour garder une durée de vie
simple. Pour un lot, réutilisez un seul worker Tesseract initialisé et reconnaissez les images de
manière séquentielle. Cela préserve l’ordre des entrées et évite de charger un moteur d’OCR
distinct pour chaque image. La promesse de la fonction est rejetée dès la première image en échec,
et la fonction arrête son worker initialisé dans finally.
Ajoutez ceci à ocr-worker.js. Pour une expérience minimale en deux passes avec la page
actuelle, remplacez const text = await performOCR(file) du gestionnaire par
const text = (await batchProcessImages([file, file])).join('\n'). Le résultat contient deux copies,
dans l’ordre ; dans une application, transmettez plutôt votre tableau ordonné de fichiers image. Le
délai externe s’applique toujours à l’ensemble de la tâche : choisissez donc une limite de lot
adaptée avant d’étendre l’interface.
async function batchProcessImages(files) {
const worker = await Tesseract.createWorker('eng', 1, {
workerPath: 'https://cdn.jsdelivr.net/npm/tesseract.js@7.0.0/dist/worker.min.js',
corePath: 'https://cdn.jsdelivr.net/npm/tesseract.js-core@7.0.0',
langPath: 'https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng@1.0.0/4.0.0_best_int',
logger: ({ status, progress }) => postMessage({ type: 'progress', status, progress }),
errorHandler: () => postMessage({ type: 'error' }),
})
const results = []
try {
for (const file of files) {
const { data: { text } } = await worker.recognize(file)
results.push(text)
}
return results
} finally {
await worker.terminate()
}
}
Considérations de sécurité et bonnes pratiques
Conservez le texte reconnu sous forme de texte. Cet exemple l’affecte à la propriété
value d’un textarea en lecture seule, de sorte que du HTML reconnu ne peut pas
s’exécuter. Si vous déplacez le résultat dans une autre vue, ne l’insérez pas via
innerHTML.
La reconnaissance locale décrit l’endroit où ce code traite l’image, et non une garantie générale de
confidentialité pour toute page qui l’intègre. Les scripts d’une page peuvent accéder aux fichiers
sélectionnés. Examinez ces dépendances et les autres scripts de votre site avant de traiter des
documents sensibles. Si vous hébergez vous-même les ressources d’OCR, servez le WASM avec
application/wasm et conservez l’intégralité du paquet core correspondant afin que Tesseract
puisse sélectionner un build pris en charge par l’appareil.
