Загрузка данных


История разработки OCR-сервиса 1. Постановка задачи 

Изначально проект задумывался как локальный сервис распознавания документов, который можно развернуть на собственной Ubuntu-машине и использовать без передачи файлов во внешние облачные сервисы.

Основными требованиями были:

распознавание документов на русском и английском языках; поддержка PNG, JPEG и PDF; возможность использования GPU; простой веб-интерфейс; REST API для интеграции с другими системами; сохранение структуры текста и простых табличных данных; полностью локальная обработка документов; запуск через Docker Compose. 

На первом этапе важнее было получить работающий MVP, чем сразу создавать сложную распределённую систему с очередями, базой данных и постоянным хранилищем заданий.

2. Первый подход: универсальная OCR-модель через Ollama 

В ранней версии проекта рассматривался подход с использованием мультимодальной модели, запущенной через Ollama. Предполагалось, что vision-language-модель сможет одновременно:

распознавать текст; определять структуру документа; восстанавливать таблицы; возвращать результат в JSON или Markdown; учитывать инструкции пользователя. 

Такой подход выглядел привлекательным, поскольку одна модель могла заменить несколько отдельных компонентов обработки документов.

Преимущества подхода единый интерфейс через Ollama HTTP API; возможность задавать модели сложные инструкции; потенциально хорошая работа с нестандартной структурой документов; возможность получать сразу структурированный результат; простое переключение между установленными моделями. Обнаруженные проблемы 

Во время экспериментов выяснилось, что мультимодальные языковые модели не всегда подходят для стабильного потокового OCR.

Основными проблемами стали:

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

Для одиночных сложных документов этот подход мог давать качественный результат, однако для обычного OCR-сервиса он оказался слишком тяжёлым и непредсказуемым.

Решение 

Использование vision-language-модели в качестве основного OCR-движка было отклонено.

При этом сам подход не был признан бесполезным. В дальнейшем такую модель можно использовать как дополнительный этап для:

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

Таким образом, языковая модель была исключена из базового OCR-контура, но может быть возвращена как отдельный постпроцессор.

3. Второй подход: EasyOCR как основной движок 

Следующим вариантом стал EasyOCR. Он был выбран благодаря простому Python API, поддержке русского языка и возможности работы как на CPU, так и на GPU.

На этом этапе была реализована базовая схема:

пользователь загружает файл; PDF преобразуется в изображения; каждая страница передаётся в EasyOCR; распознанные блоки сортируются по координатам; текст объединяется в строки; результат возвращается через FastAPI. Почему EasyOCR был выбран простая установка; понятный программный интерфейс; поддержка русского языка; наличие координат текстовых блоков; возможность запуска без отдельного модельного сервиса; быстрый старт разработки. Недостатки 

После тестирования на реальных документах проявились ограничения:

качество распознавания мелкого текста было нестабильным; часто возникали ошибки в кириллице; сложные шрифты распознавались хуже; обработка больших PDF занимала значительное время; структура таблиц восстанавливалась неточно; модель занимала память основного веб-процесса; одновременные запросы могли создавать высокую нагрузку. Решение 

EasyOCR было решено оставить, но перестать использовать как основной движок.

В текущей архитектуре он выполняет роль резервного OCR-механизма. Если основной сервис PP-OCR недоступен, приложение может продолжить обработку через EasyOCR.

Это позволило сохранить отказоустойчивость без необходимости поддерживать два равнозначных OCR-контура.

4. Переход на PP-OCRv5 

После сравнения нескольких решений основным движком был выбран PP-OCRv5 из состава PaddleOCR.

Причинами выбора стали:

наличие специализированной модели для русского и других восточнославянских языков; хорошая точность распознавания печатных документов; высокая скорость на GPU; получение координат и confidence score; предсказуемый формат результата; меньшая склонность к генерации несуществующего текста; ориентация именно на OCR, а не на универсальную генерацию. 

PP-OCRv5 лучше соответствовал задаче массового распознавания документов, чем универсальная vision-language-модель.

5. Выделение PP-OCR в отдельный сервис 

Первоначально рассматривалась возможность загрузить PaddleOCR непосредственно внутрь FastAPI-приложения. От этого варианта отказались из-за сложности зависимостей.

PaddlePaddle, CUDA-библиотеки и основной веб-сервис могли использовать разные версии системных пакетов. Кроме того, загрузка модели непосредственно в API-процесс усложняла:

обновление модели; диагностику GPU; перезапуск OCR независимо от веб-интерфейса; замену OCR-движка; контроль используемой памяти. 

Поэтому PP-OCR был вынесен в отдельный контейнер.

Текущая схема выглядит следующим образом:

