OCR in Rust, tested on scanned pages
1,743
of 1,955 words found
Run ocrs on a scanned PDF
35 s for 10 pages, Mac CPU, 2 threads
12 MB of models, fetched once
With Rust and poppler, next to scan.pdf:
cargo install ocrs-cli --locked
pdfimages -j scan.pdf p
for f in p-*.jpg; do ocrs "$f"; done > scan.txt
ocrs reads images, not PDFs, so pdfimages takes out the page JPEGs as they are. The install builds ocrs from source: 3 min 52 s with 2 jobs. After the first run, a run with no network gave the same text.
The PDF error
A PDF in place of an image stops:
Error: Failed to read image from scan.pdf
Caused by:
The file extension `."pdf"` was not recognized as an image format
ocrs reads the Latin alphabet only, so English and other languages in Latin letters.
ocrs as a Rust library
Next to the page JPEGs:
cargo new rocr && cd rocr
cargo add ocrs@0.14 rten@0.28
cargo add image@0.25 --no-default-features --features jpeg,png
curl -sO https://ocrs-models.s3-accelerate.amazonaws.com/text-detection.onnx
curl -sO https://ocrs-models.s3-accelerate.amazonaws.com/text-recognition.onnx
cargo run --release -- p-*.jpg > scan.txt
Before the last line, move the JPEGs into rocr and save src/main.rs:
use ocrs::{ImageSource, OcrEngine, OcrEngineParams};
use rten::Model;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let engine = OcrEngine::new(OcrEngineParams {
detection_model: Some(Model::load_file("text-detection.onnx")?),
recognition_model: Some(Model::load_file("text-recognition.onnx")?),
..Default::default()
})?;
for path in std::env::args().skip(1) {
let img = image::open(&path)?.into_rgb8();
let src = ImageSource::from_bytes(img.as_raw(), img.dimensions())?;
let input = engine.prepare_input(src)?;
println!("{}", engine.get_text(&input)?);
}
Ok(())
}
Same text as the ocrs program. First build: 2 min 7 s with 2 jobs. Then 10 pages in 30 s.
Tesseract from Rust: leptess
With Homebrew Tesseract, add leptess = "=0.14.0" to
Cargo.toml:
use leptess::LepTess;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut lt = LepTess::new(None, "eng")?;
for path in std::env::args().skip(1) {
lt.set_image(&path)?;
print!("{}", lt.get_utf8_text()?);
}
Ok(())
}
It found the same 1,877 words as the Tesseract program, in 28 s. The build took 31 s. leptess has had no release since February 2023, and it still builds on Tesseract 5.5.4.
ocrs vs Tesseract, RapidOCR and PaddleOCR
| Tool | Words found | Time |
|---|---|---|
| ocrs 0.14.0 | 1,743 | 35 s |
ocrs --beam |
1,742 | 77 s |
| Tesseract 5.5.4 | 1,877 | 47 s |
| RapidOCR 3.10.0 | 1,894 | 55 s |
| PaddleOCR 3.7.0 | 1,908 | 333 s |
| Our Actor | 1,874 |
ocrs is the fastest here, and it missed 134 more words than Tesseract.
Its README says so first: early preview. The
--beam option found 1 word less, in twice the time.
The other rows are from my RapidOCR test, on the same pages.
What the text looks like
The Boy Scouts of the Air
" Keep your head, Hatton," commanded
Hardy; don't talk tommyrot. What in the
name of herring could one of the yacht's boats
One line per text line. It drops some quote marks and question marks, and misreads a few letters: OVer, nsed, bim, con?irm.
Other Rust OCR crates
| Crate | Downloads, 90 days | Newest |
|---|---|---|
| ocrs | 355,652 | Nov 2026 |
| oar-ocr | 88,401 | Oct 2026 |
| leptess | 77,052 | Feb 2023 |
| tesseract | 62,445 | Apr 2025 |
leptess and tesseract call the Tesseract C library, so Tesseract must be installed too. I tested ocrs and leptess.
Need text with nothing to install?
Our Actor runs Tesseract, and RapidOCR on English pages that Tesseract reads badly. It takes PDF, JPG and PNG files and returns Markdown. On the same 10 pages, inside a PDF, it found 1,874 words.
curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
| jq -r '.[].markdown' > book.md
It costs $0.006 a page.
How I tested it
- Pages: the 10 scans of OCR in n8n, proofread Wikisource book pages, 1,955 true words. Each page is one color JPEG, 1,920 x 3,079 pixels, at 300 ppi.
- ocrs-cli 0.14.0 (ocrs 0.14.0, rten 0.28.0), built with Rust 1.96.1 on an Apple Silicon Mac: an 8.7 MB program. pdfimages from poppler 26.03.0. The library: image 0.25.10. leptess 0.14.0 on Homebrew Tesseract 5.5.4.
- CPU, 2 threads (RTEN_NUM_THREADS=2), lowest priority, other jobs on the Mac. Times are rough. Peak memory of the ocrs program: 0.4 GB.
- Score: true words found in order, with the scorer of the tessdata test.
- The Actor: one real run, build 0.1.65, on November 12. Its file holds the same 10 pages plus a typed cover page.
What I did not test
- The tesseract and oar-ocr crates.
- Photos, other languages, Windows, Linux and WebAssembly.
Sources
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Is ocrs better than Tesseract?
Not on scanned book pages yet. ocrs 0.14.0 found 1,743 of 1,955 words in 35 s, and Tesseract 5.5.4 found 1,877 in 47 s. The ocrs README calls it an early preview.
Can ocrs read a PDF?
No. ocrs reads images. Take out the page images first, for example with pdfimages -j scan.pdf p, then run ocrs on each one.
Does ocrs need a network connection?
Only on the first run, to download its 2 models (12 MB) to ~/.cache/ocrs. After that, a run with no network gave the same text.
Can I use Tesseract from Rust?
Yes, with the leptess crate and Tesseract installed. leptess 0.14.0 found the same 1,877 words as the Tesseract program on the same 10 pages.