Skip to content

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())?;
}

Як це працює

  1. PDF Oxide рендерить сторінку у внутрішнє зображення (300 DPI)
  2. Зображення ресайзиться згідно з обраною стратегією (MaxSide для v3/v4, MinSide для v5)
  3. Детектор DBNet++ знаходить області тексту як чотирикутні рамки
  4. Розпізнавач SVTR зчитує символи з кожної знайденої області
  5. Результати збираються у текст із сортуванням за порядком читання
  6. Для гібридних сторінок 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())?;

Пов’язані сторінки