← Все статьи

Как научить нейросеть читать рукописные цифры: от CNN до TrOCR

Практический путь от классификации одной цифры до распознавания последовательностей на реальных бланках: CNN, CTC, CRNN, TrOCR, разметка из базы, эксперименты и production-пайплайн без лишней модели.

Как научить нейросеть читать рукописные цифры: от CNN до TrOCR
Содержание

Когда говорят «OCR», обычно представляют печатный текст: договор, счёт, скан паспорта. Рукописные цифры в ячейке таблицы — другая задача: другой шум, другой алфавит, другая постановка. Одна картинка может означать класс 7, а может — строку 783. От выбора формулировки зависит, нужен ли вам простой классификатор, CRNN с CTC или полноразмерный TrOCR.

Ниже — практический разбор для инженеров, которые строят распознавание на реальных фотографиях бланков, а не на датасете MNIST. Мы пройдём путь от геометрии документа до сравнения архитектур, разберём, как получить разметку из базы данных, и покажем, почему самая большая модель редко оказывается лучшим первым шагом. Материал дополняет контур OCR в загрузке документов для RAG — там фокус на корпоративных PDF, здесь — на узкой, но типичной задаче полевого ввода.

Ключевые выводы

OCR — не одна задача. Классификация одной цифры и распознавание последовательности требуют разных моделей, метрик и данных.

Структура документа важнее нейросети. Если шаблон бланка известен, сначала выравниваете геометрию и режете ячейки — а не скармливаете целую страницу универсальному OCR.

Лучший датасет — production-подобный. Высокая точность на MNIST не переносится на фотографии с перспективой, тенями и линиями таблицы.

Разметка из базы может быть ценнее ручной. Когда фото связано с записью в системе, ground truth уже есть — остаётся построить конвейер cell image → label.

CNN — сильный baseline. Для одной цифры в известной ячейке маленькая сверточная сеть часто быстрее, дешевле и понятнее, чем Transformer.

CRNN + CTC — естественный шаг для последовательностей. Несколько цифр в ячейке без посимвольной разметки — классическая постановка Connectionist Temporal Classification.

TrOCR оправдан не всегда. Универсальная модель имеет смысл при сложном тексте и расширении алфавита; для десяти цифр в фиксированной ячейке специализированное решение часто выигрывает.

Production — это пайплайн. Детекция документа, коррекция перспективы, правила валидации и human-in-the-loop не менее важны, чем выбор архитектуры.

Две разные задачи под одним словом «OCR»

Прежде чем сравнивать CNN и TrOCR, нужно зафиксировать, что именно вы распознаёте.

Классификация изображения

Одна обрезанная ячейка содержит одну цифру: картинка [ 7 ] → класс 7. Это классификация на 10 классов (09). Модель выдаёт распределение вероятностей по фиксированному набору меток. Длина ответа всегда равна одному символу.

Такая постановка возможна, когда шаблон бланка гарантирует: в конкретном поле всегда одна цифра, или вы заранее режете область так, что в кадре остаётся один символ.

Распознавание последовательности

Другая ячейка содержит несколько цифр: [ 7 8 3 ] → строка "783". Модель должна определить символы, их порядок и количество. Количество классов на выходе больше не фиксировано одним числом — нужен механизм декодирования последовательности.

Это принципиально другая задача. Классификатор, обученный на одиночных цифрах, не «додумает» порядок трёх символов без отдельного этапа сегментации. А сегментация рукописных цифр на реальном бланке — отдельная головная боль.

Почему MNIST — плохой прокси для реальных бланков

MNIST — 60 000 обучающих изображений рукописных цифр, 10 классов, одна цифра на картинку, нормализованный размер 28×28, контролируемые условия съёмки. Для обучения основ computer vision датасет отличный. Для обещаний заказчику — опасный.

Реальные фотографии заполненных бланков добавляют факторы, которых в MNIST нет:

Фактор Что ломает
Перспектива и поворот Цифра искажена, пропорции не совпадают с обучающей выборкой
Неравномерное освещение Тени, блики, локальный контраст
Сетка таблицы Линии пересекают штрихи цифр
Фон бумаги Текстура, пятна, сгибы
Разная толщина пера Тонкие и жирные штрихи в одном датасете
Разные почерки и размеры Один и тот же класс 3 выглядит по-разному
Качество камеры Шум, размытие, сжатие JPEG

Это domain gap: модель, натренированная на MNIST, может показать 99% accuracy на тесте датасета и провалиться на фотографиях с телефона оператора.

