Фаза 04 · урок 19
OCR и понимание документов
Цель урока: Изображения, полные текста, повсюду: чеки, счета, удостоверения, отсканированные книги, формы, доски, вывески, снимки экрана. Извлечение из них структурированных данных — не только символов, но и, например, «это итоговая сумма», — одна…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Классический конвейер
- CTC в одном абзаце
- Современные сквозные модели
- Разбор макета
- Метрики оценки
- Соберите сами
- Шаг 1: Функция потерь CTC + жадный декодер
- Шаг 2: Миниатюрный распознаватель CRNN
- Шаг 3: Синтетический OCR
- Шаг 4: Набросок обучения
- Примените
- Введите в эксплуатацию
- Упражнения
- Ключевые термины
- Дополнительные материалы
OCR — это трёхэтапный конвейер: обнаружить текстовые рамки, распознать символы, затем упорядочить их на макете. Каждая современная OCR-система меняет порядок этих этапов или объединяет их.
Тип: Изучить + применить Языки: Python Предварительные знания: Фаза 4, урок 06 (обнаружение), фаза 7, урок 02 (самовнимание) Время: ~45 минут
Цели обучения
- Проследить классический конвейер OCR (обнаружение -> распознавание -> макет) и современные сквозные альтернативы (Donut, Qwen-VL-OCR)
- Реализовать функцию потерь CTC (Connectionist Temporal Classification) для обучения OCR «последовательность в последовательность»
- Использовать PaddleOCR или EasyOCR для промышленного разбора документов без обучения
- Различать OCR, разбор макета и понимание документов — и выбирать подходящий инструмент для каждой задачи
Проблема
Изображения, полные текста, повсюду: чеки, счета, удостоверения, отсканированные книги, формы, доски, вывески, снимки экрана. Извлечение из них структурированных данных — не только символов, но и, например, «это итоговая сумма», — одна из наиболее ценных прикладных задач компьютерного зрения.
Область делится на три уровня навыков:
- Собственно OCR: превратить пиксели в текст.
- Разбор макета: сгруппировать результат OCR в области (заголовок, основной текст, таблица, колонтитул).
- Понимание документов: извлечь структурированные поля (
invoice_total = $42.50) из макета.
У каждого уровня есть классические и современные подходы, а разрыв между «я хочу получить текст с изображения» и «мне нужна итоговая сумма с этого чека» больше, чем осознаёт большинство команд.
Концепция
Классический конвейер
- Обнаружение текста создаёт четырёхугольники для каждой строки или слова.
- Распознавание обрезает каждую область до фиксированной высоты, пропускает её через CNN + BiLSTM + CTC и получает последовательность символов.
- Макет восстанавливает порядок чтения (сверху вниз, слева направо для латиницы; иначе для арабского и японского языков).
CTC в одном абзаце
Распознавание OCR создаёт последовательность переменной длины из карты признаков фиксированной длины. CTC (Graves et al., 2006) позволяет обучать это без выравнивания на уровне символов. Модель выдаёт распределение по (vocab + blank) на каждом временном шаге; функция потерь CTC маргинализирует по всем выравниваниям, которые сводятся к целевому тексту после объединения повторов и удаления пустых символов.
raw output: "h h h _ _ e e l l _ l l o _ _"
after merge repeats and remove blanks: "hello"
CTC — причина, по которой CRNN работала в 2015 году и до сих пор обучает большинство промышленных OCR-моделей в 2026 году.
Современные сквозные модели
- Donut (Kim et al., 2022) — кодировщик ViT + текстовый декодер; считывает изображение и выдаёт JSON напрямую. Нет ни детектора текста, ни модуля макета.
- TrOCR — ViT + декодер transformer для OCR на уровне строк.
- Qwen-VL-OCR / InternVL — полноценные модели зрения и языка, дообученные для задач OCR; в 2026 году обеспечивают наивысшую точность на сложных документах.
- PaddleOCR — классический конвейер DB + CRNN в зрелом промышленном пакете; всё ещё главная рабочая лошадка open-source.
Сквозным моделям нужно больше данных и вычислительных ресурсов, но они избегают накопления ошибок многоэтапного конвейера.
Разбор макета
Для структурированных документов запустите детектор макета (LayoutLMv3, DocLayNet), который размечает каждую область: Title, Paragraph, Figure, Table, Footnote. Тогда порядок чтения становится таким: «перебирать области в порядке макета и конкатенировать».
Для форм используйте модели извлечения ключ—значение (Donut для визуально насыщенных документов, LayoutLMv3 для простых сканов). Они получают изображение + обнаруженный текст + позиции и предсказывают структурированные пары ключ—значение.
Метрики оценки
- Character Error Rate (CER) — расстояние Левенштейна / длина эталона. Меньше — лучше. Целевой показатель для промышленного применения: < 2% на чистых сканах.
- Word Error Rate (WER) — то же на уровне слов.
- F1 по структурированным полям — для задач «ключ—значение»; измеряет, появляется ли корректно
{invoice_total: 42.50}. - Расстояние редактирования JSON — для сквозного разбора документов; статья Donut ввела нормализованное расстояние редактирования дерева.
Соберите сами
Шаг 1: Функция потерь CTC + жадный декодер
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 использует эффективную реализацию CuDNN, когда она доступна. Жадный декодер проще поиска луча и обычно отстаёт от него по CER не более чем на 1%.
Шаг 2: Миниатюрный распознаватель CRNN
Минимальная CNN + BiLSTM для OCR строк.
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)
Вход имеет фиксированную высоту (CNN выполняет max-pooling высоты до 1). Ширина — это временное измерение для CTC.
Шаг 3: Синтетический OCR
Сгенерируйте чёрно-белые строки цифр для сквозной smoke-проверки.
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()}")
В реальном наборе данных OCR добавляются шрифты, шум, поворот, размытие и цвет. Приведённый выше конвейер остаётся тем же.
Шаг 4: Набросок обучения
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()
На этих тривиальных синтетических данных функция потерь должна снизиться примерно с 3 до 0,2 за 200 шагов.
Примените
Есть три пути для промышленного применения:
- PaddleOCR — зрелый, быстрый, многоязычный. Использование одной строкой:
paddleocr.PaddleOCR(lang="en").ocr(image_path). - EasyOCR — Python-native, многоязычный, с основой на PyTorch.
- Tesseract — классический; всё ещё полезен для старых отсканированных документов, когда модели испытывают трудности.
Для сквозного разбора документов используйте Donut или 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")
Для чеков, счетов и форм с повторяемой структурой дообучите Donut. Для произвольных документов или OCR с рассуждением текущим вариантом по умолчанию служит VLM, такой как Qwen-VL-OCR.
Введите в эксплуатацию
Этот урок создаёт:
outputs/prompt-ocr-stack-picker.md— промпт, который выбирает Tesseract / PaddleOCR / Donut / VLM-OCR по типу документа, языку и структуре.outputs/skill-ctc-decoder.md— навык, который пишет с нуля жадные и beam-search CTC-декодеры, включая нормализацию длины.
Упражнения
- (Легко) Обучите TinyCRNN на случайных числовых строках из 5 цифр в течение 500 шагов. Сообщите CER на отложенной выборке.
- (Средне) Замените жадное декодирование поиском луча (beam_width=5). Сообщите разницу CER. На каких входах поиск луча выигрывает?
- (Сложно) Используйте PaddleOCR на наборе из 20 чеков, извлеките позиции и вычислите F1 относительно вручную размеченного эталона для пар {item_name, price}.
Ключевые термины
| Термин | Что обычно говорят | Что это на самом деле означает |
|---|---|---|
| OCR | «Текст из пикселей» | Преобразование областей изображения в последовательности символов |
| CTC | «Функция потерь без выравнивания» | Функция потерь, обучающая последовательностную модель без меток для каждого временного шага; маргинализирует по выравниваниям |
| CRNN | «Классическая OCR-модель» | Свёрточный экстрактор признаков + BiLSTM + CTC; базовый подход 2015 года, всё ещё используемый в промышленности |
| Donut | «Сквозной OCR» | Кодировщик ViT + текстовый декодер; выдаёт JSON непосредственно из изображения |
| Разбор макета | «Найти области» | Обнаружить и разметить области Title/Table/Figure/Paragraph в документе |
| Порядок чтения | «Последовательность текста» | Упорядочивание распознанных областей в предложение; тривиально для латиницы, нетривиально для смешанных макетов |
| CER / WER | «Доли ошибок» | Расстояние Левенштейна / длина эталона на уровне символов или слов |
| VLM-OCR | «LLM, которая читает» | Модель зрения и языка, обученная или направляемая промптом для OCR; текущий SOTA на сложных документах |
Дополнительные материалы
- CRNN (Shi et al., 2015) — исходная архитектура CNN+RNN+CTC
- CTC (Graves et al., 2006) — исходная статья о CTC; насыщена алгоритмическими идеями
- Donut (Kim et al., 2022) — transformer для понимания документов без OCR
- PaddleOCR — open-source стек OCR для промышленного применения
Источник: OCR & Document Understanding 04.18 — Зрение с открытым словарём — CLIP · Фаза 04 — Компьютерное зрение · 04.20 — Поиск изображений и метрическое обучение · Полный каталог