PDF-OCR in Python, Node.js, Go, C# und Rust — ohne Tesseract
Text aus gescannten PDFs mit integriertem OCR extrahieren. Seit v0.3.27 ist OCR für alle Sprachanbindungen verfügbar — Python, Node.js, Go, C# und Rust — über eine einheitliche FFI-Schicht (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 liefert PaddleOCR über ONNX Runtime mit — keine Tesseract-Installation, keine Systemabhängigkeiten, keine Subprozess-Aufrufe. Die OCR-Engine läuft direkt im Prozess. Unterstützt die Modellfamilien PP-OCRv3, PP-OCRv4 und PP-OCRv5.
Hinweis: Für WebAssembly ist OCR nicht verfügbar (benötigt native ONNX Runtime). Für Go, Node.js, C# und Rust mit dem Feature
ocrbauen. Python-Wheels werden standardmäßig mit aktiviertem OCR ausgeliefert.
Python-PDF-OCR ohne Tesseract
Die meisten Python-PDF-OCR-Lösungen setzen Tesseract als Systemabhängigkeit voraus — ein komplexes Setup, das sich je nach Betriebssystem und CI-Umgebung unterscheidet. PDF Oxide enthält die PaddleOCR-Modelle direkt im Python-Wheel:
- Keine Systemabhängigkeiten —
pip install pdf_oxidegenügt - Keine Subprozess-Aufrufe — OCR läuft nativ über ONNX Runtime
- Drei Modellfamilien — PP-OCRv3, PP-OCRv4 und PP-OCRv5
- Automatische Seitenerkennung — erkennt, welche Seiten gescannt und welche textbasiert sind
Vergleich: PDF Oxide OCR vs PyMuPDF + Tesseract
| PDF Oxide | PyMuPDF + Tesseract | |
|---|---|---|
| Installation | pip install pdf_oxide |
pip install pymupdf + System-Tesseract |
| OCR-Engine | PaddleOCR (ONNX) | Tesseract (Subprozess) |
| Setup-Aufwand | Eine Zeile | Betriebssystemspezifische Tesseract-Installation |
| CI/Docker | Keine Zusatzkonfiguration | Benötigt apt-get install tesseract-ocr |
| Modelle enthalten | Ja (im Wheel) | Nein (separater Download) |
Installation
Python
pip install pdf_oxide
Die OCR-Modelle sind im Wheel enthalten. Es sind keine weiteren Downloads nötig.
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#
Das NuGet-Paket bringt OCR in den Standardbinärdateien für Linux, macOS und Windows bereits mit — eine zusätzliche Konfiguration ist nicht erforderlich.
Wann OCR sinnvoll ist
Die meisten PDFs enthalten eingebetteten Text, den extract_text() mit 0,8 ms pro Seite verarbeitet. OCR wird nur in folgenden Fällen gebraucht:
- Gescannte Dokumente — Papierdokumente, die als PDF eingescannt wurden
- Reine Bild-PDFs — aus Fotos oder Screenshots erstellte PDFs
- PDFs mit Text als Bild — manche Generatoren rastern Text
- Hybride Seiten — Seiten mit nativem Text und gleichzeitig gescannten Bildbereichen
PP-OCR-Modellversionen
PDF Oxide unterstützt drei Generationen von PaddleOCR-Modellen. Die Standardkonfiguration passt zu PP-OCRv3 und PP-OCRv4. Die PP-OCRv5-Servermodelle benötigen eine andere Resize-Strategie.
PP-OCRv3 / PP-OCRv4 (Standard)
Für mobile Geräte optimierte Modelle, die Bilder herunterskalieren, damit sie in eine maximale Seitenlänge passen. Gut für die meisten Dokumente.
- Erkennungsmodell: DBNet++ (leichtgewichtig)
- Textlesemodell: SVTR
- Resize-Strategie:
MaxSide— skaliert die längere Seite auf 960 px herunter - Am besten für: Standarddokumente, Mobile- und Edge-Deployments
Python
from pdf_oxide import OcrConfig, OcrEngine
# Standardkonfiguration passt zu v3/v4-Modellen
config = OcrConfig()
engine = OcrEngine("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrEngine};
// Standardkonfiguration: MaxSide { max_side: 960 }
let config = OcrConfig::default();
let engine = OcrEngine::new("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)?;
PP-OCRv5 (Server)
Server-Modelle, die eine hohe Auflösung bewahren, indem sie Bilder bei Bedarf hochskalieren. Deutlich präziser bei dichten oder fein gedruckten Dokumenten.
- Erkennungsmodell: DBNet++ (Server, größer)
- Textlesemodell: SVTR-v5
- Resize-Strategie:
MinSide— stellt sicher, dass die kürzere Seite mindestens 64 px hat, gedeckelt bei 4000 px - Am besten für: hochgenaue Extraktion, Serverumgebungen, dichten Text
Python
from pdf_oxide import OcrConfig, OcrEngine
# v5-Konfiguration: hochaufgelöste Eingabe für Servermodelle
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-Konfiguration: 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)?;
Modellvergleich
| Merkmal | PP-OCRv3/v4 | PP-OCRv5 |
|---|---|---|
| Resize-Strategie | MaxSide (herunter auf 960 px) |
MinSide (hoch, gedeckelt bei 4000 px) |
| Eingangsauflösung | Niedriger (schneller) | Höher (genauer) |
| Größe Erkennungsmodell | ~3 MB | ~12 MB |
| Größe Textlesemodell | ~12 MB | ~25 MB |
| Am besten für | Mobile, Edge, Standarddokumente | Server, dichter Text, Kleindruck |
OcrConfig |
OcrConfig() / OcrConfig::default() |
OcrConfig(use_v5=True) / OcrConfig::v5() |
Seitentyp-Erkennung
PDF Oxide klassifiziert Seiten automatisch, um zu entscheiden, ob OCR nötig ist. extract_text_ocr() erledigt das intern; Sie können Seitentypen aber auch manuell bestimmen.
Gescannte Seiten automatisch erkennen
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:
# Wahrscheinlich gescannt — OCR verwenden
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 => {
// Hat nativen Text und gescannte Bilder — beide Quellen werden zusammengeführt
let text = extract_text_with_ocr(&mut doc, i, Some(&engine), OcrExtractOptions::default())?;
println!("Page {} (hybrid): {}...", i + 1, &text[..100.min(text.len())]);
}
}
}
PageType-Varianten (Rust)
| Variante | Beschreibung |
|---|---|
NativeText |
Seite enthält eingebetteten Text — kein OCR erforderlich |
ScannedPage |
Seite ist komplett gescannt (großes Bild, kein oder minimaler Text) — vollständiges OCR |
HybridPage |
Seite enthält sowohl nativen Text als auch große gescannte Bilder — nativer Text wird mit OCR-Ergebnissen zusammengeführt |
Die Hilfsfunktion needs_ocr() liefert sowohl bei ScannedPage als auch bei HybridPage true:
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())?;
}
Funktionsweise
- PDF Oxide rendert die Seite intern als Bild (mit 300 DPI)
- Das Bild wird gemäß der Erkennungsstrategie skaliert (
MaxSidefür v3/v4,MinSidefür v5) - Der DBNet++-Textdetektor lokalisiert Textregionen als viereckige Begrenzungsrahmen
- Der SVTR-Textleser liest die Zeichen aus jeder erkannten Region
- Die Ergebnisse werden mit Lesereihenfolge-Sortierung zu Text zusammengesetzt
- Bei hybriden Seiten wird OCR-Text mit dem nativen Text verschmolzen
Die gesamte Pipeline läuft prozessintern über ONNX Runtime. Keine externen Binärdateien, keine Subprozess-Aufrufe, keine temporären Dateien.
OCR-Konfiguration
Python
from pdf_oxide import OcrConfig, OcrEngine
# Standard (v3/v4)
config = OcrConfig()
# PP-OCRv5-Servermodelle
config = OcrConfig(use_v5=True)
# Eigene Schwellenwerte
config = OcrConfig(
det_threshold=0.5, # Erkennungs-Konfidenz (0.0-1.0)
box_threshold=0.7, # Box-Konfidenz (0.0-1.0)
rec_threshold=0.6, # Lese-Konfidenz (0.0-1.0)
num_threads=8, # ONNX-Runtime-Threads
max_candidates=500, # Maximale Textregionen
)
# v5 mit eigenen Schwellenwerten
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};
// Standard (v3/v4): MaxSide { max_side: 960 }
let config = OcrConfig::default();
// PP-OCRv5: MinSide { min_side: 64, max_side_limit: 4000 }
let config = OcrConfig::v5();
// Eigener Builder
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) // Stilerkennung aus OCR-Geometrie aktivieren
.build();
// Eigene Resize-Strategie
let config = OcrConfig::builder()
.det_resize_strategy(DetResizeStrategy::MinSide {
min_side: 128,
max_side_limit: 6000,
})
.build();
DetResizeStrategy (Rust)
Steuert, wie Eingabebilder skaliert werden, bevor das Erkennungsmodell läuft.
| Variante | Felder | Beschreibung |
|---|---|---|
MaxSide |
max_side: u32 (Standard: 960) |
HERUNTERSKALIEREN, damit die längere Seite in max_side passt. Standard für PP-OCRv3/v4. |
MinSide |
min_side: u32 (Standard: 64), max_side_limit: u32 (Standard: 4000) |
HOCHSKALIEREN, damit die kürzere Seite mindestens min_side hat, gedeckelt bei max_side_limit. Standard für PP-OCRv5. |
OcrConfig-Felder
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
det_threshold |
f32 |
0.3 |
Wahrscheinlichkeitsschwelle der Erkennung |
box_threshold |
f32 |
0.6 |
Konfidenzschwelle für Boxen |
rec_threshold |
f32 |
0.5 |
Konfidenzschwelle fürs Textlesen |
det_max_side |
u32 |
960 |
Maximale Bilddimension (v3/v4-Kompatibilität) |
det_resize_strategy |
DetResizeStrategy |
MaxSide { 960 } |
Bild-Resize-Strategie |
rec_target_height |
u32 |
48 |
Zielhöhe für Lese-Crops |
num_threads |
usize |
4 |
ONNX-Runtime-Inferenz-Threads |
unclip_ratio |
f32 |
1.5 |
Box-Aufweitungsverhältnis |
max_candidates |
usize |
1000 |
Maximale Anzahl erkannter Textregionen |
detect_styles |
bool |
true |
Schriftstile aus OCR-Geometrie ableiten |
det_model_path |
Option<PathBuf> |
None |
Pfad zum eigenen Erkennungsmodell |
rec_model_path |
Option<PathBuf> |
None |
Pfad zum eigenen Textlesemodell |
dict_path |
Option<PathBuf> |
None |
Pfad zum eigenen Zeichenwörterbuch |
Eigene Modelle
Eigene ONNX-Modelle anstelle der mitgelieferten verwenden:
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();
Stilerkennung
Ist detect_styles aktiviert (Standard), leitet PDF Oxide Schriftstile (fett, Überschriftenebene) aus der OCR-Geometrie ab — Textgröße, Abstand und Position. Das verbessert die Markdown-Ausgabe gescannter Seiten.
let config = OcrConfig::builder()
.detect_styles(true) // Stile aus Textgeometrie ableiten
.build();
OCR vs Tesseract
| Merkmal | PDF Oxide OCR | Tesseract (über PyMuPDF) |
|---|---|---|
| Installation | pip install pdf_oxide |
Systempaket + pytesseract |
| Systemabhängigkeiten | Keine | Tesseract-Binary erforderlich |
| Laufzeit | ONNX (prozessintern) | Subprozess-Aufruf |
| Modellversionen | PP-OCRv3, v4, v5 | Tesseract LSTM |
| Sprachen | Mehrsprachig | Benötigt Sprachpakete |
| Setup-Aufwand | Keiner | Mittel |
| Erkennungsmodell | DBNet++ | Tesseract-intern |
| Textlesemodell | SVTR / SVTR-v5 | Tesseract LSTM |
| Hochauflösung | MinSide-Strategie (v5) |
DPI-Einstellung |
| Seitentyp-Erkennung | Automatisch (nativ/gescannt/hybrid) | Manuell |
Eigene DPI
Steuern Sie die Rendering-Auflösung, wenn PDF-Seiten für OCR in Bilder konvertiert werden:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
# Standard sind 300 DPI — gute Balance zwischen Genauigkeit und Geschwindigkeit
text = doc.extract_text_ocr(0)
# Höhere DPI für bessere Genauigkeit bei Kleindruck
text = doc.extract_text_ocr(0) # DPI wird in Rust über OcrExtractOptions konfiguriert
Rust
use pdf_oxide::ocr::OcrExtractOptions;
// Höhere DPI = genauer, aber langsamer
let options = OcrExtractOptions::default().with_dpi(300.0);
// Niedrigere DPI = schneller, aber weniger genau
let options = OcrExtractOptions::default().with_dpi(150.0);
OCR-Ausgabestruktur (Rust)
Die Methode OcrEngine::ocr_image() liefert detaillierte Ergebnisse mit Konfidenzwerten pro Span:
use pdf_oxide::ocr::OcrEngine;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", Default::default())?;
let output = engine.ocr_image(&image)?;
// Gesamter Text in Lesereihenfolge
println!("{}", output.text_in_reading_order());
// Details pro Span
for span in &output.spans {
println!("Text: '{}' (confidence: {:.2})", span.text, span.confidence);
println!(" Begrenzungsrahmen: {:?}", span.bounding_rect());
println!(" Konfidenz je Zeichen: {:?}", span.char_confidences);
}
// Gesamtkonfidenz
println!("Total confidence: {:.2}", output.total_confidence);
OcrOutput-Felder
| Feld / Methode | Typ | Beschreibung |
|---|---|---|
spans |
Vec<OcrSpan> |
Alle erkannten Textregionen |
total_confidence |
f32 |
Durchschnittliche Konfidenz über alle Spans |
text() |
String |
Gesamter Text mit Leerzeichen verbunden |
text_in_reading_order() |
String |
Text nach Position sortiert (oben nach unten, links nach rechts) |
OcrSpan-Felder
| Feld | Typ | Beschreibung |
|---|---|---|
text |
String |
Erkannter Text |
polygon |
[[f32; 2]; 4] |
Viereckiger Begrenzungsrahmen (4 Ecken) |
confidence |
f32 |
Gesamtkonfidenz (0,0–1,0) |
char_confidences |
Vec<f32> |
Konfidenzwerte pro Zeichen |
Stapel-OCR-Verarbeitung
Ein Verzeichnis gescannter PDFs verarbeiten:
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)?;
}
}
Paralleles 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 zu Markdown
Gescannte Seiten in Markdown umwandeln:
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:
# Gescannte Seite — erst OCR, dann formatieren
text = doc.extract_text_ocr(i)
md = text # OCR-Ausgabe ist Klartext
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);
}
Leistungsaspekte
OCR ist deutlich langsamer als die Textextraktion:
| Operation | Typische Geschwindigkeit |
|---|---|
| Textextraktion | 0,8 ms pro Seite |
| OCR (v3/v4) | 200–1.000 ms pro Seite |
| OCR (v5 Server) | 500–2.000 ms pro Seite |
Die OCR-Geschwindigkeit hängt von Seitenkomplexität, Bildauflösung, Textdichte und Modellversion ab. PP-OCRv5 ist langsamer, dafür aber genauer. Für große Stapel lohnt sich Parallelverarbeitung (siehe Stapel-OCR-Verarbeitung oben).
Modelle aus Bytes laden (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())?;
Verwandte Seiten
- Textextraktion — Standard-Textextraktion
- Markdown-Konvertierung — Markdown mit Überschriftenerkennung
- Seiten-Rendering — Seiten als Bilder rendern (intern von OCR genutzt)
- Stapelverarbeitung — Muster für parallele Verarbeitung
- Text aus PDF extrahieren — Anleitung zur Textextraktion