OCR PDF — Python / Node.js / Go / C# / Rust (без Tesseract)
Видобувайте текст зі сканованих PDF із вбудованим OCR. Починаючи з v0.3.27 OCR доступний в усіх мовних біндингах — Python, Node.js, Go, C# та Rust — через єдиний шар FFI (pdf_ocr_engine_create, pdf_ocr_page_needs_ocr, pdf_ocr_extract_text).
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text_ocr(0)
print(text)
Node.js
const { PdfDocument, OcrEngine } = require("pdf-oxide");
const doc = new PdfDocument("scanned.pdf");
const ocr = new OcrEngine();
if (ocr.pageNeedsOcr(doc, 0)) {
console.log(ocr.extractText(doc, 0));
}
ocr.close();
doc.close();
Go
import pdfoxide "github.com/yfedoseev/pdf_oxide/go"
doc, _ := pdfoxide.Open("scanned.pdf")
defer doc.Close()
ocr, _ := pdfoxide.NewOcrEngine()
defer ocr.Close()
if ocr.NeedsOcr(doc, 0) {
text, _ := ocr.ExtractTextWithOcr(doc, 0)
fmt.Println(text)
}
C#
using PdfOxide.Core;
using PdfOxide.Ocr;
using var doc = PdfDocument.Open("scanned.pdf");
using var ocr = new OcrEngine();
if (ocr.PageNeedsOcr(doc, 0))
{
Console.WriteLine(ocr.ExtractText(doc, 0));
}
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::ocr::{OcrEngine, OcrConfig, OcrExtractOptions, extract_text_with_ocr};
let mut doc = PdfDocument::open("scanned.pdf")?;
let config = OcrConfig::default();
let engine = OcrEngine::new("models/det.onnx", "models/rec.onnx", "models/dict.txt", config)?;
let options = OcrExtractOptions::default();
let text = extract_text_with_ocr(&mut doc, 0, Some(&engine), options)?;
println!("{text}");
PDF Oxide уже містить PaddleOCR через ONNX Runtime — без встановлення Tesseract, без системних залежностей, без викликів підпроцесів. Рушій OCR працює просто всередині процесу. Підтримуються родини моделей PP-OCRv3, PP-OCRv4 та PP-OCRv5.
Увага. У WebAssembly OCR недоступний (потрібен нативний ONNX Runtime). Для Go, Node.js, C# та Rust збирайте з фічею
ocr. Python-колеса поставляються з увімкненим OCR за замовчуванням.
Python PDF OCR без Tesseract
Більшість рішень для OCR PDF у Python вимагають встановлювати Tesseract як системну залежність — а це клопіткий процес, що відрізняється між ОС і CI-середовищами. PDF Oxide тримає моделі PaddleOCR одразу всередині Python-колеса:
- Без системних залежностей — достатньо
pip install pdf_oxide - Без викликів підпроцесів — OCR працює нативно через ONNX Runtime
- Три родини моделей — PP-OCRv3, PP-OCRv4 та PP-OCRv5
- Автоматичне визначення сторінки — бібліотека сама розрізняє скановані й текстові сторінки
Порівняння: OCR PDF Oxide vs PyMuPDF + Tesseract
| PDF Oxide | PyMuPDF + Tesseract | |
|---|---|---|
| Встановлення | pip install pdf_oxide |
pip install pymupdf + системний Tesseract |
| Рушій OCR | PaddleOCR (ONNX) | Tesseract (підпроцес) |
| Складність налаштування | Один рядок | Встановлення Tesseract під конкретну ОС |
| CI/Docker | Додаткових налаштувань не треба | Потрібно apt-get install tesseract-ocr |
| Моделі в комплекті | Так (усередині колеса) | Ні (окреме завантаження) |
Встановлення
Python
pip install pdf_oxide
Моделі OCR уже лежать у колесі. Нічого окремо завантажувати не треба.
Rust
[dependencies]
pdf_oxide = { version = "0.3", features = ["ocr"] }
Go
go build -tags ocr ./...
Node.js
npm install pdf-oxide --build-from-source -- --features ocr
C#
NuGet-пакет постачається з увімкненим OCR у стандартних бінарниках Linux / macOS / Windows — додаткового налаштування не треба.
Коли потрібен OCR
У більшості PDF текст уже вбудований, і extract_text() обробляє його за 0,8 мс на сторінку. OCR потрібен лише для:
- Сканованих документів — паперові документи, оцифровані в PDF
- Суто графічних PDF — PDF, створених із фото чи знімків екрана
- PDF, де текст — це картинка — деякі генератори растеризують текст
- Гібридних сторінок — сторінок, де поруч є і нативний текст, і скановані області
Версії моделей PP-OCR
PDF Oxide підтримує три покоління моделей PaddleOCR. Конфігурація за замовчуванням підходить для PP-OCRv3 і PP-OCRv4. Серверні моделі PP-OCRv5 вимагають іншої стратегії ресайзу.
PP-OCRv3 / PP-OCRv4 (за замовчуванням)
Мобільні моделі зменшують зображення, щоб довга сторона вписалася у заданий максимум. Добре підходять для більшості документів.
- Модель детекції: DBNet++ (легка)
- Модель розпізнавання: SVTR
- Стратегія ресайзу:
MaxSide— довга сторона зменшується до 960px - Найкраще підходить для: звичайних документів, мобільних і edge-розгортань
Python
from pdf_oxide import OcrConfig, OcrEngine
# Конфігурація за замовчуванням працює з моделями v3/v4
config = OcrConfig()
engine = OcrEngine("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrEngine};
// Конфігурація за замовчуванням: MaxSide { max_side: 960 }
let config = OcrConfig::default();
let engine = OcrEngine::new("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)?;
PP-OCRv5 (серверна)
Серверні моделі зберігають високу роздільну здатність, за потреби збільшуючи зображення. Помітно точніші на щільному чи дрібному тексті.
- Модель детекції: DBNet++ (серверна, більша)
- Модель розпізнавання: SVTR-v5
- Стратегія ресайзу:
MinSide— коротка сторона не менша за 64px, стеля 4000px - Найкраще підходить для: задач високої точності, серверних сценаріїв, щільного тексту
Python
from pdf_oxide import OcrConfig, OcrEngine
# Конфіг v5: високороздільний вхід для серверних моделей
config = OcrConfig(use_v5=True)
engine = OcrEngine("det_v5.onnx", "rec_v5.onnx", "dict_v5.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrEngine};
// Конфіг v5: MinSide { min_side: 64, max_side_limit: 4000 }
let config = OcrConfig::v5();
let engine = OcrEngine::new("det_v5.onnx", "rec_v5.onnx", "dict_v5.txt", config)?;
Порівняння моделей
| Параметр | PP-OCRv3/v4 | PP-OCRv5 |
|---|---|---|
| Стратегія ресайзу | MaxSide (зменшує до 960px) |
MinSide (збільшує, стеля 4000px) |
| Вхідна роздільна здатність | Нижча (швидше) | Вища (точніше) |
| Розмір моделі детекції | ~3 МБ | ~12 МБ |
| Розмір моделі розпізнавання | ~12 МБ | ~25 МБ |
| Найкраще підходить для | Мобільних, edge, звичайних документів | Серверів, щільного тексту, дрібного шрифту |
OcrConfig |
OcrConfig() / OcrConfig::default() |
OcrConfig(use_v5=True) / OcrConfig::v5() |
Визначення типу сторінки
PDF Oxide автоматично класифікує сторінки, щоб вирішити, чи потрібен OCR. extract_text_ocr() робить це усередині, але тип сторінки можна визначати й вручну.
Автоматичне розпізнавання сканованих сторінок
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("mixed.pdf")
for i in range(doc.page_count()):
text = doc.extract_text(i)
if len(text.strip()) < 50:
# Імовірно скан — вмикаємо OCR
text = doc.extract_text_ocr(i)
print(f"Page {i + 1} (OCR): {text[:100]}...")
else:
print(f"Page {i + 1} (text): {text[:100]}...")
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::ocr::{detect_page_type, PageType, OcrEngine, OcrConfig, OcrExtractOptions, extract_text_with_ocr};
let mut doc = PdfDocument::open("mixed.pdf")?;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", OcrConfig::default())?;
for i in 0..doc.page_count() {
let page_type = detect_page_type(&mut doc, i)?;
match page_type {
PageType::NativeText => {
let text = doc.extract_text(i)?;
println!("Page {} (native): {}...", i + 1, &text[..100.min(text.len())]);
}
PageType::ScannedPage => {
let text = extract_text_with_ocr(&mut doc, i, Some(&engine), OcrExtractOptions::default())?;
println!("Page {} (OCR): {}...", i + 1, &text[..100.min(text.len())]);
}
PageType::HybridPage => {
// Є і нативний текст, і скановані картинки — обидва джерела зливаються
let text = extract_text_with_ocr(&mut doc, i, Some(&engine), OcrExtractOptions::default())?;
println!("Page {} (hybrid): {}...", i + 1, &text[..100.min(text.len())]);
}
}
}
Варіанти PageType (Rust)
| Варіант | Опис |
|---|---|
NativeText |
На сторінці вбудований текст — OCR не потрібен |
ScannedPage |
Сторінка повністю сканована (велике зображення, тексту немає або мінімум) — OCR на всю сторінку |
HybridPage |
На сторінці і нативний текст, і великі скановані області — нативний текст зливається з результатами OCR |
Допоміжна функція needs_ocr() повертає true і для ScannedPage, і для HybridPage:
use pdf_oxide::ocr::needs_ocr;
if needs_ocr(&mut doc, 0)? {
let text = extract_text_with_ocr(&mut doc, 0, Some(&engine), OcrExtractOptions::default())?;
}
Як це працює
- PDF Oxide рендерить сторінку у внутрішнє зображення (300 DPI)
- Зображення ресайзиться згідно з обраною стратегією (
MaxSideдля v3/v4,MinSideдля v5) - Детектор DBNet++ знаходить області тексту як чотирикутні рамки
- Розпізнавач SVTR зчитує символи з кожної знайденої області
- Результати збираються у текст із сортуванням за порядком читання
- Для гібридних сторінок OCR-текст зливається з нативним
Увесь конвеєр виконується всередині процесу через ONNX Runtime. Без зовнішніх бінарників, підпроцесів і тимчасових файлів.
Налаштування OCR
Python
from pdf_oxide import OcrConfig, OcrEngine
# За замовчуванням (v3/v4)
config = OcrConfig()
# Серверні моделі PP-OCRv5
config = OcrConfig(use_v5=True)
# Власні пороги
config = OcrConfig(
det_threshold=0.5, # Впевненість детекції (0.0-1.0)
box_threshold=0.7, # Впевненість рамки (0.0-1.0)
rec_threshold=0.6, # Впевненість розпізнавання (0.0-1.0)
num_threads=8, # Потоки ONNX Runtime
max_candidates=500, # Максимум текстових областей
)
# v5 із власними порогами
config = OcrConfig(use_v5=True, det_threshold=0.4, num_threads=8)
engine = OcrEngine("det.onnx", "rec.onnx", "dict.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrConfigBuilder, DetResizeStrategy};
// За замовчуванням (v3/v4): MaxSide { max_side: 960 }
let config = OcrConfig::default();
// PP-OCRv5: MinSide { min_side: 64, max_side_limit: 4000 }
let config = OcrConfig::v5();
// Збірка вручну
let config = OcrConfig::builder()
.det_threshold(0.5)
.box_threshold(0.7)
.rec_threshold(0.6)
.num_threads(8)
.max_candidates(500)
.detect_styles(true) // Увімкнути визначення стилів за геометрією OCR
.build();
// Власна стратегія ресайзу
let config = OcrConfig::builder()
.det_resize_strategy(DetResizeStrategy::MinSide {
min_side: 128,
max_side_limit: 6000,
})
.build();
DetResizeStrategy (Rust)
Керує тим, як вхідне зображення змінюється в розмірі перед запуском моделі детекції.
| Варіант | Поля | Опис |
|---|---|---|
MaxSide |
max_side: u32 (за замовчуванням: 960) |
ЗМЕНШУЄ так, щоб довга сторона вмістилася у max_side. За замовчуванням для PP-OCRv3/v4. |
MinSide |
min_side: u32 (за замовчуванням: 64), max_side_limit: u32 (за замовчуванням: 4000) |
ЗБІЛЬШУЄ так, щоб коротка сторона була не менша за min_side, зі стелею max_side_limit. За замовчуванням для PP-OCRv5. |
Поля OcrConfig
| Поле | Тип | За замовчуванням | Опис |
|---|---|---|---|
det_threshold |
f32 |
0.3 |
Поріг імовірності детекції |
box_threshold |
f32 |
0.6 |
Поріг впевненості рамки |
rec_threshold |
f32 |
0.5 |
Поріг впевненості розпізнавання |
det_max_side |
u32 |
960 |
Максимальний розмір сторони (сумісність v3/v4) |
det_resize_strategy |
DetResizeStrategy |
MaxSide { 960 } |
Стратегія ресайзу |
rec_target_height |
u32 |
48 |
Цільова висота кропів для розпізнавання |
num_threads |
usize |
4 |
Потоки інференсу ONNX Runtime |
unclip_ratio |
f32 |
1.5 |
Коефіцієнт розширення рамки |
max_candidates |
usize |
1000 |
Максимум текстових областей для виявлення |
detect_styles |
bool |
true |
Визначати стилі шрифту за геометрією OCR |
det_model_path |
Option<PathBuf> |
None |
Шлях до власної моделі детекції |
rec_model_path |
Option<PathBuf> |
None |
Шлях до власної моделі розпізнавання |
dict_path |
Option<PathBuf> |
None |
Шлях до власного словника символів |
Власні моделі
Використовуйте власні ONNX-моделі замість вбудованих:
Rust
use pdf_oxide::ocr::OcrConfig;
let config = OcrConfig::builder()
.det_model_path("models/custom_det.onnx")
.rec_model_path("models/custom_rec.onnx")
.dict_path("models/custom_dict.txt")
.build();
Визначення стилів
Коли detect_styles увімкнено (за замовчуванням), PDF Oxide відновлює стилі шрифту (жирний, рівень заголовка) з геометрії OCR — розміру тексту, інтервалів і позиції. Це покращує конвертацію сканованих сторінок у Markdown.
let config = OcrConfig::builder()
.detect_styles(true) // Відновлювати стилі з геометрії тексту
.build();
OCR vs Tesseract
| Параметр | OCR PDF Oxide | Tesseract (через PyMuPDF) |
|---|---|---|
| Встановлення | pip install pdf_oxide |
Системний пакет + pytesseract |
| Системні залежності | Немає | Потрібен бінарник Tesseract |
| Середовище виконання | ONNX (усередині процесу) | Виклик підпроцесу |
| Версії моделей | PP-OCRv3, v4, v5 | Tesseract LSTM |
| Мови | Багатомовно | Потрібні мовні пакети |
| Складність налаштування | Відсутня | Середня |
| Модель детекції | DBNet++ | Вбудована в Tesseract |
| Модель розпізнавання | SVTR / SVTR-v5 | Tesseract LSTM |
| Підтримка високої роздільної здатності | Стратегія MinSide (v5) |
Налаштування DPI |
| Визначення типу сторінки | Автоматичне (нативна/сканована/гібридна) | Вручну |
Власний DPI
Керуйте роздільною здатністю рендерингу, коли сторінки PDF перетворюються на зображення для OCR:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
# За замовчуванням 300 DPI — добрий баланс точності й швидкості
text = doc.extract_text_ocr(0)
# Вищий DPI — краща точність на дрібному шрифті
text = doc.extract_text_ocr(0) # У Rust DPI задається через OcrExtractOptions
Rust
use pdf_oxide::ocr::OcrExtractOptions;
// Вищий DPI = точніше, але повільніше
let options = OcrExtractOptions::default().with_dpi(300.0);
// Нижчий DPI = швидше, але менш точно
let options = OcrExtractOptions::default().with_dpi(150.0);
Структура результату OCR (Rust)
Метод OcrEngine::ocr_image() повертає детальний результат з оцінкою впевненості для кожного span:
use pdf_oxide::ocr::OcrEngine;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", Default::default())?;
let output = engine.ocr_image(&image)?;
// Повний текст у порядку читання
println!("{}", output.text_in_reading_order());
// Деталі по кожному span
for span in &output.spans {
println!("Text: '{}' (confidence: {:.2})", span.text, span.confidence);
println!(" Обмежувальна рамка: {:?}", span.bounding_rect());
println!(" Впевненість за символами: {:?}", span.char_confidences);
}
// Загальна впевненість
println!("Total confidence: {:.2}", output.total_confidence);
Поля OcrOutput
| Поле / Метод | Тип | Опис |
|---|---|---|
spans |
Vec<OcrSpan> |
Усі розпізнані текстові області |
total_confidence |
f32 |
Середня впевненість по всіх span |
text() |
String |
Увесь текст, з’єднаний пробілами |
text_in_reading_order() |
String |
Текст, відсортований за позицією (згори донизу, зліва направо) |
Поля OcrSpan
| Поле | Тип | Опис |
|---|---|---|
text |
String |
Розпізнаний текст |
polygon |
[[f32; 2]; 4] |
Чотирикутна рамка (4 кути) |
confidence |
f32 |
Загальна впевненість (0,0–1,0) |
char_confidences |
Vec<f32> |
Оцінки впевненості за кожним символом |
Пакетна обробка OCR
Обробка каталогу сканованих PDF:
Python
from pdf_oxide import PdfDocument, PdfError
from pathlib import Path
pdf_dir = Path("scans/")
output_dir = Path("text-output/")
output_dir.mkdir(exist_ok=True)
for pdf_path in pdf_dir.glob("*.pdf"):
try:
doc = PdfDocument(str(pdf_path))
pages = []
for i in range(doc.page_count()):
text = doc.extract_text(i)
if len(text.strip()) < 50:
text = doc.extract_text_ocr(i)
pages.append(text)
out_path = output_dir / pdf_path.with_suffix(".txt").name
out_path.write_text("\n\n".join(pages), encoding="utf-8")
except PdfError as e:
print(f"Error: {pdf_path.name}: {e}")
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::ocr::{OcrEngine, OcrConfig, OcrExtractOptions, extract_text_with_ocr, needs_ocr};
use std::fs;
use std::path::Path;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", OcrConfig::default())?;
let options = OcrExtractOptions::default();
for entry in fs::read_dir("scans/")? {
let path = entry?.path();
if path.extension().map_or(false, |e| e == "pdf") {
let mut doc = PdfDocument::open(path.to_str().unwrap())?;
let mut all_text = String::new();
for i in 0..doc.page_count() {
let text = if needs_ocr(&mut doc, i)? {
extract_text_with_ocr(&mut doc, i, Some(&engine), options.clone())?
} else {
doc.extract_text(i)?
};
all_text.push_str(&text);
all_text.push_str("\n\n");
}
let out_path = Path::new("text-output/")
.join(path.file_stem().unwrap())
.with_extension("txt");
fs::write(out_path, &all_text)?;
}
}
Паралельний OCR (Python)
from pdf_oxide import PdfDocument
from multiprocessing import Pool
from pathlib import Path
def ocr_pdf(pdf_path: str) -> dict:
doc = PdfDocument(pdf_path)
text = ""
for i in range(doc.page_count()):
text += doc.extract_text_ocr(i) + "\n"
return {"file": pdf_path, "text": text}
pdf_files = [str(p) for p in Path("scans/").glob("*.pdf")]
with Pool(4) as pool:
results = pool.map(ocr_pdf, pdf_files)
OCR у Markdown
Перетворіть скановані сторінки на Markdown:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned-report.pdf")
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
if len(md.strip()) < 50:
# Сканована сторінка — спершу OCR, потім форматування
text = doc.extract_text_ocr(i)
md = text # Вихід OCR — це звичайний текст
print(f"--- Page {i + 1} ---")
print(md)
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::ocr::{OcrEngine, OcrConfig, OcrExtractOptions, needs_ocr, extract_text_with_ocr};
let mut doc = PdfDocument::open("scanned-report.pdf")?;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", OcrConfig::default())?;
for i in 0..doc.page_count() {
let text = if needs_ocr(&mut doc, i)? {
extract_text_with_ocr(&mut doc, i, Some(&engine), OcrExtractOptions::default())?
} else {
doc.to_markdown(i, &Default::default())?
};
println!("--- Page {} ---\n{}", i + 1, text);
}
Нотатки щодо продуктивності
OCR помітно повільніший за видобування вбудованого тексту:
| Операція | Типова швидкість |
|---|---|
| Видобування тексту | 0,8 мс на сторінку |
| OCR (v3/v4) | 200–1 000 мс на сторінку |
| OCR (v5 серверна) | 500–2 000 мс на сторінку |
Швидкість OCR залежить від складності сторінки, роздільної здатності зображення, щільності тексту й версії моделі. PP-OCRv5 повільніша, але точніша. Для великих пакетів використовуйте паралельну обробку (див. «Пакетна обробка OCR» вище).
Завантаження моделей із байтів (Rust)
use pdf_oxide::ocr::{OcrEngine, OcrConfig};
let det_bytes = std::fs::read("models/det.onnx")?;
let rec_bytes = std::fs::read("models/rec.onnx")?;
let dict = std::fs::read_to_string("models/dict.txt")?;
let engine = OcrEngine::from_bytes(&det_bytes, &rec_bytes, &dict, OcrConfig::default())?;
Пов’язані сторінки
- Видобування тексту — стандартне видобування тексту
- Конвертація у Markdown — Markdown із визначенням заголовків
- Рендеринг сторінок — рендеринг сторінок у зображення (OCR використовує це всередині)
- Пакетна обробка — шаблони паралельної обробки
- Видобування тексту з PDF — довідник із видобування тексту