Integração de OCR no navegador com Tesseract.js
Selecione uma imagem local, clique em “Recognize text” e copie o texto dela sem fazer upload da imagem. Este passo a passo oferece a desenvolvedores web um exemplo completo de Tesseract.js com feedback de carregamento, nova tentativa com o mesmo arquivo e uma tarefa de reconhecimento por vez. Comece com uma captura de tela nítida de texto impresso em inglês; o resultado do OCR ainda precisa de revisão.
O que roda no navegador
O Tesseract.js executa o mecanismo de OCR Tesseract por meio de WebAssembly em um worker. O navegador faz o reconhecimento, então a velocidade e o uso de memória dependem do dispositivo do leitor e da imagem. Não há promessa de resultado instantâneo. O escopo do projeto também exclui a entrada direta de PDF: renderize as páginas do PDF como imagens separadamente antes de reconhecê-las.
Este exemplo fixa o Tesseract.js e o core dele na versão 7.0.0 e os dados do idioma inglês na
1.0.0. Ele usa saída de texto, que vem habilitada por padrão. A API do worker
inicializa o idioma na chamada assíncrona createWorker('eng', 1, options); as etapas antigas
loadLanguage() e initialize() são desnecessárias.
Compatibilidade e requisitos do navegador
Use um navegador atual com Web Workers, workers aninhados e WebAssembly. O exemplo completo foi testado no Chromium 145 e 152 no Linux. Você também precisa do Python 3 para servir os dois arquivos localmente, além de uma conexão de rede para carregar os scripts fixados, o WASM e os dados de idioma do jsDelivr. Não é preciso build com Node.js nem instalação de pacotes.
A imagem permanece no navegador neste exemplo, mas os downloads dos recursos ainda acessam uma CDN. O cache de idiomas sozinho não faz a página funcionar offline. Uma aplicação offline também precisa servir ou armazenar em cache o HTML, os scripts, o worker, o WASM e os recursos de idioma; este passo a passo não instala um cache offline. Veja as opções de hospedagem de recursos do projeto.
Primeiros passos com o Tesseract.js
Instalação
Crie um diretório novo e vazio chamado tesseract-browser. Se esse nome já existir, escolha
outro diretório em vez de substituir os arquivos dele. Salve os dois próximos blocos como index.html
e ocr-worker.js dentro dele. São arquivos simples de navegador, sem framework nem backend.
Exemplo básico: reconhecimento de texto em uma imagem
Salve isto como index.html. O seletor de arquivos e o botão ficam desabilitados enquanto
uma tarefa está pendente. Selecionar outra imagem depois limpa o resultado anterior; clicar no botão
novamente tenta de novo com o arquivo selecionado, sem exigir um novo evento de seleção de arquivo.
<!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>
Salve isto como ocr-worker.js. A página é dona deste worker externo e pode encerrá-lo
mesmo que o Tesseract nunca termine de inicializar. Isso importa porque um download de idioma com
falha pode deixar createWorker() pendente na versão 7.0.0.
O errorHandler dele informa a falha diretamente à página; o prazo de 90 segundos também
cobre um download travado. O prazo é uma política da demonstração, não um tempo de reconhecimento
esperado.
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' })
}
}
A partir do diretório pai de tesseract-browser, inicie um servidor local em um terminal:
(cd tesseract-browser && python3 -m http.server --bind 127.0.0.1 0)
A porta 0 pede ao sistema operacional uma porta disponível. Abra o endereço
http://127.0.0.1:PORT/ exibido pelo servidor. Use HTTP em vez de clicar duas vezes no arquivo HTML,
já que o carregamento do worker depende da origem da página. Pare o servidor com Ctrl+C ao terminar.
Iniciá-lo novamente serve os mesmos arquivos sem sobrescrevê-los.
Escolha uma pequena captura de tela contendo “BROWSER OCR TEST” e clique em “Recognize text”. Você deve ver o feedback de carregamento, o progresso do reconhecimento e as palavras extraídas em “Recognized text”. Uma imagem em branco deve, em vez disso, informar “No text found”. O arquivo original nunca é modificado, e a página não salva imagens nem texto reconhecido em armazenamento.
Tratamento de erros e validação
O limite de cinco MiB é a política de entrada desta demonstração, não o máximo do Tesseract. O
tamanho do arquivo compactado não limita a memória dos pixels decodificados, então comece com
imagens pequenas em dispositivos móveis. A lista de MIME types permitidos ajuda a detectar uma
seleção errada; um arquivo corrompido rotulado como image/png ainda precisa falhar
durante o reconhecimento. Nem um MIME type de imagem nem o
atributo accept do seletor
comprovam que o conteúdo é válido.
Se o OCR falhar, verifique a imagem e o painel Rede do navegador em busca de requisições de script, WASM ou idioma com falha e clique em “Recognize text” novamente. Você pode tentar de novo com o mesmo arquivo. Um resultado em branco não é um erro do worker e não prova que a imagem de origem não contém texto. Baixo contraste, letras muito pequenas e o idioma de reconhecimento errado também podem gerar uma saída vazia ou imprecisa.
Como lidar com vários idiomas
Para texto misto em inglês e alemão, adicione esta função a ocr-worker.js e substitua a
chamada performOCR(file) do handler por performMultilingualOCR(file). O array de idiomas
seleciona modelos; ele não traduz a saída deles. Esta variação usa as URLs de idioma padrão do
Tesseract para que cada idioma possa carregar os próprios dados, em vez do caminho fixado apenas
para inglês do exemplo 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()
}
}
Otimização de desempenho
Pré-processamento de imagem
Primeiro, compare os resultados com as suas imagens reais. Recortar bordas excessivas ou corrigir a rotação pode ajudar; reduzir as letras ou aumentar o contraste pode remover detalhes úteis. O helper opcional abaixo limita a largura a 1.000 pixels como um compromisso de memória, não como uma recomendação de precisão.
Adicione-o dentro do script index.html e substitua return recognizeImage(file) em
validateAndPerformOCR por return optimizedOCR(file). Mantenha a validação antes do
pré-processamento.
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)
}
Gerenciamento de memória
A demonstração de imagem única cria um worker novo a cada tentativa para manter o ciclo de vida
simples. Para um lote, reutilize um único worker do Tesseract inicializado e reconheça as imagens em
sequência. Isso preserva a ordem de entrada e evita carregar um mecanismo de OCR separado para cada
imagem. A função rejeita a Promise retornada na primeira imagem que falhar e encerra o worker
inicializado dela em finally.
Adicione isto a ocr-worker.js. Para um experimento mínimo de duas passagens com a página
atual, substitua o const text = await performOCR(file) do handler por
const text = (await batchProcessImages([file, file])).join('\n'). A saída contém duas cópias
em ordem; em uma aplicação, passe o seu array ordenado de arquivos de imagem. O prazo externo ainda
se aplica à tarefa inteira, então escolha um limite de lote adequado antes de expandir a 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()
}
}
Considerações de segurança e boas práticas
Mantenha o texto reconhecido como texto. Este exemplo o atribui ao value de um
textarea somente leitura, então o HTML reconhecido não pode ser executado. Se você mover o resultado
para outra visualização, não o insira por meio de innerHTML.
O reconhecimento local descreve onde este código processa a imagem, não uma garantia geral de
privacidade para qualquer página que o incorpore. Scripts em uma página podem acessar os arquivos
selecionados. Revise essas dependências e os outros scripts do seu site antes de lidar com
documentos sensíveis. Se você hospedar os recursos de OCR por conta própria, sirva o WASM com
application/wasm e mantenha o pacote core correspondente completo para que o Tesseract possa
selecionar um build compatível com o dispositivo.
