Skip to main content

Fast CPU OCR — PaddleOCR PP-OCRv6 tiny (lightweight), reimplemented in Rust + ONNX Runtime. ~7x faster than PaddlePaddle, self-contained wheels with models bundled.

Project description

faster-paddle

Fast, CPU-only OCR in Rust with Python bindings — a self-contained reimplementation of PaddleOCR's PP-OCRv6 detection + recognition pipeline powered by ONNX Runtime.

  • ~9× faster than paddleocr on CPU for the same models and output (parallel detection pre/post-processing + a concurrent recognition session pool).
  • 📦 Self-contained — the tiny + small ONNX models are bundled inside the wheel. No paddlepaddle, no model downloads for tiny/small.
  • 🎚️ Three model sizes: tiny (default, fastest), small, and medium (higher accuracy; downloaded once on first use and cached).
  • 🦀 Pure-Rust pre/post-processing (detection DB decode, minAreaRect, perspective crop, CTC decode, reading-order text reconstruction). No OpenCV.
  • 🖥️ Prebuilt wheels for Linux, Windows, macOS (x86-64 + arm64).
paddleocr (PaddlePaddle, CPU)        22.7 s / image
faster-paddle (Rust + ONNXRuntime)    2.5 s / image     →  ~9× faster

(test image 3157×4464, AMD Ryzen 7 5800X3D; both after warm-up, same weights.)


Install

pip install faster-paddle

Usage

import faster_paddle

# One-shot, using a shared default engine (lazily initialized):
with open("document.jpg", "rb") as f:
    result = faster_paddle.ocr(f.read())

print(result["text"])              # reading-order reconstructed text
for idx, b in result["bounds"].items():
    print(idx, b["text"], b["confidence"], b["topLeftCoord"], b["bottomRightCoord"])

Reuse an explicit engine (recommended for servers — load the models once):

from faster_paddle import OcrEngine

# model_size: "tiny" (default), "small", or "medium"
engine = OcrEngine(model_size="tiny", threads=None, rec_batch=6)

result = engine.ocr(image_bytes)                 # raw jpeg/png/webp/bmp/tiff/gif bytes
result = engine.ocr_base64(b64_string)           # base64-encoded image

Optional preprocessing

ocr / ocr_base64 take four optional flags (all default False), applied — when enabled — in the optimal order, all in fast parallel Rust:

result = engine.ocr(
    image_bytes,
    resize=True,     # 1. downscale to ≤ 2100×3000 (aspect preserved) if larger
    denoise=True,    # 2. fast Non-Local-Means denoise (grayscale)
    deskew=True,     # 3. detect skew (Canny + Hough) and rotate to straighten
    binarize=True,   # 4. Sauvola adaptive thresholding (clean black/white)
)

Order rationale: resize first (everything downstream is then faster), denoise before angle detection and thresholding, deskew on the cleaned image, binarize last to produce the final B/W. Enabling resize typically makes OCR faster overall (less detector work). Any of denoise/deskew/binarize converts the image to grayscale.

Returned bounds are always in the original image's coordinate space — even when resize or deskew changes the working image, the boxes are mapped back so they line up with your input.

Preprocess only (no OCR)

prepare runs the same preprocessing in one pass and returns the prepared image as PNG bytes (grayscale once any of denoise/deskew/binarize is on, else color). If every option is False the original bytes are returned unchanged.

prepared = engine.prepare(image_bytes, resize=True, denoise=True, deskew=True, binarize=False)
# or module-level:  faster_paddle.prepare(image_bytes, resize=True, ...)

with open("prepared.png", "wb") as f:
    f.write(prepared)
# you can also feed it straight back in:
result = engine.ocr(prepared)

Model sizes

size bundled det+rec notes
tiny ✅ yes ~6 MB default, fastest, lightweight
small ✅ yes ~31 MB better accuracy
medium ⬇️ on demand ~138 MB best accuracy; downloaded once from the GitHub release and cached under your user cache dir

tiny and small are embedded in the wheel (offline). medium exceeds PyPI's file-size limit, so the first OcrEngine(model_size="medium") downloads it once (needs network that time only) and caches it for subsequent runs.

Result shape

{
  "text": "full reconstructed text...",
  "structured_text": "layout-preserving text (see below)",
  "bounds": {
     0: {
        "topLeftCoord":     (x1, y1),
        "bottomRightCoord": (x2, y2),
        "text":             "line text",
        "confidence":       0.97,
     },
     1: { ... },
  }
}

text and bounds match the JSON contract of the original paddle-ocr-api service, so it is a drop-in replacement.

structured_text

A spatial reconstruction that reads left-to-right, top-to-bottom while preserving the visual layout: vertical whitespace gaps split the page into columns/panes (each read fully before the next), and within each one the rows are laid out as a monospace grid, so indentation (tree nesting) and aligned sub-columns (key/value tables) are kept. Single-glyph UI icon noise is dropped.

Use structured_text for screenshots, forms, table/tree UIs, and code — anything where spatial structure carries meaning. Use text for dense multi-column prose: there the absolute pixel spacing of structured_text produces very wide lines, so the column-merging text reconstruction reads better. Both are always returned, so you can pick per use case.

Example structured_text for a two-pane database UI:

PNS
 Collections (11)
   System
   CAGED
   IPCMAPS_MUNICIPIO
 Functions
 Users

Key                                                Value
        OUTRAS_DESPESAS_POTENCIAL_DE_CONSUMO_EM... 7332964
        TOTAL_DO_CONSUMO_URBANO_E_RURAL            613855113
        CD_MUNI_IBGE                               1100015

API

