Skip to content

Repository files navigation

fpo-rust

Crates.io Docs.rs License: MIT

Fast License Plate OCR inference in pure Rust. A high-performance port of fast-plate-ocr that provides fast and accurate license plate character recognition across multiple countries.

Features

  • Pure Rust default backend using tract-onnx for ONNX model inference
  • Optional Tencent NCNN backend for Raspberry Pi 5 / edge CPU inference from .param/.bin models
  • Multiple pre-trained models for global and regional license plates
  • Character-level confidence scores for result validation
  • Region detection for plates with regional information (e.g., country/state)
  • Efficient batch processing with automatic image resizing
  • Offline support with model caching and custom model paths
  • Cross-platform support (Linux, macOS, Windows)

Installation

As a Library

Add to your Cargo.toml:

[dependencies]
fpo-rust = "0.1"

Or use the git repository:

[dependencies]
fpo-rust = { git = "https://github.com/alparslanahmed/fpo-rust" }

Check the crates.io page for the latest version.

As a CLI Tool

Clone the repository and build:

git clone https://github.com/alparslanahmed/fpo-rust
cd fpo-rust
cargo build --release

The binary will be available at target/release/fpo-rust.

Quick Start

Using as a Library

use fpo_rust::{LicensePlateRecognizer, OcrModel, PlateInput};

fn main() -> anyhow::Result<()> {
    // Load a model from the hub (downloads on first use, then cached)
    let recognizer = LicensePlateRecognizer::from_hub(
        OcrModel::CctSV2Global,  // Recommended global model
        false  // force_download
    )?;

    // Run inference on a single image
    let prediction = recognizer.run_one(
        PlateInput::from("path/to/plate.png"),
        true,   // return_confidence scores
        true    // remove_pad_char
    )?;

    println!("Plate: {}", prediction.plate);
    println!("Average Confidence: {:.2}", prediction.avg_char_confidence());
    if let Some(region) = &prediction.region {
        println!("Region: {} ({:.1}%)", region, prediction.region_prob.unwrap_or(0.0) * 100.0);
    }

    Ok(())
}

Using the CLI

Run inference on images:

# Using a hub model (downloads model on first run)
fpo-rust run --model cct-s-v2-global-model plate1.jpg plate2.png

# Using a custom model and config
fpo-rust run --onnx ./models/custom.onnx --config ./models/custom_config.yaml plate.jpg

# Keep padding characters in output
fpo-rust run --model cct-s-v2-global-model --keep-pad plate.jpg

Run with the optional NCNN backend:

# After building with --features ncnn-cpu or --features ncnn-vulkan:
fpo-rust run --backend ncnn --model cct-s-v2-global-model --threads 4 plate.jpg

# Or with explicit converted files:
fpo-rust run --backend ncnn \
  --param ./models/custom.ncnn.param \
  --bin ./models/custom.ncnn.bin \
  --config ./models/custom_config.yaml \
  plate.jpg

Run benchmark:

# Benchmark with 500 iterations, batch size 1
fpo-rust benchmark --model cct-s-v2-global-model

# Custom benchmark settings
fpo-rust benchmark --model cct-s-v2-global-model --iters 1000 --batch 32 --warmup 100

# Include pre/post-processing time in benchmark
fpo-rust benchmark --model cct-s-v2-global-model --include-processing

# Benchmark converted NCNN model on Raspberry Pi 5
fpo-rust benchmark --backend ncnn --model cct-s-v2-global-model --threads 4

Available Models

Global Models (Recommended)

  • cct-s-v2-global-model ⭐ - Compact Convolutional Transformer Small v2, optimized for global plates
  • cct-xs-v2-global-model - XSmall v2, faster but less accurate
  • cct-s-v1-global-model - Small v1, previous version
  • cct-xs-v1-global-model - XSmall v1, lightweight
  • cct-s-relu-v1-global-model - Small v1 ReLU variant
  • cct-xs-relu-v1-global-model - XSmall v1 ReLU variant

