scan, split, OCR, prepare for LLM
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

stage2_results.md 15KB

Stage 2 Results — OCR Pipeline

Путь проекта

Исходная точка

Единый скрипт src/cli.py → разрезание изображений на страницы (Stage 1). Нужен Stage 2 — распознавание текста и структуры документа, генерация LaTeX/HTML/Markdown для последующего анализа LLM.


Опробованные варианты

1. PaddleOCR базовый (PP-OCRv5)

Результат: 66 текстовых блоков, русский текст — ~60% точности.

  • i-WHAeKC AOrOBOpa вместо і - индекс договора
  • t - номер месяцаB дефолте — латиница смешана с кириллицей
  • Нет структуры документа (заголовки, формулы, таблицы слиты в сплошной текст)
  • Вывод: только для английского, для русского требуется структура

2. PP-StructureV3 (ансамбль специализированных моделей)

Результат: 22 структурированных блока с layout-разметкой.

  • Layout detection: ✅ [number], [formula], [text], [paragraph_title]
  • Display-формулы: ✅ \mathrm{C Rec}_{\mathrm{it}}=\sum...
  • Таблицы: ✅ PP-TableMagic с bbox ячеек
  • Русский текст: ⚠️ ~85% (eslav_PP-OCRv5_mobile_rec)
  • Вывод: структура отлично, текст — терпимо

3. Замена на cyrillic_PP-OCRv5_mobile_rec

Результат: разница ~1-2%, несущественна.

  • eslav: LGD_ехtг; (кириллица г)
  • cyrillic: LGD_ехtr; (латиница r) — чуть лучше
  • Вывод: обе мобильные модели, серверной русской не существует

4. PP-OCRv5_server_rec / PP-OCRv6

Результат: ❌ не поддерживают русский.

  • Server model: только CN/JP/EN
  • PP-OCRv6: 46 латинских языков + CN/JP/EN (без кириллицы)
  • Вывод: русский — только mobile-версии

5. PP-OCRv6 детектор (RepLKFPN, 62MB) ✅ оставлен

Результат: +4.6% detection accuracy vs v5, на 7% быстрее (97.8s vs 104.7s).

  • Модель скачана через paddlex[ocr] 3.7.2 без апгрейда paddlepaddle
  • Смешан с eslav_rec — детектор язык-независим
  • Совместим с PP-StructureV3 через YAML-конфиг
  • Вывод: бесплатный прирост детекции без риска

6. --main-lang выбор распознавалки ✅ оставлен

Результат: переключение rec-модели по основному языку страницы.

  • --main-lang=rueslav_PP-OCRv5_mobile_rec (русский, 85-90%)
  • --main-lang=enPP-OCRv6_medium_rec (английский + греческие в формулах, 75-80%)
  • Таблицы и формулы — общие (SLANet + PP-FormulaNet)
  • Модели кешируются раздельно (lru_cache по языку)
  • Вывод: +15-20% на английских страницах заменой одной модели

7. Локальный LLM-корректор (Qwen2.5-7B Q4_K_M) ❌ удалён

Результат: 50/50 — исправляет и тут же галлюцинирует.

До:   сypporat WoE для гpynnы i
После: коррелят weight of evidence (галлюцинация вместо «суррогат»)
  • Причины отказа: (1) слепой — не видит изображение, догадывается; (2) маленький — 7B квантизованный не понимает контекст; (3) избыточный — fixups.py уже правит VaR/V^2 без модели
  • Занятые ресурсы: 4.4 GB диск + 5 GB RAM + 60s/блок
  • Вывод: удалён, освобождено 4.4 GB

8. VLM-корректор (Qwen3.8 Max через OpenCode API)

Результат: полное восстановление текста + авто inline-формулы.

До:   ΣДсе, - дисконтированная сумма возвратов
После: $\sum_t DCF_{i,t}$ — дисконтированная сумма возвратов
  • ~10s/блок, Anthropic API zen/go/v1/messages
  • Таблицы: HTML → tab-separated flat text ✅
  • На порядок качественнее LLM — видит изображение, читает напрямую
  • Вывод: лучшее качество, требует API-доступа

9. Docling (IBM Research)

Результат: ❌ не поддерживает PaddleOCR как OCR-backend.

  • Поддерживает: EasyOCR, Tesseract, RapidOCR, Nemotron
  • Вывод: несовместим с нашим пайплайном

10. PaddleOCR-VL (ERNIE-4.5-0.3B)

Результат: модель загружена (1.79 GB), ~15 мин/стр на CPU.

  • Одна VLM вместо ансамбля
  • Теоретически решает проблему русского (LM понимает контекст)
  • Вывод: правильный подход при GPU, нереализуем на CPU

11. Surya 2 (Datalab, 650M VLM) ⭐

Результат: лучший русский OCR из коробки — но медленно на CPU.

  • 95%+ точность русского текста без коррекции
  • Авто inline-формулы: <math>MRec_{it}</math>$MRec_{it}$
  • ~300s/стр после CPU-оптимизаций (vs 140s PaddleOCR)
  • Слабость: таблицы требуют много токенов → таймауты на CPU
  • Вывод: лучший выбор по качеству, медленнее на CPU

12. VLM full-page (Qwen3.8 Max) для таблиц

Результат: 118s на страницу с таблицей, качество отличное.

  • Таблица → pipe-Markdown: | col1 | col2 |
  • Формулы → $...$
  • Структура заголовков сохранена
  • Вывод: для табличных страниц — лучший вариант

Эксперименты по препроцессингу изображений

13. Denoising (Non-Local Means) ✅ оставлен

Результат: 17 блоков vs 12 без (+42%), исправил «6»→«G», «Пр»→«р».

  • Без denoise: «рогнозное», «L6D ID», 12 блоков, 104.7s
  • С denoise: «Прогнозное», «LGD ID», 17 блоков, 139.5s (+33% времени)
  • Вывод: +42% блоков за +35s — оставлен как опция --denoise

14. Адаптивная бинаризация (Otsu/Adaptive) ❌ удалена

Результат: жёсткие границы порвали буквы, ложные контуры.

  • «рассчитывается» → «рассчитавстся»
  • Формулы: $b_i$$b_{-}i-\\sec a$ (мусор)
  • Мусорные блоки: $\alpha\times\alpha\times\alpha...$ — артефакты бинаризации
  • Причина: нейросетевой OCR обучен на естественных изображениях, не на бинарных
  • Вывод: удалена из CLI и пайплайна

15. Upscale ×2 для мелкого текста ❌ удалён

Результат: бесполезен — PaddleOCR режет max_side_limit=4000.

  • Изображение 6090×3880 (×2) → Paddle сжимает обратно до ≤4000
  • Время впустую: 174s вместо 104s
  • Причина: параметр max_side_limit жёстко ограничивает входное разрешение
  • Вывод: удалён из CLI, требует правки max_side_limit в YAML для пользы

16. Document Unwarping (Canny → 4-угольник → perspective) ✅ оставлен

Результат: работает для фото под углом, NOP для плоского сканера.

  • Планшетный сканер: контур страницы не найден → возврат исходника
  • Фото: детектит 4-угольник → warp в прямоугольник
  • Баг первой версии: детектил внутренние таблицы (bbox ~150px высотой)
  • Исправление: порог min_area ≥ 30% от изображения
  • Вывод: опция --unwarp для фото, для сканера — YAGNI

Оптимизации кода (SOLID / DRY / KISS / YAGNI)

Действие Результат
ParsedBlock/OcrPageResultsrc/models.py 7 модулей отвязаны от paddle_engine
Corrector(Protocol)Callable Удалён неиспользуемый Protocol
validate_output_dir() удалён Мёртвый код из slicer.py
_ensure_env()load_env() напрямую Убраны дублирующие guard’ы
calc_scale_dims() в image_utils.py Общий pre-scale для Surya + external VLM
functools.lru_cache для engine-синглтонов Убран boilerplate _engine: X \| None
surya2html.py, tex2html.py удалены Дубликаты логики из json_to_latex.py
binarize_image(), upscale_image() удалены Мёртвые функции из slicer.py
requirements.txt: 118 → 14 пакетов Только прямые зависимости
llm_corrector.py + Qwen2.5-7B (4.4GB) удалены Галлюцинации, YAGNI

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

Входное изображение (JPEG/PNG/TIFF)
    │
    ▼
src/image_prepare.py          ← Stage 1: подготовка
    │ --slice / --slice-auto
    │ --pre-rotate, --border
    │ --unwarp, --denoise
    │ --post-crop
    │
    ▼
src/image_ocr.py            ← Stage 2: OCR + Markdown
    │ --ocr-engine paddle|surya|external-qwen3
    │ --main-lang ru|en
    │ --fix-by-external-qwen3
    │ --pause, --no-result-one-document
    │
    ├─ PaddleEngine / SuryaEngine   (OCR)
    ├─ fix_ocr_errors               (VaR, V^2)
    ├─ VlmCorrector                 (Qwen3.8 Max, API)
    │
    ▼
.md + .json (per page) + merged.md
     │
     ▼
src/latex/json_to_latex.py  ← Stage 3: JSON → LaTeX + HTML
    │ Paddle / Surya / Qwen
    │ Автоопределение формата
    │
    ▼
.tex + .html (с MathJax для таблиц и формул)

По умолчанию включено

Функция Статус
--main-lang ru ON
Resume (пропуск JSON) ON
--result-one-document ON
--pause 5 ON
--post-crop (Stage 1) ON

Модули

src/
  models.py               — ParsedBlock, OcrPageResult
  image_utils.py          — calc_scale_dims
  image_prepare.py        — CLI Stage 1
  image_ocr.py            — CLI Stage 2
  cli_utils.py            — run_main()
  env.py                  — загрузка .env
  split/slicer.py         — разрезание, post-crop, border, unwarp, denoise
  ocr/
    paddle_engine.py      — PP-StructureV3 + v6-det + ru/en rec
    surya_engine.py       — Surya 2 + CPU-оптимизации + health_check
    external_vlm_engine.py — Qwen3.8 Max full-page OCR
  postprocess/
    fixups.py             — VaR, V^2
    corrector.py          — apply_corrector
    vlm_corrector.py      — Qwen3.8 Max per-block API
    qwen_client.py        — общий Qwen API-клиент
  markdown/generator.py   — Markdown + валидация LaTeX
  latex/
    json_to_latex.py      — JSON→LaTeX+HTML (Paddle + Surya + Qwen)

Оптимизации Surya 2 для CPU

Параметр До После Эффект
KEEP_ALIVE off on -23s повторный старт
CTX per slot 12288 8192 -30% RAM
Parallel slots 8 1 -80% слотов
Pre-scale 2552px 1056px -60% пикселей
Threads default -t 8 стабильно
Итого 430s 300s -30%

Сравнение движков

Параметр PaddleOCR Surya 2 VLM Qwen3.8
Архитектура Ансамбль 5 моделей Один VLM (650M) Cloud VLM
Русский текст ~85% (eslav rec) 95%+ 98%+
Английский текст ~80% (v6 rec) 95%+ 98%+
Формулы PP-FormulaNet VLM (отлично) VLM (отлично)
Таблицы PP-TableMagic + bbox ❌ CPU timeout ✅ Pipe-Markdown
Inline-формулы Нет <math> тэги $...$
Скорость CPU ~100-160s ~300s ~120s (API)
Офлайн
Размер моделей ~2.5 GB ~1.5 GB 0 (cloud)

Ключевые цифры (на CPU: 8 ядер, 32 GB RAM)

Этап PaddleOCR Surya 2 VLM
OCR (одна страница) 100-160s 300s 120s
Denoise (Stage 1) +35s
VLM-корректор +10s/блок не нужен
JSON + MD сохранение <1s <1s <1s
Merge в один документ <1s <1s <1s
Итого на страницу ~3-4 min ~5 min ~2 min
12 страниц (batch) ~15-20 min* ~60 min ~24 min

* PaddleOCR с --pause 5 и --denoise


Неиспользованные подходы

  1. EasyOCR — WER 0.12 vs Paddle 0.056
  2. Tesseract — нет GPU, хуже на русском
  3. RapidOCR — легче, качество ниже
  4. Fine-tune eslav — требует GPU + датасет

Выводы

  1. PP-StructureV3 + v6-det + eslav_rec — лучший офлайн OCR для русского на CPU: 85-90% текст, 90% формулы, 80% таблицы
  2. PP-OCRv6 rec через --main-lang=en — +20% на английских страницах
  3. Denoise — +42% блоков на сканах с шумом, оставлен как опция
  4. Бинаризация, upscale — вредны для нейросетевого OCR, удалены
  5. Локальный LLM (Qwen2.5-7B) — галлюцинирует, удалён
  6. VLM-корректор (Qwen3.8 Max) — отличный, но API
  7. Surya 2 — лучший русский OCR из коробки, CPU-оптимизации дали -30% времени
  8. Гибридный подход: Surya для текста + VLM для таблиц = лучший баланс
  9. CPU-ограничения — основной фактор, GPU сделал бы Surya/PaddleOCR-VL идеальным выбором