Главный принцип прост: лучший датасет для вашей задачи — данные, максимально похожие на production. Если в проде — фото бланков с полей, тренировочная выборка должна состоять из таких же фото, а не из нормализованных глифов 28×28.

Первый этап OCR — не нейросеть

Типичная ошибка — сразу искать «лучшую OCR-модель» и скармливать ей целую фотографию. На структурированных бланках выигрывает другой порядок:

фотография → обнаружение бланка → коррекция перспективы →
привязка к шаблону → выделение ячеек → предобработка → OCR

Почему известный шаблон — огромное преимущество

Если форма бланка фиксирована:

  • координаты полей известны заранее или вычисляются после выравнивания;
  • строки и столбцы таблицы предсказуемы;
  • не нужно распознавать всю страницу целиком.

Вместо фотография → большая OCR-модель → весь текст работает схема фотография → geometry → cell → маленькая OCR-модель. Каждая ячейка — отдельный вход с маленьким алфавитом и ограниченной длиной. Задача упрощается на порядок.

Это тот же инженерный приём, что и в промышленной загрузке документов: сначала структура и канон, потом извлечение смысла. Для бланков «канон» — выровненное изображение с вырезанными ячейками.

OCR только там, где он нужен

Универсальный OCR по всей странице тратит вычисления на заголовки, подписи, печатный текст и пустые поля. На фиксированном шаблоне разумнее:

  1. Найти углы бланка (контуры, маркеры, QR).
  2. Применить гомографию — выровнять перспективу.
  3. Наложить маску шаблона.
  4. Вырезать только ячейки с рукописными показаниями.
  5. Запустить узкую модель на каждой ячейке.

Самая ценная часть — ground truth

Без честной разметки любой эксперимент с архитектурами — сравнение шума.

Откуда взять правильные ответы

Во многих прикладных системах фотография бланка уже связана с записью в базе. Оператор сфотографировал показания счётчика, а правильное значение лежит в таблице readings. Или диспетчер ввёл данные вручную после звонка — и фото служит подтверждением.

В таком случае разметка не требует армии разметчиков. Нужен конвейер:

фотография → идентификация бланка/сессии → запрос в БД →
правильное значение поля → пара (cell image, label)

Примеры:

  • cell_00001.png → "78"
  • cell_00002.png → "73"
  • cell_00003.png → "71"

Почему это лучше ручной разметки

  • Масштаб: тысячи примеров собираются автоматически по мере работы системы.
  • Меньше ошибок: человек не переписывает цифры вручную в отдельный файл.
  • Актуальность: датасет обновляется вместе с потоком документов.
  • Связь с бизнес-контекстом: метки соответствуют тому, что система считает истиной.

Оговорка: ground truth из БД честен только если запись в базе действительно верна. Если операторы систематически ошибаются при вводе, модель выучит те же ошибки.

Data leakage — тихий убийца метрик

При разбиении train/validation/test нельзя случайно смешивать:

  • разные кропы одного и того же бланка;
  • фотографии одного человека с почти идентичным почерком;
  • серии снимков, сделанных в одинаковых условиях освещения.

Случайное перемешивание без группировки даст оптимистичные 98% на тесте и разочарование в проде. Группируйте по документу, автору, сессии съёмки или дате — в зависимости от того, что реально «новое» в production.

Подход к оценке качества ИИ-систем в целом — в материале «Оценка корпоративного ИИ»; для OCR критичны именно групповые сплиты и метрики на уровне последовательности.

Первая модель — CNN как baseline

Convolutional Neural Network — сверточная нейронная сеть. Для задачи «одна цифра в ячейке» это естественная отправная точка.

Как CNN видит цифру

Иерархия признаков типична:

  1. Нижние слои — края, линии, углы.
  2. Средние — фрагменты штрихов, дуги, пересечения.
  3. Верхние — целый глиф, похожий на класс.

Схема: изображение → свёртки → пулинг → признаки → полносвязный слой → 10 классов.

Почему CNN — хороший baseline

Критерий CNN
Размер модели Десятки–сотни КБ – несколько МБ
Время обучения Минуты–часы на GPU, часы на CPU
Inference Миллисекунды на ячейку
Отладка Confusion matrix, визуализация ошибок
Деплой Легко упаковать в ONNX, TFLite, edge

На первом эксперименте измерьте не только accuracy, но и confusion matrix, время inference, размер артефакта. Эти цифры станут аргументом против «давайте сразу TrOCR» на совещании.

Минимальный эксперимент