Regional Models

  • european-plates-mobile-vit-v2-model - Optimized for European license plates
  • global-plates-mobile-vit-v2-model - Mobile-optimized global model
  • argentinian-plates-cnn-model - CNN model for Argentinian plates
  • argentinian-plates-cnn-synth-model - CNN model trained with synthetic data

Offline Usage & Custom Models

Automatic Caching

Models are automatically downloaded and cached on first use. The default cache location is:

  • Linux/macOS: ~/.cache/fpo-rust/
  • Windows: %APPDATA%\fpo-rust\

Using Custom Models

If you have your own ONNX model and config file:

use fpo_rust::{LicensePlateRecognizer, PlateInput};
use std::path::Path;

fn main() -> anyhow::Result<()> {
    let recognizer = LicensePlateRecognizer::from_files(
        Path::new("./models/my_model.onnx"),
        Path::new("./models/my_config.yaml")
    )?;

    let prediction = recognizer.run_one(
        PlateInput::from("plate.jpg"),
        true,
        true
    )?;

    println!("{}", prediction.plate);
    Ok(())
}

Save Models to Custom Directory

Download models to a specific directory for offline use:

use fpo_rust::{LicensePlateRecognizer, OcrModel};
use std::path::Path;

fn main() -> anyhow::Result<()> {
    let custom_dir = Path::new("./offline_models");

    // Downloads to ./offline_models/ instead of default cache
    let recognizer = LicensePlateRecognizer::from_hub_to_dir(
        OcrModel::CctSV2Global,
        custom_dir,
        false
    )?;

    // Later, you can use the cached models directly
    let recognizer = LicensePlateRecognizer::from_files(
        custom_dir.join("cct_s_v2_global.onnx"),
        custom_dir.join("cct_s_v2_global_plate_config.yaml")
    )?;

    Ok(())
}

CLI: Download Models Offline

Download all models to a directory for offline use:

# Create a models directory
mkdir models

# Run CLI with custom model path (this will download if not present)
fpo-rust run --onnx ./models/my_model.onnx --config ./models/my_config.yaml plate.jpg

Raspberry Pi 5 / NCNN Backend

The NCNN backend is optional. The default binary stays pure Rust and uses ONNX through tract-onnx; NCNN builds require a local Tencent NCNN install because the backend links to NCNN's C API.

Tencent's NCNN docs recommend pnnx for ONNX conversion and produce .ncnn.param plus .ncnn.bin files. See the official NCNN ONNX guide and PNNX options:

1. Build NCNN on Raspberry Pi 5

CPU-only is the most predictable path on Raspberry Pi 5. Vulkan can be experimented with separately, but NCNN's own Raspberry Pi notes call out driver maturity concerns.

sudo apt update
sudo apt install -y build-essential git cmake clang libclang-dev \
  libprotobuf-dev protobuf-compiler libgomp1

git clone --recursive https://github.com/Tencent/ncnn.git
cd ncnn
mkdir build-rpi5-cpu
cd build-rpi5-cpu
cmake -DCMAKE_BUILD_TYPE=Release \
  -DNCNN_VULKAN=OFF \
  -DNCNN_C_API=ON \
  -DNCNN_STRING=ON \
  -DNCNN_STDIO=ON \
  -DNCNN_BUILD_TOOLS=ON \
  -DNCNN_BUILD_EXAMPLES=OFF \
  ..
cmake --build . -j"$(nproc)"
sudo cmake --install . --prefix /opt/ncnn

2. Build fpo-rust with NCNN enabled

cd /path/to/fpo-rust
export NCNN_LIB_DIR=/opt/ncnn/lib
cargo build --release --features ncnn-cpu

