Skip to content

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 sistemapip install pdf_oxide já 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

  1. O PDF Oxide renderiza a página internamente como imagem (em 300 DPI)
  2. A imagem é redimensionada conforme a estratégia de detecção (MaxSide para v3/v4, MinSide para v5)
  3. O detector de texto DBNet++ localiza as regiões de texto como caixas delimitadoras quadrilaterais
  4. O reconhecedor SVTR lê os caracteres de cada região detectada
  5. Os resultados são montados em texto ordenado por leitura
  6. 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