Пользователь │ ▼ FastAPI OCR Service │ ├── извлечение встроенного текста из PDF │ ├── отправка изображения в PP-OCR Service │ └── fallback на EasyOCR │ ▼ PP-OCRv5 Service │ ▼ PaddlePaddle + NVIDIA GPU 

Основной сервис передаёт изображение в PP-OCR по внутреннему HTTP API. Контейнер PP-OCR не требуется публиковать во внешнюю сеть: он доступен только внутри Docker Compose.

Почему этот подход был оставлен изоляция тяжёлых ML-зависимостей; независимый перезапуск компонентов; более простая отладка; возможность использовать отдельный Docker-образ с CUDA; основной API остаётся сравнительно лёгким; модель загружается один раз при запуске контейнера; в будущем PP-OCR можно заменить другим сервисом без полной переработки API. 

Дополнительный HTTP-вызов создаёт небольшой overhead, но он незначителен по сравнению со временем выполнения OCR.

6. Извлечение текстового слоя из PDF 

При тестировании выяснилось, что многие PDF уже содержат машинный текст. Применять OCR к таким документам не только бессмысленно, но и вредно: распознавание может внести ошибки в текст, который уже хранится в документе в точном виде.

Поэтому перед OCR был добавлен этап проверки текстового слоя PDF.

Алгоритм работает следующим образом:

сервис открывает PDF; пытается извлечь текст из страниц; оценивает количество страниц с текстовым содержимым; если документ преимущественно текстовый, используется встроенный текст; если текста нет, страницы преобразуются в изображения и передаются в OCR. Почему подход был оставлен значительно ускоряет обработку электронных PDF; не нагружает GPU без необходимости; сохраняет исходную точность текста; уменьшает потребление памяти; позволяет быстрее обрабатывать большие документы. Ограничение текущей реализации 

Пороговая проверка всего документа недостаточно хорошо работает со смешанными PDF, в которых часть страниц содержит текстовый слой, а часть является сканами.

Например, если восемь страниц из десяти содержат текст, документ может быть признан текстовым целиком, а две отсканированные страницы останутся без содержимого.

Следующим улучшением должна стать постраничная обработка:

есть достаточный текстовый слой → использовать текст; страница пустая → отправить страницу в OCR. 

Сам подход с предварительным извлечением текста сохраняется, но его реализация должна стать более точной.

7. Восстановление строк и простых таблиц 

OCR-движок возвращает отдельные текстовые блоки с координатами. Для пользователя такой набор фрагментов неудобен, поэтому был добавлен алгоритм восстановления строк.

Блоки:

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

Такой подход позволяет частично сохранять структуру:

накладных; счетов; простых таблиц; списков; форм с несколькими колонками. Почему не был добавлен полноценный table recognition 

Полноценное распознавание таблиц требует отдельной модели анализа структуры документа. Необходимо определять:

границы строк и колонок; объединённые ячейки; вложенные заголовки; многострочные значения; соответствие текста конкретной ячейке. 

Для MVP это значительно увеличило бы сложность и количество зависимостей. Поэтому была оставлена координатная эвристика, которая не претендует на восстановление произвольных таблиц, но хорошо работает на простых документах.

В перспективе этот компонент может быть заменён на PP-Structure или отдельную layout-модель.

8. Выбор синхронной обработки вместо Celery 

На раннем этапе рассматривалась более сложная архитектура с:

PostgreSQL; Redis; Celery; отдельной очередью OCR-задач; хранением статусов обработки; возможностью возобновления заданий. 

Такая архитектура подходит для многопользовательского промышленного сервиса, однако для локального MVP она создавала избыточную сложность.

Потребовалось бы отдельно поддерживать:

брокер сообщений; базу данных; Celery worker; миграции; очистку старых заданий; мониторинг очереди; повторное выполнение задач; синхронизацию статусов. 

В текущей версии запрос обрабатывается непосредственно после загрузки документа. Блокирующие операции запускаются вне основного event loop, а доступ к модели ограничивается lock-механизмом.

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

Очередь задач станет необходимой, если сервис должен:

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

Таким образом, Celery и Redis были не отвергнуты окончательно, а отложены до следующего этапа развития.

9. Ограничение параллельного доступа к модели 

OCR-модель потребляет значительный объём GPU-памяти. Параллельный запуск нескольких inference-запросов может привести к:

переполнению VRAM; резкому увеличению задержек; нестабильности PaddlePaddle; падению контейнера; ошибкам CUDA out of memory. 

Поэтому доступ к модели был сериализован с помощью блокировки. Одновременно выполняется только один OCR-вызов.

Почему подход был оставлен 

Для локального сервиса стабильность важнее максимальной пропускной способности. Последовательная обработка позволяет предсказуемо использовать GPU и снижает вероятность падений.

Недостатком является ожидание при нескольких одновременных запросах. В дальнейшем lock целесообразно заменить ограниченной очередью с понятным статусом задания.

