Phase 04 - Lesson 19
OCR e Compreensão de Documentos
OCR e um pipeline de tres estagios: detectar caixas de texto, reconhecer os caracteres e, em seguida, organizar seu layout. Todo sistema moderno de OCR reordena esses estagios ou os funde.
Type: Learn + Use Languages: Python Prerequisites: Fase 4 Licao 06 (Deteccao), Fase 7 Licao 02 (Auto-atencao) Time: ~45 minutos
Objetivos de Aprendizagem
- Percorrer o pipeline classico de OCR (detectar -> reconhecer -> layout) e as alternativas modernas ponta a ponta (Donut, Qwen-VL-OCR)
- Implementar a perda CTC (Connectionist Temporal Classification) para treinamento de OCR sequencia a sequencia
- Usar PaddleOCR ou EasyOCR para analise de documentos em producao sem treinamento
- Distinguir OCR, analise de layout e compreensao de documentos, e escolher a ferramenta certa para cada tarefa
O Problema
Imagens cheias de texto estao em toda parte: recibos, faturas, documentos de identidade, livros digitalizados, formularios, quadros brancos, placas, capturas de tela. Extrair dados estruturados delas, nao apenas os caracteres, mas "este e o valor total", e um dos problemas de visao aplicada de maior valor.
A area se divide em tres camadas de habilidade:
- OCR propriamente dito: transformar pixels em texto.
- Analise de layout: agrupar a saida do OCR em regioes (titulo, corpo, tabela, cabecalho).
- Compreensao de documentos: extrair campos estruturados ("invoice_total = $42.50") a partir do layout.
Cada camada tem abordagens classicas e modernas, e a distancia entre "quero o texto de uma imagem" e "preciso do valor total deste recibo" e maior do que a maioria das equipes imagina.
O Conceito
O pipeline classico
flowchart LR
IMG["Imagem"] --> DET["Deteccao de texto<br/>(DB, EAST, CRAFT)"]
DET --> BOX["Caixas delimitadoras<br/>de palavra/linha"]
BOX --> CROP["Recortar cada regiao"]
CROP --> REC["Reconhecimento<br/>(CRNN + CTC)"]
REC --> TXT["Strings de texto"]
TXT --> LAY["Ordenacao<br/>de layout"]
LAY --> OUT["Texto em ordem de leitura"]
style DET fill:#dbeafe,stroke:#2563eb
style REC fill:#fef3c7,stroke:#d97706
style OUT fill:#dcfce7,stroke:#16a34a
- A deteccao de texto produz quadrilateros por linha ou por palavra.
- O reconhecimento recorta cada regiao para uma altura fixa e executa uma CNN + BiLSTM + CTC para produzir uma sequencia de caracteres.
- O layout reconstroi a ordem de leitura (de cima para baixo, da esquerda para a direita no alfabeto latino; diferente para arabe e japones).
CTC em um paragrafo
O reconhecimento de OCR produz uma sequencia de comprimento variavel a partir de um mapa de caracteristicas de comprimento fixo. O CTC (Graves et al., 2006) permite treinar isso sem alinhamento em nivel de caractere. O modelo gera uma distribuicao sobre (vocabulario + blank) em cada passo de tempo; a perda CTC marginaliza sobre todos os alinhamentos que se reduzem ao texto-alvo apos fundir repeticoes e remover os blanks.
raw output: "h h h _ _ e e l l _ l l o _ _"
after merge repeats and remove blanks: "hello"
O CTC e a razao pela qual a CRNN funcionou em 2015 e ainda treina a maioria dos modelos de OCR de producao em 2026.
Modelos modernos ponta a ponta
- Donut (Kim et al., 2022): um codificador ViT + um decodificador de texto; le uma imagem e emite JSON diretamente. Sem detector de texto, sem modulo de layout.
- TrOCR: ViT + decodificador transformer para OCR em nivel de linha.
- Qwen-VL-OCR / InternVL: modelos de visao e linguagem completos ajustados para tarefas de OCR; melhor precisao em 2026 em documentos complexos.
- PaddleOCR: pipeline classico DB + CRNN em um pacote de producao maduro; ainda o cavalo de batalha de codigo aberto.
Modelos ponta a ponta precisam de mais dados e computacao, mas evitam o acumulo de erros dos pipelines de varios estagios.
Analise de layout
Para documentos estruturados, execute um detector de layout (LayoutLMv3, DocLayNet) que rotula cada regiao: Titulo, Paragrafo, Figura, Tabela, Nota de rodape. A ordem de leitura entao se torna "iterar pelas regioes na ordem do layout e concatenar".
Para formularios, use modelos de extracao Chave-Valor (Donut para documentos visualmente ricos, LayoutLMv3 para digitalizacoes simples). Eles recebem imagem + texto detectado + posicoes e preveem pares chave-valor estruturados.
Metricas de avaliacao
- Taxa de Erro de Caractere (CER): distancia de Levenshtein / comprimento da referencia. Quanto menor, melhor. Meta de producao: < 2% em digitalizacoes limpas.
- Taxa de Erro de Palavra (WER): o mesmo em nivel de palavra.
- F1 em campos estruturados: para tarefas chave-valor; mede se
{invoice_total: 42.50}aparece corretamente. - Distancia de edicao em JSON: para analise de documentos ponta a ponta; o artigo do Donut introduziu a distancia de edicao de arvore normalizada.
Construa
Passo 1: perda CTC + decodificador guloso
import torch
import torch.nn as nn
import torch.nn.functional as F
def ctc_loss(log_probs, targets, input_lengths, target_lengths, blank=0):
"""
log_probs: (T, N, C) log-softmax over vocab including blank at index 0
targets: (N, S) int targets (no blanks)
input_lengths: (N,) per-sample time steps used
target_lengths: (N,) per-sample target length
"""
return F.ctc_loss(log_probs, targets, input_lengths, target_lengths,
blank=blank, reduction="mean", zero_infinity=True)
def greedy_ctc_decode(log_probs, blank=0):
"""
log_probs: (T, N, C) log-softmax
returns: list of index sequences (blanks removed, repeats merged)
"""
preds = log_probs.argmax(dim=-1).transpose(0, 1).cpu().tolist()
out = []
for seq in preds:
decoded = []
prev = None
for idx in seq:
if idx != prev and idx != blank:
decoded.append(idx)
prev = idx
out.append(decoded)
return out
F.ctc_loss usa a implementacao eficiente do CuDNN quando disponivel. O decodificador guloso e mais simples do que uma busca em feixe (beam search) e geralmente fica a 1% de CER dela.
Passo 2: reconhecedor CRNN minusculo
CNN + BiLSTM minimo para OCR de linha.
class TinyCRNN(nn.Module):
def __init__(self, vocab_size=40, hidden=128, feat=32):
super().__init__()
self.cnn = nn.Sequential(
nn.Conv2d(1, feat, 3, 1, 1), nn.BatchNorm2d(feat), nn.ReLU(inplace=True),
nn.MaxPool2d(2),
nn.Conv2d(feat, feat * 2, 3, 1, 1), nn.BatchNorm2d(feat * 2), nn.ReLU(inplace=True),
nn.MaxPool2d(2),
nn.Conv2d(feat * 2, feat * 4, 3, 1, 1), nn.BatchNorm2d(feat * 4), nn.ReLU(inplace=True),
nn.MaxPool2d((2, 1)),
nn.Conv2d(feat * 4, feat * 4, 3, 1, 1), nn.BatchNorm2d(feat * 4), nn.ReLU(inplace=True),
nn.MaxPool2d((2, 1)),
)
self.rnn = nn.LSTM(feat * 4, hidden, bidirectional=True, batch_first=True)
self.head = nn.Linear(hidden * 2, vocab_size)
def forward(self, x):
# x: (N, 1, H, W)
f = self.cnn(x) # (N, C, H', W')
f = f.mean(dim=2).transpose(1, 2) # (N, W', C)
h, _ = self.rnn(f)
return F.log_softmax(self.head(h).transpose(0, 1), dim=-1) # (W', N, vocab)
Entrada de altura fixa (a CNN faz max-pool da altura ate 1). A largura e a dimensao temporal para o CTC.
Passo 3: OCR sintetico
Gere strings de digitos pretos sobre branco para um teste rapido ponta a ponta.
import numpy as np
def synthetic_line(text, height=32, char_width=16):
W = char_width * len(text)
img = np.ones((height, W), dtype=np.float32)
for i, c in enumerate(text):
x = i * char_width
shade = 0.0 if c.isalnum() else 0.5
img[6:height - 6, x + 2:x + char_width - 2] = shade
return img
def build_batch(strings, vocab):
H = 32
W = 16 * max(len(s) for s in strings)
imgs = np.ones((len(strings), 1, H, W), dtype=np.float32)
target_lengths = []
targets = []
for i, s in enumerate(strings):
imgs[i, 0, :, :16 * len(s)] = synthetic_line(s)
ids = [vocab.index(c) for c in s]
targets.extend(ids)
target_lengths.append(len(ids))
return torch.from_numpy(imgs), torch.tensor(targets), torch.tensor(target_lengths)
vocab = ["_"] + list("0123456789abcdefghijklmnopqrstuvwxyz")
imgs, targets, lengths = build_batch(["hello", "world"], vocab)
print(f"images: {imgs.shape} targets: {targets.shape} lengths: {lengths.tolist()}")
Um conjunto de dados de OCR real adiciona fontes, ruido, rotacao, desfoque e cor. O pipeline acima e identico.
Passo 4: esboco de treinamento
model = TinyCRNN(vocab_size=len(vocab))
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
for step in range(200):
strings = ["abc" + str(step % 10)] * 4 + ["xyz" + str((step + 1) % 10)] * 4
imgs, targets, target_lens = build_batch(strings, vocab)
log_probs = model(imgs) # (W', 8, vocab)
input_lens = torch.full((8,), log_probs.size(0), dtype=torch.long)
loss = ctc_loss(log_probs, targets, input_lens, target_lens, blank=0)
opt.zero_grad(); loss.backward(); opt.step()
A perda deve cair de ~3 para ~0.2 ao longo de 200 passos nesses dados sinteticos triviais.
Use
Tres caminhos para producao:
- PaddleOCR: maduro, rapido, multilingue. Uso em uma linha:
paddleocr.PaddleOCR(lang="en").ocr(image_path). - EasyOCR: nativo em Python, multilingue, backbone em PyTorch.
- Tesseract: classico; ainda util para documentos digitalizados antigos quando os modelos tem dificuldade.
Para analise de documentos ponta a ponta, use Donut ou um VLM:
from transformers import DonutProcessor, VisionEncoderDecoderModel
processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
Para recibos, faturas e formularios com estrutura repetivel, ajuste o Donut. Para documentos arbitrarios ou OCR com raciocinio, um VLM como o Qwen-VL-OCR e o padrao atual.
Entregue
Esta licao produz:
outputs/prompt-ocr-stack-picker.md: um prompt que escolhe Tesseract / PaddleOCR / Donut / VLM-OCR dado o tipo de documento, idioma e estrutura.outputs/skill-ctc-decoder.md: uma skill que escreve decodificadores CTC guloso e por busca em feixe do zero, incluindo normalizacao por comprimento.
Exercicios
- (Facil) Treine a TinyCRNN em strings numericas aleatorias de 5 digitos por 500 passos. Reporte o CER em um conjunto de teste separado.
- (Medio) Substitua a decodificacao gulosa por busca em feixe (beam_width=5). Reporte a variacao de CER. Em quais entradas a busca em feixe vence?
- (Dificil) Use PaddleOCR em um conjunto de 20 recibos, extraia os itens de linha e calcule o F1 em relacao a uma verdade de referencia rotulada a mao para pares {item_name, price}.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| OCR | "Texto a partir de pixels" | Transformar regioes de imagem em sequencias de caracteres |
| CTC | "Perda sem alinhamento" | Perda que treina um modelo de sequencia sem rotulos por passo de tempo; marginaliza sobre os alinhamentos |
| CRNN | "Modelo classico de OCR" | Extrator de caracteristicas conv + BiLSTM + CTC; o baseline de 2015 ainda usado em producao |
| Donut | "OCR ponta a ponta" | Codificador ViT + decodificador de texto; emite JSON diretamente da imagem |
| Analise de layout | "Encontrar regioes" | Detectar e rotular regioes de Titulo/Tabela/Figura/Paragrafo em um documento |
| Ordem de leitura | "Sequencia de texto" | Ordenacao das regioes reconhecidas em uma frase; trivial para o latim, nao trivial para layouts mistos |
| CER / WER | "Taxas de erro" | Distancia de Levenshtein / comprimento da referencia em granularidade de caractere ou palavra |
| VLM-OCR | "LLM que le" | Um modelo de visao e linguagem treinado ou induzido para tarefas de OCR; o SOTA atual em documentos complexos |
Leitura Complementar
- CRNN (Shi et al., 2015): a arquitetura original CNN+RNN+CTC
- CTC (Graves et al., 2006): o artigo original do CTC; densamente repleto das ideias algoritmicas
- Donut (Kim et al., 2022): transformer de compreensao de documentos sem OCR
- PaddleOCR: a stack de OCR de producao de codigo aberto