If your NCNN install uses a non-standard link setup, these environment variables are supported:

  • NCNN_LIB_DIR: directory containing libncnn.a or libncnn.so
  • NCNN_LINK_KIND: static or dylib (default: static)
  • NCNN_LIB_NAME: library name without prefix/suffix (default: ncnn)
  • NCNN_OPENMP_LIB: OpenMP runtime for static Linux builds (default: gomp; use omp for LLVM OpenMP or none for -DNCNN_OPENMP=OFF)
  • NCNN_VULKAN: set to 1 to link libvulkan when using a Vulkan-enabled NCNN build
  • NCNN_VULKAN_LIBS: override glslang/static shader compiler libraries for Vulkan builds. By default the build script uses pkg-config --libs --static glslang spirv, then falls back to glslang,MachineIndependent,GenericCodeGen,SPIRV,OSDependent.
  • NCNN_EXTRA_LIBS: extra libraries separated by commas, semicolons, or spaces

For an NCNN build compiled with -DNCNN_VULKAN=ON, install Vulkan and glslang development libraries and build with:

sudo apt install -y libvulkan-dev glslang-dev pkg-config

export NCNN_LIB_DIR=/opt/ncnn/lib
cargo build --release --features ncnn-vulkan

On older revisions of this crate, the equivalent manual workaround is:

NCNN_EXTRA_LIBS=gomp,vulkan,glslang,MachineIndependent,GenericCodeGen,SPIRV,OSDependent \
  cargo build --release --features ncnn-cpu

3. Bundled and custom models

The repository includes converted NCNN files for every built-in hub model under models/. For those models, no conversion is required:

./target/release/fpo-rust run \
  --backend ncnn \
  --model cct-s-v2-global-model \
  --threads 4 \
  plate.jpg

To refresh a bundled model or convert into a custom directory, run:

./target/release/fpo-rust convert-ncnn --model cct-s-v2-global-model --pnnx pnnx

For custom models:

./target/release/fpo-rust convert-ncnn \
  --onnx ./models/custom.onnx \
  --config ./models/custom_config.yaml

The converter command passes inputshape=[1,img_height,img_width,channels] from the YAML config and writes:

<model>.ncnn.param
<model>.ncnn.bin

The default is fp16=0 for portability. Add --fp16 if you want PNNX to store fp16 weights and have verified accuracy/performance on your NCNN build.

If NCNN reports a missing blob name, inspect the .ncnn.param file or Netron graph and pass explicit names:

./target/release/fpo-rust run --backend ncnn \
  --param ./models/custom.ncnn.param \
  --bin ./models/custom.ncnn.bin \
  --config ./models/custom_config.yaml \
  --input-name in0 \
  --plate-output out0 \
  --region-output out1 \
  plate.jpg

The built-in fast-plate-ocr ONNX models use NHWC input shape [1, H, W, C]; the NCNN backend preserves that layout for converted models.

Set FPO_NCNN_DEBUG=1 before run to print the NCNN load, input, and output-extraction steps. This is useful when diagnosing native NCNN crashes on a target device.

API Reference

Main Types

LicensePlateRecognizer

The main inference engine.

Methods:

  • from_hub(model: OcrModel, force_download: bool) -> Result<Self>

    • Load a model from the hub with automatic caching
  • from_hub_to_dir(model: OcrModel, save_dir: &Path, force_download: bool) -> Result<Self>

    • Load a model from hub, saving to a specific directory
  • from_files(onnx_path: impl AsRef<Path>, config_path: impl AsRef<Path>) -> Result<Self>

    • Load a custom ONNX model with its config
  • from_hub_ncnn(model: OcrModel, force_download: bool) -> Result<Self> (feature ncnn)

    • Load a bundled NCNN hub model, falling back to converted cache files
  • from_hub_to_dir_ncnn(model: OcrModel, save_dir: &Path, force_download: bool) -> Result<Self> (feature ncnn)

    • Load a converted NCNN model from a specific directory
  • from_ncnn_files(param_path, bin_path, config_path) -> Result<Self> (feature ncnn)

    • Load a converted NCNN model with default blob-name inference
  • from_ncnn_files_with_options(param_path, bin_path, config_path, NcnnOptions) -> Result<Self> (feature ncnn)

    • Load a converted NCNN model with explicit blob names / runtime options
  • run(inputs: &[PlateInput], return_confidence: bool, remove_pad_char: bool) -> Result<Vec<PlatePrediction>>

    • Run inference on multiple images
  • run_one(input: PlateInput, return_confidence: bool, remove_pad_char: bool) -> Result<PlatePrediction>

    • Run inference on a single image