# Псевдокод: классификатор 10 классов
model = Sequential([
    Conv2D(32, 3, activation='relu'),
    MaxPooling2D(),
    Conv2D(64, 3, activation='relu'),
    MaxPooling2D(),
    Flatten(),
    Dense(128, activation='relu'),
    Dense(10, activation='softmax'),
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

Для production добавьте нормализацию размера ячейки, инверсию при тёмном фоне и логирование уверенности (softmax probability).

Несколько цифр в одной ячейке

Ячейка [ 78 ] ломает постановку «один класс на картинку». Наивный путь:

78 → сегментация → 7 | 8 → два прогона CNN → "78"

Почему сегментация — отдельная проблема

  • Цифры разного размера в одной ячейке.
  • Расстояние между символами непостоянно.
  • Символы соприкасаются или перекрываются.
  • Линии таблицы проходят через штрихи.
  • Почерк не соблюдает моноширинную сетку.

Каждый из этих факторов добавляет этап, который сам по себе требует разметки и метрик. Проще спросить: можно ли распознать всю последовательность сразу? Здесь появляется CTC.

CTC: последовательность без посимвольной разметки

Connectionist Temporal Classification учит соответствие между изображением и строкой без разметки границ каждого символа. Достаточно пары целая ячейка → "783".

Интуиция

Модель на каждом временном шаге выдаёт распределение по символам плюс специальный токен blank (пустой шаг). Декодер CTC схлопывает повторы и убирает blank:

сырой выход: 7 → 7 → blank → 8 → 8 → 8 → blank → 3
после CTC:   7 → 8 → 3

Вам не нужно вручную рисовать bounding box вокруг каждой цифры.

Ограничения CTC

  • Последовательность обычно моделируется слева направо (для цифр в ячейке это чаще всего ок).
  • Двумерная структура текста (многострочные блоки) CTC переносит хуже, чем seq2seq с attention.
  • Длина входной последовательности признаков должна быть сопоставима с длиной строки — для коротких чисел в ячейке ограничение редко мешает.

CRNN — классический рабочий конь OCR

Convolutional Recurrent Neural Network объединяет свёртки, рекуррентные слои и CTC:

image → CNN → feature map → sequence → BiLSTM → CTC → "783"

Зачем RNN между CNN и CTC

Текст имеет порядок: 7 затем 8 затем 3. Feature map после CNN можно разрезать на полосы слева направо; каждая полоса — шаг времени. BiLSTM смотрит в обе стороны и уточняет, какой символ вероятен с учётом соседей.

Плюсы и минусы

Плюсы Минусы
Компактная модель RNN медленнее параллельных Transformer на длинных последовательностях
Хорошо изучен, много рецептов Сложнее тюнить, чем простой CNN
Работает на CPU для коротких строк Хуже масштабируется на свободный текст
Прямая тренировка end-to-end с CTC Менее гибок при смене алфавита, чем generative decoder

Для ячеек с двумя–пятью цифрами CRNN + CTC остаётся сильным выбором в 2026 году — особенно если важны размер модели и inference на CPU.

TrOCR — когда в игру входит Transformer

TrOCR (Transformer-based OCR) — end-to-end модель: Vision Transformer кодирует изображение, Transformer decoder генерирует текст посимвольно.

image → ViT encoder → visual tokens → text decoder → "783"

CRNN против TrOCR

CRNN + CTC TrOCR
Энкодер CNN Vision Transformer
Декодер CTC (выравнивание) Autoregressive Transformer
Предобучение Обычно с нуля на своих данных Доступны веса на печатном/рукописном тексте
Размер Малый–средний Small / Base / Large — от десятков МБ до сотен
Сильная сторона Короткие цифровые строки Сложный текст, смешанный алфавит

Fine-tuning pretrained

Типичный рецепт: взять microsoft/trocr-base-handwritten или аналог, дообучить на своих ячейках с цифрами. Pretrained знания о штрихах и кривых переносятся; последние слои адаптируются к вашему домену — линиям таблицы, шуму камеры, локальному почерку.

Важно: больше параметров ≠ лучше на вашей выборке. Large-модель может переобучиться на тысяче ячеек, тогда как Small даст ту же CER при inference в десять раз быстрее.

Почему TrOCR может быть избыточен

Главный тезис: если задача формулируется как image → "7" в известной ячейке с алфавитом из десяти цифр, полноразмерный OCR-Transformer — стрельба из пушки по воробьям.

TrOCR становится интереснее, когда:

  • в ячейке переменная длина (7835, 12, 0);
  • алфавит расширяется (буквы, десятичная точка, знак минус);
  • качество съёмки сильно варьирует и pretrained даёт заметный перенос;
  • планируется единая модель на цифры и короткий текст в соседних полях.

Если же:

  • шаблон фиксирован;
  • алфавит — только 0–9;
  • длина ограничена (например, не больше пяти символов);
  • область текста вырезана геометрией,

то специализированная CRNN или даже каскад CNN часто выигрывает по latency, стоимости и простоте сопровождения.

Эксперимент: CNN vs CRNN vs TrOCR на одном датасете

Сравнение архитектур имеет смысл только на одинаковых данных и с честным сплитом.

Разбиение выборки

Типичное соотношение: 70% train, 15% validation, 15% test — но внутри групп:

  • по document_id (все ячейки одного бланка — в одном сплите);
  • по author_id / оператору;
  • по серии бланков или дате съёмки.

Случайное перемешивание ячеек без группировки завышает метрики.

Метрики

Для одной цифры (CNN):

  • Accuracy;
  • Precision / Recall по классам (особенно для редких 0 и 1);
  • Confusion matrix.

Для последовательности (CRNN, TrOCR):

  • CER (Character Error Rate) — доля неверных символов;
  • Exact Match Accuracy — доля полностью верных строк;
  • Sequence Accuracy — то же, с явным именем в отчётах.

Инженерные метрики (часто решают спор на проде):

Метрика Зачем
Размер модели Деплой на edge, мобильное приложение
Время inference на CPU/GPU Пропускная способность на смену
VRAM / RAM Батч на сервере
Throughput Ячеек в секунду на линии
Время обучения Как быстро переобучить после смены бланка

Зафиксируйте железо и batch size в отчёте — иначе сравнение бессмысленно.

Анализ ошибок важнее одной цифры accuracy

accuracy = 98.7% звучит хорошо, пока не откроете confusion matrix. Типичные пары путаницы на рукописных цифрах:

  • 1 ↔ 7
  • 3 ↔ 8
  • 5 ↔ 6
  • 0 ↔ 6
  • 4 ↔ 9

Что смотреть кроме матрицы

  • Почерк: какие операторы или контрагенты дают худший CER;
  • Инструмент письма: гелевая ручка vs карандаш;
  • Освещение: блик на ламинированном бланке;
  • Размер цифр: мелкий почерк в узкой ячейке;
  • Артефакты съёмки: смаз, обрезанный край ячейки.

Соберите папку hard examples — 50–200 ячеек, где все модели ошибаются. Дообучение на hard set или ручная проверка этих кейсов часто даёт больше, чем смена Large на Base.

Цикл улучшения:

обычные примеры → ошибки на val → hard examples →
аугментация / дообучение / правила → повтор

Наблюдаемость такого конвейера — логирование уверенности, сохранение кропов с низким score, дашборд CER по полю и по оператору — пересекается с практиками из AI observability.

Data augmentation: имитация камеры, не фантазия

Аугментация помогает при нехватке данных, если она имитирует реальные условия съёмки:

  • небольшой поворот и сдвиг;
  • масштаб;
  • изменение контраста и яркости;
  • гауссов шум;
  • лёгкое размытие;
  • небольшая перспективная деформация;
  • случайная обрезка с сохранением цифры в кадре.

Плохая аугментация — та, которой нет в production: экстремальные искажения, инверсия цвета без смысла, наложение случайных линий не там, где бывают линии таблицы.

Если линии таблицы — главный источник ошибок, лучше вырезать их на этапе предобработки (морфология, цветовая маска, inpainting линий), чем надеяться, что модель «привыкнет».

Semi-supervised learning и human-in-the-loop

Когда baseline уже работает, можно замкнуть цикл:

уверенное распознавание → pseudo-label → новые training samples → переобучение

Confidence threshold

Если confidence > threshold (например, 0.95 для CNN или низкий CER beam search для CRNN), пример добавляется в пул с псевдоразметкой. Ниже порога — в очередь человеку.

Human-in-the-loop

model → confidence → high: accept / low: human review → БД

Это снижает стоимость разметки и не даёт модели тихо деградировать: доля ручных проверок — метрика здоровья системы.

Осторожно с feedback loop: если модель систематически путает 5 и 6, псевдоразметка усилит ошибку. Периодически подмешивайте верифицированные человеком примеры и мониторьте confusion matrix на свежем потоке.

Production pipeline: больше, чем нейросеть

Итоговая цепочка для структурированного бланка:

photograph → document detection → perspective correction →
template alignment → cell extraction → preprocessing →
OCR model → confidence → validation rules → database

Где работают детерминированные правила

Нейросеть не должна решать то, что задано бизнес-логикой:

  • поле допускает только цифры;
  • значение в диапазоне 0–100;
  • фиксированное количество знаков (например, показание счётчика — ровно пять цифр);
  • контрольная сумма или повторное чтение соседнего поля.

Если OCR вернул 783 для поля с максимумом 100, правило отклоняет результат независимо от softmax.

Каскад моделей

Не обязательно выбирать одну архитектуру:

быстрый CNN → уверен? → accept
              ↓ нет
         CRNN или TrOCR → уверен? → accept
              ↓ нет
         human review

Почему это часто лучше одной большой модели:

  • дешевле в среднем на ячейку;
  • быстрее на «лёгких» примерах;
  • проще масштабировать нагрузку;
  • сложные случаи получают больше вычислений, а не все подряд.

Что выбрать для реальной системы

Задача Рекомендация
Одна цифра в ячейке CNN
Несколько цифр, свой датасет CRNN + CTC
Смешанный текст, расширение алфавита TrOCR fine-tune
Фиксированный бланк Geometry + узкая модель на ячейку

Архитектурная карта в одном блоке:

ОДНА ЦИФРА          → CNN
НЕСКОЛЬКО ЦИФР      → CRNN + CTC
СЛОЖНЫЙ ТЕКСТ       → TrOCR
СТРУКТУРИРОВАННЫЙ   → Document AI + geometry + OCR на ячейках
ДОКУМЕНТ

Что эта задача говорит о выборе нейросетей

Не начинайте с вопроса «какая сейчас самая мощная модель?». Начинайте с «какова структура моей задачи?»

  • 10 классов, одна цифра — не нужен generative OCR.
  • Последовательность символов без посимвольных box — CTC / CRNN.
  • Сложный текст и перенос pretrained — Transformer-модели вроде TrOCR.
  • Известный шаблон — Document AI на уровне геометрии снимает половину ML-сложности.

Архитектура должна следовать структуре информации, а не популярности модели в ленте.

Что сделать сегодня

  1. Зафиксируйте постановку: одна цифра или строка? Какой максимальной длины?
  2. Нарисуйте пайплайн до ML: выравнивание бланка и нарезка ячеек в приоритете.
  3. Проверьте источник разметки: можно ли связать фото с БД?
  4. Обучите CNN baseline и сохраните confusion matrix.
  5. Если в ячейке несколько цифр — CRNN + CTC на тех же кропах.
  6. Сравните с TrOCR Small только после честного сплита по документам.
  7. Введите порог уверенности и очередь ручной проверки до «полного автомата».

Дальнейшее чтение

FAQ

Можно ли начать с готового Tesseract?

Да, как с очень грубым baseline на вырезанных ячейках. Tesseract рассчитан на строки текста; на одиночных рукописных цифрах в ячейке с линиями сетки часто проигрывает маленькой CNN. Имеет смысл для быстрой проверки «есть ли сигнал в данных», но редко как финальное решение для полевых бланков.

Сколько данных нужно для CNN?

Порядок величины: от нескольких сотен примеров на класс для простого домена до нескольких тысяч, если почерк сильно разнороден. Без аугментации и без группового val/test цифры будут обманчивыми.

CTC работает для пустой ячейки?

Нужен явный класс «пусто» или отдельный детектор пустоты до OCR. Иначе модель будет «галлюцинировать» цифры на шуме и линиях таблицы.

TrOCR Base или Small для тысячи ячеек?

Начните со Small: быстрее итерации, меньше риск переобучения. Base имеет смысл, если Small упирается в CER на validation, а не на train.

Как связать OCR с ERP?

После валидации поля пишите в API ERP или в промежуточную очередь с идемпотентным ключом document_id + field_id. Паттерны интеграции — в ERP integration.

Нужен ли GPU в production?

Для CNN на одной ячейке — часто нет, CPU хватает. Для батча TrOCR на сервере — желательно. Каскад «CNN на CPU → TrOCR на GPU для сложных» — типичный компромисс.

Как не смешать train и test одного бланка?

Храните document_id в метаданных каждого кропа и используйте GroupKFold или явный сплит по списку документов. Любой случайный train_test_split без групп — красный флаг.

Что делать, если accuracy высокая, а пользователи жалуются?

Смотрите ошибки на репрезентативном тесте (новые люди, новые условия съёмки), а не на случайных кропах. Высокая accuracy на «лёгком» тесте при плохом опыте в поле — классический симптом leakage или смещённой выборки.