Faza 04 · lecția 19
OCR și înțelegerea documentelor
Scopul lecției: OCR este un flux cu trei etape — detectați casetele de text, recunoașteți caracterele, apoi aranjați-le în pagină. Fiecare sistem OCR modern modifică ordinea acestor etape sau le combină.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- Fluxul clasic
- CTC într-un paragraf
- Modele end-to-end moderne
- Analiza aranjării în pagină
- Metrici de evaluare
- Construiți
- Pasul 1: Pierderea CTC + decodorul greedy
- Pasul 2: Un recunoscător CRNN mic
- Pasul 3: OCR sintetic
- Pasul 4: Schița antrenării
- Utilizați
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
OCR este un flux cu trei etape — detectați casetele de text, recunoașteți caracterele, apoi aranjați-le în pagină. Fiecare sistem OCR modern modifică ordinea acestor etape sau le combină.
Tip: Învățați + utilizați Limbaje: Python Cerințe prealabile: Faza 4 Lecția 06 (Detecție), Faza 7 Lecția 02 (Autoatenție) Timp: ~45 de minute
Obiective de învățare
- Urmăriți fluxul OCR clasic (detectare -> recunoaștere -> aranjare în pagină) și alternativele end-to-end moderne (Donut, Qwen-VL-OCR)
- Implementați pierderea CTC (Connectionist Temporal Classification) pentru antrenarea OCR secvență-la-secvență
- Utilizați PaddleOCR sau EasyOCR pentru analiza documentelor în producție, fără antrenare
- Deosebiți OCR, analiza aranjării în pagină și înțelegerea documentelor — și alegeți instrumentul potrivit pentru fiecare sarcină
Problema
Imaginile pline de text sunt peste tot: bonuri, facturi, acte de identitate, cărți scanate, formulare, table albe, semne, capturi de ecran. Extragerea de date structurate din ele — nu doar a caracterelor, ci și a informației „acesta este totalul” — este una dintre cele mai valoroase probleme aplicate de viziune.
Domeniul se împarte în trei niveluri de competență:
- OCR propriu-zis: transformați pixelii în text.
- Analiza aranjării în pagină: grupați ieșirea OCR în regiuni (titlu, corp, tabel, antet).
- Înțelegerea documentelor: extrageți câmpuri structurate (
invoice_total = $42.50) din aranjare.
Fiecare nivel are abordări clasice și moderne, iar distanța dintre „vreau text dintr-o imagine” și „am nevoie de suma totală din acest bon” este mai mare decât își dau seama majoritatea echipelor.
Conceptul
Fluxul clasic
- Detectarea textului produce patrulatere pentru fiecare linie sau cuvânt.
- Recunoașterea decupează fiecare regiune la o înălțime fixă, aplică un CNN + BiLSTM + CTC pentru a produce o secvență de caractere.
- Aranjarea în pagină reconstruiește ordinea de citire (de sus în jos, de la stânga la dreapta pentru scrierea latină; diferită pentru arabă, japoneză).
CTC într-un paragraf
Recunoașterea OCR produce o secvență cu lungime variabilă dintr-o hartă de caracteristici cu lungime fixă. CTC (Graves și colab., 2006) permite antrenarea fără aliniere la nivelul caracterelor. Modelul produce la fiecare pas de timp o distribuție peste (vocabular + simbol gol); pierderea CTC marginalizează peste toate alinierile care se reduc la textul-țintă după îmbinarea repetițiilor și eliminarea simbolurilor goale.
ieșire brută: "h h h _ _ e e l l _ l l o _ _"
după îmbinarea repetițiilor și eliminarea simbolurilor goale: "hello"
CTC este motivul pentru care CRNN a funcționat în 2015 și încă antrenează majoritatea modelelor OCR de producție în 2026.
Notă tehnică a traducerii: Nu există o inventariere publică care să susțină afirmația „majoritatea” pentru sistemele de producție din 2026. CTC rămâne un obiectiv important pentru recunoașterea de text, dar TrOCR și Donut sunt arhitecturi encoder–decoder autoregresive, nu modele CRNN+CTC; alegerea obiectivului trebuie evaluată pe sarcina și datele concrete.
Modele end-to-end moderne
- Donut (Kim și colab., 2022) — un encoder ViT + un decodor de text; citește o imagine și emite direct JSON. Fără detector de text, fără modul de aranjare în pagină.
- TrOCR — ViT + decodor Transformer pentru OCR la nivel de linie.
- Qwen-VL-OCR / InternVL — modele complete limbaj–viziune ajustate fin pentru sarcini OCR; cea mai bună acuratețe în 2026 pe documente complexe.
- PaddleOCR — flux clasic DB + CRNN într-un pachet matur pentru producție; rămâne instrumentul de bază open-source.
Modelele end-to-end au nevoie de mai multe date și resurse de calcul, dar evită acumularea erorilor din fluxurile cu mai multe etape.
Notă tehnică a traducerii: Formulări precum „cea mai bună acuratețe”, „instrumentul de bază” și „în 2026” nu reprezintă proprietăți universale ale acestor modele. Comparația depinde de setul de date, limbă, tipul documentului, protocolul de evaluare, constrângerile de latență și versiunea implementării.
Analiza aranjării în pagină
Pentru documente structurate, rulați un detector de aranjare (LayoutLMv3, DocLayNet) care etichetează fiecare regiune: Titlu, Paragraf, Figură, Tabel, Notă de subsol. Ordinea de citire devine apoi „iterați prin regiuni în ordinea aranjării, concatenați”.
Notă tehnică a traducerii: LayoutLMv3 este un model de preantrenare multimodală pentru documente, nu denumirea unui detector de aranjare gata de utilizare, iar DocLayNet este un set de date etichetat pentru aranjarea documentelor, nu un detector. Alegeți și, când este necesar, antrenați un model de detectare sau segmentare compatibil cu clasele, limba și tipul documentelor-țintă.
Pentru formulare, utilizați modele de extracție cheie–valoare (Donut pentru documente bogate vizual, LayoutLMv3 pentru scanări simple). Acestea primesc imaginea + textul detectat + pozițiile și prezic perechi cheie–valoare structurate.
Notă tehnică a traducerii: Această propoziție combină interfețe diferite. Donut este un model OCR-free imagine-la-secvență și nu necesită text detectat sau casete de încadrare ca intrare obligatorie; fluxurile bazate pe LayoutLMv3 folosesc, în mod tipic, tokeni, casete de încadrare și imagini. Verificați contractul exact al checkpointului și al procesorului ales.
Metrici de evaluare
- Rata erorii la nivel de caracter (CER) — distanța Levenshtein / lungimea referinței. Mai mic este mai bine. Ținta de producție: < 2% pentru scanări curate.
- Rata erorii la nivel de cuvânt (WER) — aceeași măsură la nivelul cuvintelor.
- F1 pe câmpuri structurate — pentru sarcini cheie–valoare; măsoară dacă
{invoice_total: 42.50}apare corect. - Distanța de editare pe JSON — pentru analiza end-to-end a documentelor; lucrarea Donut a introdus distanța de editare a arborilor normalizată.
Notă tehnică a traducerii: O țintă CER sub 2% nu este un prag universal de producție. Ea trebuie stabilită printr-un set de evaluare reprezentativ, cu distribuția reală de limbi, calitatea imaginilor, vocabular, cerințe de câmpuri și costurile erorilor.
Construiți
Pasul 1: Pierderea CTC + decodorul greedy
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 folosește implementarea eficientă CuDNN când este disponibilă. Decodorul greedy este mai simplu decât o căutare beam și se află, de regulă, la mai puțin de 1% CER de aceasta.
Notă tehnică a traducerii: Utilizarea cuDNN de către
F.ctc_lossnu este garantată numai de disponibilitatea bibliotecii. Documentația PyTorch impune condiții suplimentare, inclusivblank=0, ținte concatenate, lungimi-țintă de cel mult 256 și lungimi de tipint32; verificați backendul și versiunea efectiv folosite. De asemenea, nu există o limită universală de 1% CER între decodarea greedy și beam search: diferența depinde de model, limbaj, lexicon, scorare și parametrii căutării.
Pasul 2: Un recunoscător CRNN mic
CNN + BiLSTM minimal pentru OCR de linii.
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)
Intrarea cu înălțime fixă (CNN-ul aplică max-pooling asupra înălțimii până la 1). Lățimea este dimensiunea temporală pentru CTC.
Notă tehnică a traducerii: Pentru intrarea de înălțime 32 din acest exemplu, cele patru operații de pooling produc înălțimea 2, nu 1; apelul
f.mean(dim=2)este cel care agregă ulterior această dimensiune. Forma exactă depinde de înălțimea de intrare și de parametrii straturilor.
Pasul 3: OCR sintetic
Generați șiruri de cifre negre pe fundal alb pentru un test de fum end-to-end.
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()}")
Un set de date OCR real adaugă fonturi, zgomot, rotație, neclaritate și culoare. Fluxul de mai sus este identic.
Notă tehnică a traducerii: În codul demonstrativ, toate caracterele alfanumerice sunt desenate ca același dreptunghi negru, astfel că imaginea nu codifică identitatea caracterului. Nu este un test end-to-end valid al recunoașterii OCR și nu poate justifica o țintă CER; pentru aceasta, redați glife distincte (de exemplu, cu fonturi) sau atribuiți fiecărui caracter un tipar unic.
Pasul 4: Schița antrenării
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()
Pierderea ar trebui să scadă de la ~3 la ~0,2 în 200 de pași pe aceste date sintetice triviale.
Notă tehnică a traducerii: Din cauza reprezentării sintetice identice a caracterelor, această valoare a pierderii nu este un rezultat reproductibil al codului furnizat. Chiar și după corectarea generatorului, curba de antrenare depinde de inițializare, optimizator, lot, aleatoritate și implementare.
Utilizați
Trei opțiuni de producție:
- PaddleOCR — matur, rapid, multilingv. Utilizare într-o singură linie:
paddleocr.PaddleOCR(lang="en").ocr(image_path). - EasyOCR — nativ Python, multilingv, cu backbone PyTorch.
- Tesseract — clasic; încă util pentru documentele vechi scanate când modelele întâmpină dificultăți.
Pentru analiza end-to-end a documentelor, utilizați Donut sau un 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")
Pentru bonuri, facturi și formulare cu structură repetabilă, realizați ajustarea fină a Donut. Pentru documente arbitrare sau OCR cu raționament, un VLM precum Qwen-VL-OCR este opțiunea implicită actuală.
Notă tehnică a traducerii: Nu există un „implicit” universal pentru VLM-uri în OCR. Înainte de alegere, comparați pe documente reprezentative exactitatea textului și a câmpurilor, costul, latența, confidențialitatea, limba și comportamentul la documente rare; un flux OCR specializat poate fi mai potrivit decât un VLM generalist.
Livrați
Această lecție produce:
outputs/prompt-ocr-stack-picker.md— un prompt care alege Tesseract / PaddleOCR / Donut / VLM-OCR în funcție de tipul documentului, limbă și structură.outputs/skill-ctc-decoder.md— o competență care scrie de la zero decodoare CTC greedy și cu căutare beam, inclusiv normalizarea lungimii.
Exerciții
- (Ușor) Antrenați TinyCRNN pe șiruri numerice aleatoare de cinci cifre timp de 500 de pași. Raportați CER pe un set păstrat pentru evaluare.
- (Mediu) Înlocuiți decodarea greedy cu căutarea beam (
beam_width=5). Raportați diferența CER. Pentru ce intrări câștigă căutarea beam? - (Dificil) Utilizați PaddleOCR pe un set de 20 de bonuri, extrageți articolele de pe bon și calculați F1 față de adevărul de referință etichetat manual pentru perechile
{item_name, price}.
Termeni-cheie
| Termen | Cum este numit în practică | Ce înseamnă de fapt |
|---|---|---|
| OCR | „Text din pixeli” | Transformarea regiunilor de imagine în secvențe de caractere |
| CTC | „Pierdere fără aliniere” | Pierdere care antrenează un model secvențial fără etichete la fiecare pas de timp; marginalizează peste alinieri |
| CRNN | „Model OCR clasic” | Extractor convoluțional de caracteristici + BiLSTM + CTC; referința din 2015 încă utilizată în producție |
| Donut | „OCR end-to-end” | Encoder ViT + decodor de text; emite JSON direct din imagine |
| Analiza aranjării în pagină | „Găsiți regiuni” | Detectarea și etichetarea regiunilor Titlu/Tabel/Figură/Paragraf dintr-un document |
| Ordinea de citire | „Secvența de text” | Ordonarea regiunilor recunoscute într-o propoziție; trivială pentru scrierea latină, netrivială pentru aranjări mixte |
| CER / WER | „Rate de eroare” | Distanța Levenshtein / lungimea referinței la granularitate de caracter sau de cuvânt |
| VLM-OCR | „LLM care citește” | Un model limbaj–viziune antrenat sau ghidat prin prompt pentru sarcini OCR; SOTA actuală pe documente complexe |
Lecturi suplimentare
- CRNN (Shi et al., 2015) — arhitectura originală CNN+RNN+CTC
- CTC (Graves et al., 2006) — lucrarea originală despre CTC; densă în idei algoritmice
- Donut (Kim et al., 2022) — Transformer pentru înțelegerea documentelor fără OCR
- PaddleOCR — fluxul OCR open-source pentru producție
Sursă: Originalul în limba engleză
Navigare: ← Lecția 04.18 — Viziune cu vocabular deschis — CLIP · Faza 4 — Viziune computerizată · Lecția 04.20 — Regăsirea imaginilor și învățarea metrică → · Catalog complet