Menggunakan Rust
Crate rusto-rs adalah mesin inti Rust murni yang mentenagai seluruh binding platform RustO!. Crate ini menyediakan abstraksi tanpa biaya (zero-cost), jaminan keamanan memori, serta pra-pemrosesan gambar terakselerasi SIMD.
Instalasi
Tambahkan rusto-rs ke dalam Cargo.toml:
[dependencies]
rusto-rs = "0.3.2"
1. Inisialisasi Mesin (InitializeConfig)
Inisialisasi mesin OCR satu kali per siklus aplikasi atau thread worker. RustO! menyediakan konfigurasi preset yang sesuai dengan generasi model PaddleOCR:
use rusto::{InitializeConfig, RustO};
// Inisialisasi dengan preset standar PP-OCRv6
let config = InitializeConfig::ppv6("models/det.rten", "models/rec.rten", "models/dict.txt")
.with_det_thresh(0.3)
.with_det_box_thresh(0.6)
.with_limit_side_len(736)
.with_limit_type("min")
.with_unclip_ratio(2.0)
.with_use_dilation(true)
.with_text_score(0.5)
.with_rec_batch_num(6);
let mut ocr = RustO::initialize(config)?;
Model Tambahan (Klasifikasi Orientasi & CLS)
Untuk dokumen hasil pemindaian yang miring atau teks terbalik, sertakan model klasifikasi orientasi:
// PP-OCRv5 dengan pengklasifikasi baris terbalik 180° (CLS) dan rotasi halaman penuh
let config = InitializeConfig::ppv5("models/det.rten", "models/rec.rten", "models/dict.txt")
.with_cls_threshold("models/cls.rten", 0.9)
.with_orientation_threshold("models/orient.rten", 0.85);
let mut ocr = RustO::initialize(config)?;
Komposisi Mesin Generik & Pluggable
RustO! menyediakan trait InferenceSession dan enum EngineSession sehingga Anda dapat mengganti mesin inferensi dengan mudah (misalnya ONNX Runtime ort, Tract, Candle, atau mesin pengujian tiruan) tanpa menyentuh logika geometri OCR:
use rusto::{InferenceSession, EngineError, RustO, InitializeConfig, TextDetector, TextRecognizer};
use ndarray::ArrayD;
use std::collections::HashMap;
// Pilihan 1: Berikan sesi inferensi kustom
let config = InitializeConfig::ppv6("models/det.rten", "models/rec.rten", "models/dict.txt");
let ocr = RustO::with_custom_engines(config, my_det_session, my_rec_session)?;
// Pilihan 2: Gabungkan TextDetector dan TextRecognizer yang telah dikonfigurasi sebelumnya
let det = TextDetector::new(config.det.clone())?;
let rec = TextRecognizer::new(config.rec.clone())?;
let ocr = RustO::with_components(config.global, det, rec)?;
2. Opsi Permintaan Lokal (OcrRunOptions)
Batas ukuran gambar, penskalaan detektor, dilatasi morfologi, rasio unclip, dan mode keluaran dapat dikonfigurasi per panggilan deteksi melalui OcrRunOptions tanpa mengubah sesi model yang telah diinisialisasi:
use rusto::{
DetectionRunOptions, ImageSource, OcrRunOptions, OutputGranularity,
PostprocessRunOptions,
};
let options = OcrRunOptions {
output: OutputGranularity::Lines, // OutputGranularity::Words atau OutputGranularity::Spatial
line_y_threshold: Some(0.5), // Toleransi dinamis pengelompokan baris
word_x_threshold: Some(0.4), // Toleransi pemisahan kata/kolom
text_score: Some(0.55), // Batas skor kepercayaan pengenalan teks
classification: Some(true), // Evaluasi CLS jika model tersedia
max_side_len: Some(1600.0), // Batas panjang sisi maksimum gambar
min_height: Some(32.0), // Tinggi minimum kotak teks
detection: Some(DetectionRunOptions {
limit_side_len: Some(960),
limit_type: Some("max".into()),
..Default::default()
}),
postprocess: Some(PostprocessRunOptions {
use_dilation: Some(true), // Dilatasi morfologi untuk struk redup/pudar
unclip_ratio: Some(2.0),
threshold: Some(0.3),
box_threshold: Some(0.6),
..Default::default()
}),
..Default::default()
};
3. Sumber Gambar yang Didukung
ImageSource menerima path berkas sistem maupun buffer byte dalam memori:
use rusto::ImageSource;
use std::path::PathBuf;
// 1. Path berkas filesystem (memuat & mendekode secara efisien)
let source_path = ImageSource::Path(PathBuf::from("data/invoice.jpg"));
// 2. Buffer byte mentah dalam memori RAM (PNG, JPEG, WebP, BMP)
let raw_bytes: Vec<u8> = std::fs::read("data/invoice.jpg")?;
let source_bytes = ImageSource::Bytes(raw_bytes);
4. Contoh Praktis & Penanganan Keluaran
Contoh A: Mengekstrak Baris Teks Terstruktur
use rusto::{DetectTextResult, ImageSource, OcrRunOptions, OutputGranularity, RustO};
let mut options = OcrRunOptions::default();
options.output = OutputGranularity::Lines;
let result = ocr.detect_text(&ImageSource::Path("invoice.png".into()), &options)?;
if let DetectTextResult::Structured(items) = result {
for item in items {
println!("Teks: {}", item.text);
println!("Skor: {:.3}", item.score);
println!("Titik Poligon [TL, TR, BR, BL]: {:?}", item.box_points);
println!(
"Frame Kotak Batas: Kiri={}, Atas={}, Lebar={}, Tinggi={}",
item.frame.left, item.frame.top, item.frame.width, item.frame.height
);
}
}
Contoh Keluaran:
[
{
"text": "FAKTUR #98214",
"score": 0.992,
"box_points": [
[42.0, 35.0],
[210.0, 35.0],
[210.0, 58.0],
[42.0, 58.0]
],
"frame": { "left": 42.0, "top": 35.0, "width": 168.0, "height": 23.0 }
},
{
"text": "Total Tagihan: Rp4.500.000",
"score": 0.985,
"box_points": [
[42.0, 280.0],
[265.0, 280.0],
[265.0, 304.0],
[42.0, 304.0]
],
"frame": { "left": 42.0, "top": 280.0, "width": 223.0, "height": 24.0 }
}
]
Contoh B: Rekonstruksi Tata Letak Spasial Dokumen
Pertahankan perataan kolom tabel, faktur, dan formulir menjadi format teks rapi:
use rusto::{DetectTextResult, ImageSource, OcrRunOptions, OutputGranularity};
let mut options = OcrRunOptions::default();
options.output = OutputGranularity::Spatial;
options.line_y_threshold = Some(0.5);
options.word_x_threshold = Some(0.4);
if let DetectTextResult::Spatial(layout_text) = ocr.detect_text(
&ImageSource::Path("table.png".into()),
&options,
)? {
println!("{layout_text}");
}
Contoh Keluaran:
Deskripsi Barang Kuantitas Harga Satuan Subtotal
Widget Pro X100 2 Rp750.000 Rp1.500.000
Kabel Daya Tahan Tinggi 4 Rp125.000 Rp500.000
Braket Pemasangan 1 Rp350.000 Rp350.000
────────────────────────────────────────────────────────────────
Total Pembayaran: Rp2.350.000
Contoh C: Pemrosesan Batch Multi-Thread dengan Rayon
Karena instance RustO memiliki status sesi inferensi, gunakan instance lokal per thread atau worker pool untuk beban kerja paralel:
use rayon::prelude::*;
use rusto::{DetectTextResult, ImageSource, InitializeConfig, OcrRunOptions, RustO};
use std::path::PathBuf;
let image_paths: Vec<PathBuf> = vec![
"doc1.jpg".into(),
"doc2.jpg".into(),
"doc3.jpg".into(),
"doc4.jpg".into(),
];
// Proses secara bersamaan di beberapa inti CPU
image_paths.par_iter().for_each(|path| {
// Setiap thread menginisialisasi sesinya sendiri yang ringan
let config = InitializeConfig::ppv6("models/det.rten", "models/rec.rten", "models/dict.txt");
let mut thread_ocr = RustO::initialize(config).expect("Gagal inisialisasi thread OCR");
let result = thread_ocr.detect_text(
&ImageSource::Path(path.clone()),
&OcrRunOptions::default(),
).expect("Terjadi kesalahan OCR");
if let DetectTextResult::Structured(lines) = result {
println!("Selesai memproses {:?}: ditemukan {} baris", path, lines.len());
}
});