10. Использование Base64 для передачи изображений 

Основной API передаёт изображение в модельный сервис в формате Base64 внутри JSON.

Этот вариант был выбран благодаря простоте:

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

Base64 увеличивает объём данных примерно на треть и создаёт дополнительные копии изображения в памяти.

Для небольших изображений и локальной Docker-сети это допустимо. Для больших документов или высокой нагрузки более эффективным вариантом станет передача бинарного файла через multipart/form-data либо работа через общее временное хранилище.

Подход был сохранён для MVP из-за простоты, но не считается оптимальным для высоконагруженной версии.

11. Контейнеризация через Docker Compose 

Для запуска проекта был выбран Docker Compose. Он одновременно поднимает:

основной OCR API; контейнер PP-OCR; внутреннюю сеть; постоянный volume с загруженными моделями; доступ к NVIDIA GPU; health-check сервисов. Почему Docker Compose был оставлен воспроизводимое окружение; отсутствие необходимости вручную устанавливать Python-зависимости; изоляция версий PaddlePaddle и CUDA; простой запуск одной командой; сохранение моделей между перезапусками; удобное обновление компонентов. 

От Kubernetes и других оркестраторов отказались, поскольку для одной целевой машины они не дают существенных преимуществ и значительно усложняют эксплуатацию.

12. Fallback-механизм 

В режиме auto приложение сначала пытается использовать PP-OCRv5. Если основной сервис недоступен, запрос может быть передан в EasyOCR.

Fallback был добавлен для следующих ситуаций:

PP-OCR ещё загружает модель; контейнер перезапускается; произошла ошибка CUDA; модельный сервис временно недоступен; возник сетевой таймаут между контейнерами. Почему fallback был оставлен 

Даже менее точный результат лучше полного отказа сервиса. При этом пользователь может принудительно выбрать PP-OCR и получить ошибку, если резервное распознавание нежелательно.

Недостатком является то, что результат двух движков может различаться. Поэтому API должен явно сообщать, какой OCR-движок фактически использовался.

13. Экспорт результатов 

Кроме JSON-ответа были добавлены экспорт в TXT и DOCX, а также простой веб-интерфейс.

Это решение связано с тем, что конечному пользователю часто нужен не программный JSON, а готовый файл, который можно:

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

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

14. Итоговая архитектура 

В результате была сформирована компромиссная архитектура:

FastAPI используется для REST API и веб-интерфейса; PP-OCRv5 является основным OCR-движком; PaddleOCR работает в отдельном GPU-контейнере; EasyOCR используется как резервный движок; текстовый слой PDF извлекается до запуска OCR; координаты используются для восстановления строк и простых колонок; обработка выполняется синхронно; доступ к модели ограничивается блокировкой; развёртывание выполняется через Docker Compose; результаты можно получить через API, TXT или DOCX. 

Эта архитектура была выбрана не как окончательное промышленное решение, а как баланс между:

качеством распознавания; простотой запуска; использованием GPU; надёжностью; количеством зависимостей; скоростью разработки. 15. Отложенные улучшения 

В ходе разработки были сознательно отложены следующие возможности:

Постраничное объединение текстового слоя PDF и OCR. Настоящее распознавание структуры таблиц. Очередь заданий на базе Redis и Celery. Хранение истории обработки в PostgreSQL. Отображение прогресса по страницам. Аутентификация пользователей. Ограничение частоты запросов. Передача изображений без Base64. Пакетная обработка нескольких документов. Метрики Prometheus и централизованные логи. Автоматический выбор размера batch под доступную VRAM. Повторная обработка только страниц с низкой уверенностью. Использование языковой модели для исправления и структурирования уже распознанного текста. 

Эти функции не были реализованы в MVP, поскольку не являлись обязательными для проверки основной гипотезы: возможности локально распознавать русскоязычные документы через веб-интерфейс и API с использованием NVIDIA GPU.

Заключение 

Разработка сервиса прошла путь от универсального, но тяжёлого OCR на основе мультимодальной языковой модели к более специализированной архитектуре на базе PP-OCRv5.

В процессе были сохранены решения, которые давали предсказуемость и упрощали эксплуатацию:

специализированный OCR-движок; отдельный модельный контейнер; предварительное извлечение текста из PDF; резервный движок; Docker Compose; простой REST API. 

От решений, усложняющих MVP или снижающих стабильность, временно отказались:

vision-language-модель как основной OCR; полноценная очередь заданий; база данных; сложный анализ таблиц; Kubernetes; параллельный inference на одной GPU. 

В итоге был получен локальный OCR-сервис, который можно развернуть на одной Ubuntu-машине, использовать через браузер или API и постепенно развивать до более производительной многопользовательской системы.