faster_paddle.ocr(image, resize=False, denoise=False, deskew=False, binarize=False) -> dict OCR encoded image bytes (shared default engine).
faster_paddle.ocr_base64(image_base64, resize=False, denoise=False, deskew=False, binarize=False) -> dict OCR a base64 image string.
OcrEngine(model_size="tiny", threads=None, rec_batch=None) Construct a reusable engine.
OcrEngine.ocr(image, resize=False, denoise=False, deskew=False, binarize=False) -> dict OCR encoded image bytes.
OcrEngine.ocr_base64(image_base64, resize=False, denoise=False, deskew=False, binarize=False) -> dict OCR a base64 image string.
faster_paddle.prepare(image, resize=False, denoise=False, deskew=False, binarize=False) -> bytes Preprocess only; returns PNG bytes (no OCR).
OcrEngine.prepare(image, resize=False, denoise=False, deskew=False, binarize=False) -> bytes Preprocess only; returns PNG bytes (no OCR).
  • resize/denoise/deskew/binarize: optional preprocessing (see above).
  • model_size: "tiny" (default), "small", or "medium".
  • threads: ONNX Runtime intra-op threads. Defaults to the number of physical CPU cores (SMT/logical threads tend to slow compute-bound inference down).
  • rec_batch: recognition batch size (default 6).

Calls are thread-safe (serialized internally) and release the GIL during inference.


How it works

The pipeline faithfully mirrors PaddleOCR's lightweight path:

  1. Detection — resize (min-side 736, clamp max-side 4000, round to ×32), normalize (BGR mean/std), run the DB detector.
  2. DB post-process — threshold 0.2, connected components, minAreaRect, box score ≥ 0.4, unclip ratio 1.4, rescale to source coordinates.
  3. Sort boxes top-to-bottom / left-to-right; crop each via perspective warp.
  4. Recognition — resize each crop to H=48, normalize, batch, run the CTC recognizer ([N, T, 6906]), greedy CTC decode.
  5. Reconstruct reading-order text with dynamic column/line detection.

Detection matches PaddlePaddle at 96 % IoU>0.5 with 0.93 character-level similarity on the recognized text; the residual difference is ONNX-Runtime vs PaddlePaddle floating-point numerics, not the algorithm.

The bundled models are PP-OCRv6_tiny_det and PP-OCRv6_tiny_rec exported with paddle2onnx.

Building from source

pip install maturin
maturin develop --release      # build + install into the current environment
# or
maturin build --release        # produce a wheel in target/wheels/

Requires a Rust toolchain. ONNX Runtime is fetched automatically by the ort crate at build time and linked into the extension.

Tests

cargo test --release                 # Rust unit tests (geometry, resize, CTC)
maturin develop --release            # then the Python integration tests:
python faster_paddle/tests/test_integration.py

The integration tests check the result shape, known-text detection, that the recognition session pool is deterministic, that bounds map back to original coordinates after resize, that all preprocessing options run, and a speed regression guard.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

faster_paddle-0.0.11.tar.gz (32.3 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

faster_paddle-0.0.11-cp38-abi3-win_amd64.whl (72.9 MB view details)

Uploaded CPython 3.8+Windows x86-64

faster_paddle-0.0.11-cp38-abi3-manylinux_2_34_x86_64.whl (74.0 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.34+ x86-64

faster_paddle-0.0.11-cp38-abi3-manylinux_2_28_aarch64.whl (74.6 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.28+ ARM64

faster_paddle-0.0.11-cp38-abi3-macosx_11_0_arm64.whl (72.9 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

File details

Details for the file faster_paddle-0.0.11.tar.gz.

File metadata

  • Download URL: faster_paddle-0.0.11.tar.gz
  • Upload date:
  • Size: 32.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for faster_paddle-0.0.11.tar.gz
Algorithm Hash digest
SHA256 eb2293c5f3eeeb7583af9aa3a5f8dcf173c701a80db2ca872bf1d3493ab4b98b
MD5 0bc9a8a9b4c764c0441a28f28ffb982a
BLAKE2b-256 6407f9321c8577bfcb7a0f0f9571fe98030a3223a3aafe130a567f4d2a4ad7fe

See more details on using hashes here.

File details

Details for the file faster_paddle-0.0.11-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for faster_paddle-0.0.11-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 72340033125a43c146e72ebff0e93fd22267b8d0764d4321139f3691f1ff82c8
MD5 9ac812aa11445b49a01e87ac53a24a74
BLAKE2b-256 b20354bc4ea8c7deef9db38b6a892816fe7de9400678657f6e4508d2bc521509

See more details on using hashes here.

File details

Details for the file faster_paddle-0.0.11-cp38-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for faster_paddle-0.0.11-cp38-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 2557bbf12616a75d8d38ec19dca4dd5666d1aa451ca538a3fcac5eedf40edb24
MD5 91c04861ddd39e84af03e96b619b566b
BLAKE2b-256 8663e623c81dcd3d00eb9d4d33cb163dd8b4f18a8e831617a6228ce67d470fee

See more details on using hashes here.

File details

Details for the file faster_paddle-0.0.11-cp38-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for faster_paddle-0.0.11-cp38-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 50f695098b5fae634ef1c4ce71838827bb7090cd88d2a168e974f9dcc2a4734b
MD5 b3bd72b0961c9f4649fcb54ee935fd37
BLAKE2b-256 c4137824282ca19455522fd533123aed1b9e0d977ee7d7c9c9d32cd561621197

See more details on using hashes here.

File details

Details for the file faster_paddle-0.0.11-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for faster_paddle-0.0.11-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 55811d1ce05b9f656b910a226fcb7bd1133b8c5a1c020051074f1fbe4282d64d
MD5 9af5599d23e642d84efb583d0cad7e88
BLAKE2b-256 2fdaab5f3348548ec8b07ed916b698403caf52de743071b308ad2e5bc2fa7b74

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page