PlateInput<'a>

Represents a single plate image input. Can be:

// From file path
PlateInput::from("plate.jpg")

// From Path object
PlateInput::from(Path::new("plate.jpg"))

// From pre-loaded DynamicImage
use image::open;
let img = open("plate.jpg")?;
PlateInput::from(img)

PlatePrediction

Output of a single inference.

Fields:

  • plate: String - Recognized plate text
  • region: Option<String> - Region/country if detected
  • region_prob: Option<f32> - Confidence of region (0.0-1.0)
  • char_probs: Option<Vec<f32>> - Per-character confidence scores

Methods:

  • avg_char_confidence() -> f32 - Average confidence across all characters

OcrModel

Enum of available hub models:

pub enum OcrModel {
    CctSV2Global,
    CctXsV2Global,
    CctSV1Global,
    CctXsV1Global,
    CctSReluV1Global,
    CctXsReluV1Global,
    ArgentinianPlatesCnn,
    ArgentinianPlatesCnnSynth,
    EuropeanPlatesMobileVitV2,
    GlobalPlatesMobileVitV2,
}

Output Format

Library Output

let pred = recognizer.run_one(input, true, true)?;
println!("Plate: {}", pred.plate);
println!("Avg Confidence: {:.2}", pred.avg_char_confidence());
println!("Region: {}", pred.region.unwrap_or("Unknown".to_string()));

CLI Output

plate.jpg: 34PE7523 [Turkey] (98.7%) - Char Confidence: 0.99
  • 34PE7523 - Recognized plate text
  • [Turkey] - Detected region (if available)
  • (98.7%) - Region confidence score
  • 0.99 - Average character confidence (0.0-1.0)

Performance

Performance varies by model size and hardware. Example benchmarks on standard hardware:

Model Inference Time Memory
cct-xs-v2-global ~10-15ms ~50MB
cct-s-v2-global ~20-30ms ~100MB
mobile-vit-v2-global ~30-50ms ~150MB

Use fpo-rust benchmark --model <name> to measure performance on your hardware.

Configuration

The library respects the XDG_CACHE_HOME environment variable on Linux/macOS. To use a custom cache directory:

# Use custom cache directory
export XDG_CACHE_HOME=/path/to/cache
cargo run -- run --model cct-s-v2-global-model plate.jpg

Building for Production

Optimize the binary:

cargo build --release

The optimized binary will be at target/release/fpo-rust.

Static linking (optional):

For maximum portability, you can create a statically-linked binary. This requires additional setup depending on your platform.

Troubleshooting

Model Download Fails

  • Check your internet connection
  • Verify GitHub is not blocked
  • Use RUST_LOG=debug for more details:
    RUST_LOG=debug cargo run -- run --model cct-s-v2-global-model plate.jpg

Low Confidence Scores

  • Try a different model optimized for your region
  • Ensure plate images have sufficient contrast and resolution
  • Pre-process images (crop, rotate) if plates are at odd angles

Out of Memory

  • Use a smaller model (e.g., cct-xs-v2-global-model)
  • Reduce batch size in benchmarks: --batch 1

Development

Running Tests

cargo test

Running Benchmarks

cargo run --release -- benchmark --model cct-s-v2-global-model --iters 1000

Dependencies

  • tract-onnx - Pure-Rust ONNX runtime
  • Tencent NCNN C API - Optional linked backend (feature ncnn, ncnn-cpu, or ncnn-vulkan)
  • image - Image loading and processing
  • serde - Serialization framework
  • serde_yml - YAML parsing for config files
  • ureq - Lightweight HTTP client for model downloads
  • anyhow - Error handling

License

This project is licensed under the MIT License - see the LICENSE file for details.

References

  • fast-plate-ocr - Original Python implementation
  • tract - Pure Rust ONNX runtime
  • ONNX - Open Neural Network Exchange format

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

About

fast-plate-ocr inference but in Rust

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages