OCR de PDF em Python, Node.js, Go, C# e Rust — sem Tesseract
Extraia texto de PDFs digitalizados com OCR embutido. A partir da v0.3.27, o OCR está exposto em todos os bindings — Python, Node.js, Go, C# e Rust — por meio de uma camada FFI unificada (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}");
O PDF Oxide traz o PaddleOCR via ONNX Runtime — sem instalar Tesseract, sem dependências de sistema, sem chamadas a subprocessos. O motor de OCR roda direto dentro do processo. Ele suporta as famílias de modelos PP-OCRv3, PP-OCRv4 e PP-OCRv5.
Nota: O OCR não está disponível no WebAssembly (ele exige o ONNX Runtime nativo). Para Go, Node.js, C# e Rust, compile com a feature
ocr. Os wheels de Python já vêm com OCR habilitado por padrão.
OCR de PDF em Python sem Tesseract
A maioria das soluções de OCR de PDF em Python exige instalar o Tesseract como dependência de sistema — uma configuração complicada que varia entre sistemas operacionais e ambientes de CI. O PDF Oxide inclui os modelos PaddleOCR direto no wheel do Python:
- Sem dependências de sistema —
pip install pdf_oxidejá resolve - Sem chamadas a subprocessos — o OCR roda nativo via ONNX Runtime
- Três famílias de modelos — PP-OCRv3, PP-OCRv4 e PP-OCRv5
- Detecção automática de página — identifica quais páginas são digitalizadas e quais têm texto
Comparativo: OCR do PDF Oxide vs PyMuPDF + Tesseract
| PDF Oxide | PyMuPDF + Tesseract | |
|---|---|---|
| Instalação | pip install pdf_oxide |
pip install pymupdf + Tesseract do sistema |
| Motor de OCR | PaddleOCR (ONNX) | Tesseract (subprocesso) |
| Complexidade da configuração | Uma linha | Instalação do Tesseract específica do SO |
| CI/Docker | Sem configuração extra | Exige apt-get install tesseract-ocr |
| Modelos incluídos | Sim (no wheel) | Não (download separado) |
Instalação
Python
pip install pdf_oxide
Os modelos de OCR já vêm no wheel. Não é preciso baixar nada a mais.
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#
O pacote NuGet entrega OCR já habilitado nos binários padrão de Linux, macOS e Windows — sem configuração extra.
Quando usar OCR
A maioria dos PDFs contém texto embutido, e o extract_text() processa a 0,8ms por página. O OCR só é necessário para:
- Documentos digitalizados — papéis digitalizados em PDF
- PDFs apenas com imagem — PDFs criados a partir de fotos ou capturas de tela
- PDFs com texto como imagem — alguns geradores rasterizam o texto
- Páginas híbridas — páginas com texto nativo e regiões digitalizadas
Versões dos modelos PP-OCR
O PDF Oxide suporta três gerações de modelos PaddleOCR. A configuração padrão atende PP-OCRv3 e PP-OCRv4. Os modelos de servidor PP-OCRv5 pedem uma estratégia diferente de redimensionamento.
PP-OCRv3 / PP-OCRv4 (padrão)
Modelos otimizados para mobile que escalam imagens para baixo a fim de caber em um tamanho máximo de lado. Funcionam bem na maior parte dos documentos.
- Modelo de detecção: DBNet++ (leve)
- Modelo de reconhecimento: SVTR
- Estratégia de redimensionamento:
MaxSide— reduz o lado mais longo até 960px - Indicado para: documentos padrão, deploy mobile/edge
Python
from pdf_oxide import OcrConfig, OcrEngine
# A config padrão funciona com modelos v3/v4
config = OcrConfig()
engine = OcrEngine("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrEngine};
// Config padrão: MaxSide { max_side: 960 }
let config = OcrConfig::default();
let engine = OcrEngine::new("det_v4.onnx", "rec_v4.onnx", "dict.txt", config)?;
PP-OCRv5 (servidor)
Modelos de nível servidor que preservam alta resolução aumentando as imagens para cima quando necessário. Bem mais precisos em documentos densos ou com letras pequenas.
- Modelo de detecção: DBNet++ (servidor, maior)
- Modelo de reconhecimento: SVTR-v5
- Estratégia de redimensionamento:
MinSide— garante que o lado mais curto tenha pelo menos 64px, com teto de 4000px - Indicado para: extração de alta precisão, ambientes de servidor, texto denso
Python
from pdf_oxide import OcrConfig, OcrEngine
# Config v5: entrada em alta resolução para modelos de servidor
config = OcrConfig(use_v5=True)
engine = OcrEngine("det_v5.onnx", "rec_v5.onnx", "dict_v5.txt", config)
Rust
use pdf_oxide::ocr::{OcrConfig, OcrEngine};
// Config 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)?;
Comparativo dos modelos
| Característica | PP-OCRv3/v4 | PP-OCRv5 |
|---|---|---|
| Estratégia de redimensionamento | MaxSide (reduz para 960px) |
MinSide (aumenta, teto de 4000px) |
| Resolução de entrada | Mais baixa (mais rápida) | Mais alta (mais precisa) |
| Tamanho do modelo de detecção | ~3 MB | ~12 MB |
| Tamanho do modelo de reconhecimento | ~12 MB | ~25 MB |
| Indicado para | Mobile, edge, documentos padrão | Servidor, texto denso, letras pequenas |
OcrConfig |
OcrConfig() / OcrConfig::default() |
OcrConfig(use_v5=True) / OcrConfig::v5() |
Detecção do tipo de página
O PDF Oxide classifica as páginas automaticamente para decidir se precisa de OCR. O extract_text_ocr() faz isso internamente, mas você também pode detectar os tipos de página manualmente.
Detectar páginas digitalizadas automaticamente
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:
# Provavelmente digitalizada — usar 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 => {
// Tem texto nativo e imagens digitalizadas — funde as duas fontes
let text = extract_text_with_ocr(&mut doc, i, Some(&engine), OcrExtractOptions::default())?;
println!("Page {} (hybrid): {}...", i + 1, &text[..100.min(text.len())]);
}
}
}
Variantes de PageType (Rust)
| Variante | Descrição |
|---|---|
NativeText |
A página tem texto embutido — não precisa de OCR |
ScannedPage |
A página é totalmente digitalizada (imagem grande, sem texto ou mínimo) — OCR completo |
HybridPage |
A página tem texto nativo e imagens digitalizadas grandes — funde o texto nativo com os resultados do OCR |
O helper needs_ocr() devolve true tanto para ScannedPage quanto para 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())?;
}
Como funciona
- O PDF Oxide renderiza a página internamente como imagem (em 300 DPI)
- A imagem é redimensionada conforme a estratégia de detecção (
MaxSidepara v3/v4,MinSidepara v5) - O detector de texto DBNet++ localiza as regiões de texto como caixas delimitadoras quadrilaterais
- O reconhecedor SVTR lê os caracteres de cada região detectada
- Os resultados são montados em texto ordenado por leitura
- Em páginas híbridas, o texto do OCR é fundido com o texto nativo
A pipeline inteira roda em processo sobre o ONNX Runtime. Sem binários externos, sem subprocessos e sem arquivos temporários.
Configuração do OCR
Python
from pdf_oxide import OcrConfig, OcrEngine
# Padrão (v3/v4)
config = OcrConfig()
# Modelos de servidor PP-OCRv5
config = OcrConfig(use_v5=True)
# Thresholds personalizados
config = OcrConfig(
det_threshold=0.5, # Confiança de detecção (0.0-1.0)
box_threshold=0.7, # Confiança da caixa (0.0-1.0)
rec_threshold=0.6, # Confiança de reconhecimento (0.0-1.0)
num_threads=8, # Threads do ONNX Runtime
max_candidates=500, # Máximo de regiões de texto
)
# v5 com thresholds personalizados
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};
// Padrão (v3/v4): MaxSide { max_side: 960 }
let config = OcrConfig::default();
// PP-OCRv5: MinSide { min_side: 64, max_side_limit: 4000 }
let config = OcrConfig::v5();
// Builder personalizado
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) // Liga a detecção de estilos a partir da geometria do OCR
.build();
// Estratégia de redimensionamento personalizada
let config = OcrConfig::builder()
.det_resize_strategy(DetResizeStrategy::MinSide {
min_side: 128,
max_side_limit: 6000,
})
.build();
DetResizeStrategy (Rust)
Controla como as imagens de entrada são redimensionadas antes da execução do modelo de detecção.
| Variante | Campos | Descrição |
|---|---|---|
MaxSide |
max_side: u32 (padrão: 960) |
REDUZ para que o lado mais longo caiba em max_side. Padrão para PP-OCRv3/v4. |
MinSide |
min_side: u32 (padrão: 64), max_side_limit: u32 (padrão: 4000) |
AUMENTA para que o lado mais curto tenha pelo menos min_side, com teto em max_side_limit. Padrão para PP-OCRv5. |
Campos de OcrConfig
| Campo | Tipo | Padrão | Descrição |
|---|---|---|---|
det_threshold |
f32 |
0.3 |
Threshold de probabilidade da detecção |
box_threshold |
f32 |
0.6 |
Threshold de confiança da caixa |
rec_threshold |
f32 |
0.5 |
Threshold de confiança do reconhecimento |
det_max_side |
u32 |
960 |
Dimensão máxima da imagem (compat. v3/v4) |
det_resize_strategy |
DetResizeStrategy |
MaxSide { 960 } |
Estratégia de redimensionamento |
rec_target_height |
u32 |
48 |
Altura-alvo dos crops de reconhecimento |
num_threads |
usize |
4 |
Threads de inferência do ONNX Runtime |
unclip_ratio |
f32 |
1.5 |
Razão de expansão da caixa |
max_candidates |
usize |
1000 |
Máximo de regiões de texto a detectar |
detect_styles |
bool |
true |
Detectar estilos de fonte a partir da geometria do OCR |
det_model_path |
Option<PathBuf> |
None |
Caminho para modelo de detecção customizado |
rec_model_path |
Option<PathBuf> |
None |
Caminho para modelo de reconhecimento customizado |
dict_path |
Option<PathBuf> |
None |
Caminho para dicionário de caracteres customizado |
Modelos customizados
Use seus próprios modelos ONNX no lugar dos que já vêm inclusos:
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();
Detecção de estilos
Quando detect_styles está ligado (padrão), o PDF Oxide infere estilos de fonte (negrito, nível de título) a partir da geometria do OCR — tamanho do texto, espaçamento e posição. Isso melhora a conversão para Markdown a partir de páginas digitalizadas.
let config = OcrConfig::builder()
.detect_styles(true) // Inferir estilos a partir da geometria do texto
.build();
OCR vs Tesseract
| Característica | OCR do PDF Oxide | Tesseract (via PyMuPDF) |
|---|---|---|
| Instalação | pip install pdf_oxide |
Pacote do sistema + pytesseract |
| Dependências de sistema | Nenhuma | Binário do Tesseract obrigatório |
| Runtime | ONNX (em processo) | Chamada a subprocesso |
| Versões de modelo | PP-OCRv3, v4, v5 | Tesseract LSTM |
| Idiomas | Multi-idioma | Exige pacotes de idioma |
| Complexidade de setup | Zero | Moderada |
| Modelo de detecção | DBNet++ | Interno do Tesseract |
| Modelo de reconhecimento | SVTR / SVTR-v5 | Tesseract LSTM |
| Suporte a alta resolução | Estratégia MinSide (v5) |
Ajuste de DPI |
| Detecção do tipo de página | Automática (nativo/digitalizado/híbrido) | Manual |
DPI personalizado
Controle a resolução de renderização ao converter páginas PDF em imagens para OCR:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
# O padrão é 300 DPI — bom equilíbrio entre precisão e velocidade
text = doc.extract_text_ocr(0)
# DPI mais alto para melhor precisão em letras pequenas
text = doc.extract_text_ocr(0) # Em Rust, o DPI é configurado via OcrExtractOptions
Rust
use pdf_oxide::ocr::OcrExtractOptions;
// DPI maior = mais preciso, porém mais lento
let options = OcrExtractOptions::default().with_dpi(300.0);
// DPI menor = mais rápido, porém menos preciso
let options = OcrExtractOptions::default().with_dpi(150.0);
Estrutura de saída do OCR (Rust)
O método OcrEngine::ocr_image() devolve resultados detalhados, com confiança por span:
use pdf_oxide::ocr::OcrEngine;
let engine = OcrEngine::new("det.onnx", "rec.onnx", "dict.txt", Default::default())?;
let output = engine.ocr_image(&image)?;
// Texto completo em ordem de leitura
println!("{}", output.text_in_reading_order());
// Detalhes por span
for span in &output.spans {
println!("Text: '{}' (confidence: {:.2})", span.text, span.confidence);
println!(" Caixa delimitadora: {:?}", span.bounding_rect());
println!(" Confiança por caractere: {:?}", span.char_confidences);
}
// Confiança geral
println!("Total confidence: {:.2}", output.total_confidence);
Campos de OcrOutput
| Campo / Método | Tipo | Descrição |
|---|---|---|
spans |
Vec<OcrSpan> |
Todas as regiões de texto reconhecidas |
total_confidence |
f32 |
Confiança média de todos os spans |
text() |
String |
Todo o texto concatenado por espaços |
text_in_reading_order() |
String |
Texto ordenado por posição (cima para baixo, esquerda para direita) |
Campos de OcrSpan
| Campo | Tipo | Descrição |
|---|---|---|
text |
String |
Texto reconhecido |
polygon |
[[f32; 2]; 4] |
Caixa delimitadora quadrilateral (4 cantos) |
confidence |
f32 |
Confiança geral (0,0–1,0) |
char_confidences |
Vec<f32> |
Pontuações de confiança por caractere |
Processamento de OCR em lote
Processa um diretório de PDFs digitalizados:
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 paralelo (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 para Markdown
Converte páginas digitalizadas em 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:
# Página digitalizada — OCR e depois formatar
text = doc.extract_text_ocr(i)
md = text # A saída do OCR é texto simples
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);
}
Considerações de desempenho
O OCR é bem mais lento que a extração de texto:
| Operação | Velocidade típica |
|---|---|
| Extração de texto | 0,8ms por página |
| OCR (v3/v4) | 200–1.000ms por página |
| OCR (v5 servidor) | 500–2.000ms por página |
A velocidade do OCR depende da complexidade da página, da resolução da imagem, da densidade do texto e da versão do modelo. O PP-OCRv5 é mais lento, porém mais preciso. Em lotes grandes, considere processamento paralelo (veja Processamento de OCR em lote acima).
Carregar modelos a partir de bytes (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())?;
Páginas relacionadas
- Extração de texto — extração de texto padrão
- Conversão para Markdown — Markdown com detecção de títulos
- Renderização de páginas — renderiza páginas como imagens (usado internamente pelo OCR)
- Processamento em lote — padrões de processamento paralelo
- Extrair texto de PDF — guia de extração de texto