|
|
@@ -33,39 +33,57 @@
|
|
33
|
33
|
### 4. PP-OCRv5_server_rec / PP-OCRv6
|
|
34
|
34
|
**Результат:** ❌ не поддерживают русский.
|
|
35
|
35
|
- Server model: только CN/JP/EN
|
|
36
|
|
-- PP-OCRv6: 52 латинских языка
|
|
|
36
|
+- PP-OCRv6: 46 латинских языков + CN/JP/EN (без кириллицы)
|
|
37
|
37
|
- **Вывод:** русский — только mobile-версии
|
|
38
|
38
|
|
|
39
|
|
-### 5. Локальный LLM-корректор (Qwen2.5-7B GGUF)
|
|
40
|
|
-**Результат:** +25% читаемости текста.
|
|
|
39
|
+### 5. PP-OCRv6 детектор (RepLKFPN, 62MB) ✅ оставлен
|
|
|
40
|
+**Результат:** +4.6% detection accuracy vs v5, на 7% быстрее (97.8s vs 104.7s).
|
|
|
41
|
+- Модель скачана через paddlex[ocr] 3.7.2 без апгрейда paddlepaddle
|
|
|
42
|
+- Смешан с eslav_rec — детектор язык-независим
|
|
|
43
|
+- Совместим с PP-StructureV3 через YAML-конфиг
|
|
|
44
|
+- **Вывод:** бесплатный прирост детекции без риска
|
|
|
45
|
+
|
|
|
46
|
+### 6. `--main-lang` выбор распознавалки ✅ оставлен
|
|
|
47
|
+**Результат:** переключение rec-модели по основному языку страницы.
|
|
|
48
|
+- `--main-lang=ru` → `eslav_PP-OCRv5_mobile_rec` (русский, 85-90%)
|
|
|
49
|
+- `--main-lang=en` → `PP-OCRv6_medium_rec` (английский + греческие в формулах, 75-80%)
|
|
|
50
|
+- Таблицы и формулы — общие (SLANet + PP-FormulaNet)
|
|
|
51
|
+- Модели кешируются раздельно (lru_cache по языку)
|
|
|
52
|
+- **Вывод:** +15-20% на английских страницах заменой одной модели
|
|
|
53
|
+
|
|
|
54
|
+### 7. Локальный LLM-корректор (Qwen2.5-7B Q4_K_M) ❌ удалён
|
|
|
55
|
+**Результат:** 50/50 — исправляет и тут же галлюцинирует.
|
|
41
|
56
|
```
|
|
42
|
|
-До: t - номер месяцаB дефолте, на который прогнозируются возвраты
|
|
43
|
|
-После: т — номер месяца в диапазоне между текущим сроком в дефолте и горизонтом взыскания
|
|
|
57
|
+До: сypporat WoE для гpynnы i
|
|
|
58
|
+После: коррелят weight of evidence (галлюцинация вместо «суррогат»)
|
|
44
|
59
|
```
|
|
45
|
|
-- ~4 tok/sec на CPU, ~20s/блок
|
|
46
|
|
-- **Вывод:** эффективен для пост-обработки
|
|
|
60
|
+- Причины отказа: (1) слепой — не видит изображение, догадывается; (2) маленький — 7B квантизованный не понимает контекст; (3) избыточный — `fixups.py` уже правит VaR/V^2 без модели
|
|
|
61
|
+- Занятые ресурсы: 4.4 GB диск + 5 GB RAM + 60s/блок
|
|
|
62
|
+- **Вывод:** удалён, освобождено 4.4 GB
|
|
47
|
63
|
|
|
48
|
|
-### 6. VLM-корректор (Qwen3.8 Max через OpenCode API)
|
|
|
64
|
+### 8. VLM-корректор (Qwen3.8 Max через OpenCode API)
|
|
49
|
65
|
**Результат:** полное восстановление текста + авто inline-формулы.
|
|
50
|
66
|
```
|
|
51
|
67
|
До: ΣДсе, - дисконтированная сумма возвратов
|
|
52
|
68
|
После: $\sum_t DCF_{i,t}$ — дисконтированная сумма возвратов
|
|
53
|
69
|
```
|
|
54
|
70
|
- ~10s/блок, Anthropic API `zen/go/v1/messages`
|
|
|
71
|
+- Таблицы: HTML → tab-separated flat text ✅
|
|
|
72
|
+- На порядок качественнее LLM — видит изображение, читает напрямую
|
|
55
|
73
|
- **Вывод:** лучшее качество, требует API-доступа
|
|
56
|
74
|
|
|
57
|
|
-### 7. Docling (IBM Research)
|
|
|
75
|
+### 9. Docling (IBM Research)
|
|
58
|
76
|
**Результат:** ❌ не поддерживает PaddleOCR как OCR-backend.
|
|
59
|
77
|
- Поддерживает: EasyOCR, Tesseract, RapidOCR, Nemotron
|
|
60
|
78
|
- **Вывод:** несовместим с нашим пайплайном
|
|
61
|
79
|
|
|
62
|
|
-### 8. PaddleOCR-VL (ERNIE-4.5-0.3B)
|
|
|
80
|
+### 10. PaddleOCR-VL (ERNIE-4.5-0.3B)
|
|
63
|
81
|
**Результат:** модель загружена (1.79 GB), ~15 мин/стр на CPU.
|
|
64
|
82
|
- Одна VLM вместо ансамбля
|
|
65
|
83
|
- Теоретически решает проблему русского (LM понимает контекст)
|
|
66
|
84
|
- **Вывод:** правильный подход при GPU, нереализуем на CPU
|
|
67
|
85
|
|
|
68
|
|
-### 9. Surya 2 (Datalab, 650M VLM) ⭐
|
|
|
86
|
+### 11. Surya 2 (Datalab, 650M VLM) ⭐
|
|
69
|
87
|
**Результат:** лучший русский OCR из коробки — но медленно на CPU.
|
|
70
|
88
|
- 95%+ точность русского текста без коррекции
|
|
71
|
89
|
- Авто inline-формулы: `<math>MRec_{it}</math>` → `$MRec_{it}$`
|
|
|
@@ -73,7 +91,7 @@
|
|
73
|
91
|
- **Слабость:** таблицы требуют много токенов → таймауты на CPU
|
|
74
|
92
|
- **Вывод:** лучший выбор по качеству, медленнее на CPU
|
|
75
|
93
|
|
|
76
|
|
-### 10. VLM full-page (Qwen3.8 Max) для таблиц
|
|
|
94
|
+### 12. VLM full-page (Qwen3.8 Max) для таблиц
|
|
77
|
95
|
**Результат:** 118s на страницу с таблицей, качество отличное.
|
|
78
|
96
|
- Таблица → pipe-Markdown: `| col1 | col2 |`
|
|
79
|
97
|
- Формулы → `$...$`
|
|
|
@@ -82,37 +100,97 @@
|
|
82
|
100
|
|
|
83
|
101
|
---
|
|
84
|
102
|
|
|
|
103
|
+## Эксперименты по препроцессингу изображений
|
|
|
104
|
+
|
|
|
105
|
+### 13. Denoising (Non-Local Means) ✅ оставлен
|
|
|
106
|
+**Результат:** 17 блоков vs 12 без (+42%), исправил «6»→«G», «Пр»→«р».
|
|
|
107
|
+- Без denoise: «рогнозное», «L6D ID», 12 блоков, 104.7s
|
|
|
108
|
+- С denoise: «Прогнозное», «LGD ID», 17 блоков, 139.5s (+33% времени)
|
|
|
109
|
+- **Вывод:** +42% блоков за +35s — оставлен как опция `--denoise`
|
|
|
110
|
+
|
|
|
111
|
+### 14. Адаптивная бинаризация (Otsu/Adaptive) ❌ удалена
|
|
|
112
|
+**Результат:** жёсткие границы порвали буквы, ложные контуры.
|
|
|
113
|
+- «рассчитывается» → «рассчита**встся**»
|
|
|
114
|
+- Формулы: `$b_i$` → `$b_{-}i-\\sec a$` (мусор)
|
|
|
115
|
+- Мусорные блоки: `$\alpha\times\alpha\times\alpha...$` — артефакты бинаризации
|
|
|
116
|
+- **Причина:** нейросетевой OCR обучен на естественных изображениях, не на бинарных
|
|
|
117
|
+- **Вывод:** удалена из CLI и пайплайна
|
|
|
118
|
+
|
|
|
119
|
+### 15. Upscale ×2 для мелкого текста ❌ удалён
|
|
|
120
|
+**Результат:** бесполезен — PaddleOCR режет `max_side_limit=4000`.
|
|
|
121
|
+- Изображение 6090×3880 (×2) → Paddle сжимает обратно до ≤4000
|
|
|
122
|
+- Время впустую: 174s вместо 104s
|
|
|
123
|
+- **Причина:** параметр `max_side_limit` жёстко ограничивает входное разрешение
|
|
|
124
|
+- **Вывод:** удалён из CLI, требует правки `max_side_limit` в YAML для пользы
|
|
|
125
|
+
|
|
|
126
|
+### 16. Document Unwarping (Canny → 4-угольник → perspective) ✅ оставлен
|
|
|
127
|
+**Результат:** работает для фото под углом, NOP для плоского сканера.
|
|
|
128
|
+- Планшетный сканер: контур страницы не найден → возврат исходника
|
|
|
129
|
+- Фото: детектит 4-угольник → warp в прямоугольник
|
|
|
130
|
+- Баг первой версии: детектил внутренние таблицы (bbox ~150px высотой)
|
|
|
131
|
+- **Исправление:** порог `min_area ≥ 30%` от изображения
|
|
|
132
|
+- **Вывод:** опция `--unwarp` для фото, для сканера — YAGNI
|
|
|
133
|
+
|
|
|
134
|
+---
|
|
|
135
|
+
|
|
|
136
|
+## Оптимизации кода (SOLID / DRY / KISS / YAGNI)
|
|
|
137
|
+
|
|
|
138
|
+| Действие | Результат |
|
|
|
139
|
+|---|---|
|
|
|
140
|
+| `ParsedBlock`/`OcrPageResult` → `src/models.py` | 7 модулей отвязаны от paddle_engine |
|
|
|
141
|
+| `Corrector(Protocol)` → `Callable` | Удалён неиспользуемый Protocol |
|
|
|
142
|
+| `validate_output_dir()` удалён | Мёртвый код из `slicer.py` |
|
|
|
143
|
+| `_ensure_env()` → `load_env()` напрямую | Убраны дублирующие guard'ы |
|
|
|
144
|
+| `calc_scale_dims()` в `image_utils.py` | Общий pre-scale для Surya + external VLM |
|
|
|
145
|
+| `functools.lru_cache` для engine-синглтонов | Убран boilerplate `_engine: X \| None` |
|
|
|
146
|
+| `surya2html.py`, `tex2html.py` удалены | Дубликаты логики из `json_to_latex.py` |
|
|
|
147
|
+| `binarize_image()`, `upscale_image()` удалены | Мёртвые функции из `slicer.py` |
|
|
|
148
|
+| `requirements.txt`: 118 → 14 пакетов | Только прямые зависимости |
|
|
|
149
|
+| `llm_corrector.py` + Qwen2.5-7B (4.4GB) удалены | Галлюцинации, YAGNI |
|
|
|
150
|
+
|
|
|
151
|
+---
|
|
|
152
|
+
|
|
85
|
153
|
## Итоговая архитектура
|
|
86
|
154
|
|
|
87
|
155
|
```
|
|
88
|
156
|
Входное изображение (JPEG/PNG/TIFF)
|
|
89
|
157
|
│
|
|
90
|
158
|
▼
|
|
91
|
|
-src/image_prepare.py ← Stage 1: разрезание
|
|
|
159
|
+src/image_prepare.py ← Stage 1: подготовка
|
|
92
|
160
|
│ --slice / --slice-auto
|
|
93
|
|
- │ --border, --post-crop
|
|
|
161
|
+ │ --pre-rotate, --border
|
|
|
162
|
+ │ --unwarp, --denoise
|
|
|
163
|
+ │ --post-crop
|
|
94
|
164
|
│
|
|
95
|
165
|
▼
|
|
96
|
166
|
src/image_ocr.py ← Stage 2: OCR + Markdown
|
|
97
|
|
- │ --ocr-engine paddle|surya
|
|
98
|
|
- │ --llm, --vlm
|
|
99
|
|
- │ --force-ocr, --pause
|
|
100
|
|
- │ --result-one-document (ON по умолчанию)
|
|
|
167
|
+ │ --ocr-engine paddle|surya|external-qwen3
|
|
|
168
|
+ │ --main-lang ru|en
|
|
|
169
|
+ │ --fix-by-external-qwen3
|
|
|
170
|
+ │ --pause, --no-result-one-document
|
|
101
|
171
|
│
|
|
102
|
172
|
├─ PaddleEngine / SuryaEngine (OCR)
|
|
103
|
173
|
├─ fix_ocr_errors (VaR, V^2)
|
|
104
|
|
- ├─ LlmCorrector (Qwen2.5-7B, local)
|
|
105
|
174
|
├─ VlmCorrector (Qwen3.8 Max, API)
|
|
106
|
175
|
│
|
|
107
|
176
|
▼
|
|
108
|
177
|
.md + .json (per page) + merged.md
|
|
|
178
|
+ │
|
|
|
179
|
+ ▼
|
|
|
180
|
+src/latex/json_to_latex.py ← Stage 3: JSON → LaTeX + HTML
|
|
|
181
|
+ │ Paddle / Surya / Qwen
|
|
|
182
|
+ │ Автоопределение формата
|
|
|
183
|
+ │
|
|
|
184
|
+ ▼
|
|
|
185
|
+.tex + .html (с MathJax для таблиц и формул)
|
|
109
|
186
|
```
|
|
110
|
187
|
|
|
111
|
188
|
### По умолчанию включено
|
|
112
|
189
|
|
|
113
|
190
|
| Функция | Статус |
|
|
114
|
191
|
|---------|:------:|
|
|
115
|
|
-| `--resume` (пропуск JSON) | ON |
|
|
|
192
|
+| `--main-lang ru` | ON |
|
|
|
193
|
+| Resume (пропуск JSON) | ON |
|
|
116
|
194
|
| `--result-one-document` | ON |
|
|
117
|
195
|
| `--pause 5` | ON |
|
|
118
|
196
|
| `--post-crop` (Stage 1) | ON |
|
|
|
@@ -121,23 +199,25 @@ src/image_ocr.py ← Stage 2: OCR + Markdown
|
|
121
|
199
|
|
|
122
|
200
|
```
|
|
123
|
201
|
src/
|
|
124
|
|
- image_prepare.py — CLI Stage 1
|
|
125
|
|
- image_ocr.py — CLI Stage 2
|
|
126
|
|
- cli_utils.py — run_main()
|
|
127
|
|
- env.py — загрузка .env
|
|
128
|
|
- split/slicer.py — разрезание, post-crop, border
|
|
|
202
|
+ models.py — ParsedBlock, OcrPageResult
|
|
|
203
|
+ image_utils.py — calc_scale_dims
|
|
|
204
|
+ image_prepare.py — CLI Stage 1
|
|
|
205
|
+ image_ocr.py — CLI Stage 2
|
|
|
206
|
+ cli_utils.py — run_main()
|
|
|
207
|
+ env.py — загрузка .env
|
|
|
208
|
+ split/slicer.py — разрезание, post-crop, border, unwarp, denoise
|
|
129
|
209
|
ocr/
|
|
130
|
|
- paddle_engine.py — PP-StructureV3 + кастом YAML (eslav)
|
|
131
|
|
- surya_engine.py — Surya 2 + CPU-оптимизации + health_check
|
|
|
210
|
+ paddle_engine.py — PP-StructureV3 + v6-det + ru/en rec
|
|
|
211
|
+ surya_engine.py — Surya 2 + CPU-оптимизации + health_check
|
|
|
212
|
+ external_vlm_engine.py — Qwen3.8 Max full-page OCR
|
|
132
|
213
|
postprocess/
|
|
133
|
|
- fixups.py — VaR, V^2
|
|
134
|
|
- corrector.py — Corrector Protocol
|
|
135
|
|
- llm_corrector.py — Qwen2.5-7B локально
|
|
136
|
|
- vlm_corrector.py — Qwen3.8 Max API (Anthropic)
|
|
137
|
|
- markdown/generator.py — Markdown + валидация LaTeX
|
|
|
214
|
+ fixups.py — VaR, V^2
|
|
|
215
|
+ corrector.py — apply_corrector
|
|
|
216
|
+ vlm_corrector.py — Qwen3.8 Max per-block API
|
|
|
217
|
+ qwen_client.py — общий Qwen API-клиент
|
|
|
218
|
+ markdown/generator.py — Markdown + валидация LaTeX
|
|
138
|
219
|
latex/
|
|
139
|
|
- json_to_latex.py — Единый JSON→LaTeX (Paddle + Surya)
|
|
140
|
|
- json_to_latex.py — Единый JSON→LaTeX+HTML (Paddle + Surya + Qwen)
|
|
|
220
|
+ json_to_latex.py — JSON→LaTeX+HTML (Paddle + Surya + Qwen)
|
|
141
|
221
|
```
|
|
142
|
222
|
|
|
143
|
223
|
---
|
|
|
@@ -153,12 +233,6 @@ src/
|
|
153
|
233
|
| Threads | default | `-t 8` | стабильно |
|
|
154
|
234
|
| **Итого** | **430s** | **300s** | **-30%** |
|
|
155
|
235
|
|
|
156
|
|
-### Health-check + retry
|
|
157
|
|
-```
|
|
158
|
|
-llama-server не отвечает → logger.warning → перезапуск SuryaEngine
|
|
159
|
|
-→ повторный health_check → OK / RuntimeError с инструкцией
|
|
160
|
|
-```
|
|
161
|
|
-
|
|
162
|
236
|
---
|
|
163
|
237
|
|
|
164
|
238
|
## Сравнение движков
|
|
|
@@ -166,11 +240,12 @@ llama-server не отвечает → logger.warning → перезапуск S
|
|
166
|
240
|
| Параметр | PaddleOCR | Surya 2 | VLM Qwen3.8 |
|
|
167
|
241
|
|----------|:---:|:---:|:---:|
|
|
168
|
242
|
| Архитектура | Ансамбль 5 моделей | Один VLM (650M) | Cloud VLM |
|
|
169
|
|
-| Русский текст | ~85% | 95%+ | 98%+ |
|
|
|
243
|
+| Русский текст | ~85% (eslav rec) | 95%+ | 98%+ |
|
|
|
244
|
+| Английский текст | ~80% (v6 rec) | 95%+ | 98%+ |
|
|
170
|
245
|
| Формулы | PP-FormulaNet | VLM (отлично) | VLM (отлично) |
|
|
171
|
246
|
| Таблицы | PP-TableMagic + bbox | ❌ CPU timeout | ✅ Pipe-Markdown |
|
|
172
|
247
|
| Inline-формулы | Нет | `<math>` тэги | `$...$` |
|
|
173
|
|
-| Скорость CPU | ~140s | ~300s | ~120s (API) |
|
|
|
248
|
+| Скорость CPU | ~100-160s | ~300s | ~120s (API) |
|
|
174
|
249
|
| Офлайн | ✅ | ✅ | ❌ |
|
|
175
|
250
|
| Размер моделей | ~2.5 GB | ~1.5 GB | 0 (cloud) |
|
|
176
|
251
|
|
|
|
@@ -180,15 +255,15 @@ llama-server не отвечает → logger.warning → перезапуск S
|
|
180
|
255
|
|
|
181
|
256
|
| Этап | PaddleOCR | Surya 2 | VLM |
|
|
182
|
257
|
|------|:---:|:---:|:---:|
|
|
183
|
|
-| OCR (одна страница) | 140s | 300s | 120s |
|
|
184
|
|
-| LLM-корректор | +5 min | не нужен | не нужен |
|
|
|
258
|
+| OCR (одна страница) | 100-160s | 300s | 120s |
|
|
|
259
|
+| Denoise (Stage 1) | +35s | — | — |
|
|
185
|
260
|
| VLM-корректор | +10s/блок | не нужен | — |
|
|
186
|
261
|
| JSON + MD сохранение | <1s | <1s | <1s |
|
|
187
|
262
|
| Merge в один документ | <1s | <1s | <1s |
|
|
188
|
|
-| **Итого на страницу** | ~5 min | ~5 min | ~2 min |
|
|
189
|
|
-| **12 страниц (batch)** | ~28 min* | ~60 min | ~24 min |
|
|
|
263
|
+| **Итого на страницу** | ~3-4 min | ~5 min | ~2 min |
|
|
|
264
|
+| **12 страниц (batch)** | ~15-20 min* | ~60 min | ~24 min |
|
|
190
|
265
|
|
|
191
|
|
-\* PaddleOCR с `--pause 5`
|
|
|
266
|
+\* PaddleOCR с `--pause 5` и `--denoise`
|
|
192
|
267
|
|
|
193
|
268
|
---
|
|
194
|
269
|
|
|
|
@@ -203,9 +278,12 @@ llama-server не отвечает → logger.warning → перезапуск S
|
|
203
|
278
|
|
|
204
|
279
|
## Выводы
|
|
205
|
280
|
|
|
206
|
|
-1. **PP-StructureV3** — надёжен для структуры и формул, русский текст требует LLM/VLM коррекции
|
|
207
|
|
-2. **Surya 2** — лучший русский OCR из коробки, CPU-оптимизации дали -30% времени
|
|
208
|
|
-3. **VLM Qwen3.8 Max** — решает таблицы там, где Surya падает, при приемлемой скорости
|
|
209
|
|
-4. **Гибридный подход**: Surya для текста + VLM для таблиц = лучший баланс
|
|
210
|
|
-5. **LLM/VLM коррекция** эффективна для PaddleOCR, не нужна для Surya 2
|
|
211
|
|
-6. **CPU-ограничения** — основной фактор, GPU сделал бы Surya/PaddleOCR-VL идеальным выбором
|
|
|
281
|
+1. **PP-StructureV3 + v6-det + eslav_rec** — лучший офлайн OCR для русского на CPU: 85-90% текст, 90% формулы, 80% таблицы
|
|
|
282
|
+2. **PP-OCRv6 rec** через `--main-lang=en` — +20% на английских страницах
|
|
|
283
|
+3. **Denoise** — +42% блоков на сканах с шумом, оставлен как опция
|
|
|
284
|
+4. **Бинаризация, upscale** — вредны для нейросетевого OCR, удалены
|
|
|
285
|
+5. **Локальный LLM (Qwen2.5-7B)** — галлюцинирует, удалён
|
|
|
286
|
+6. **VLM-корректор (Qwen3.8 Max)** — отличный, но API
|
|
|
287
|
+7. **Surya 2** — лучший русский OCR из коробки, CPU-оптимизации дали -30% времени
|
|
|
288
|
+8. **Гибридный подход**: Surya для текста + VLM для таблиц = лучший баланс
|
|
|
289
|
+9. **CPU-ограничения** — основной фактор, GPU сделал бы Surya/PaddleOCR-VL идеальным выбором
|