Recognize text in images (OCR) in Rust
Optical Character Recognition (OCR) is a powerful technology that enables computers to extract text from images. In this DevTip, we explore implementing OCR in Rust using the Tesseract library and effective image processing techniques for accurate text extraction.
Introduction
Rust's performance and safety guarantees make it an excellent choice for implementing OCR solutions. Whether you are building a document processing system or adding text extraction capabilities to your application, Rust provides a robust ecosystem for handling these tasks efficiently.
Prerequisites
- Current stable Rust (
image0.25.10 requires Rust 1.88 or later) - Tesseract 5.0 or later
- pkg-config, a C compiler, and libclang (Xcode Command Line Tools on macOS)
- English Tesseract language data (
eng.traineddata) - Basic knowledge of Rust and Cargo
Setting up the project
First, create a new Rust project and add the required dependencies to your Cargo.toml:
[dependencies]
tesseract = "0.15.2"
image = "0.25.10"
anyhow = "1.0"
tempfile = "3"
Keep Cargo.lock in your application repository to reproduce the resolved dependency versions.
Installing Tesseract
Before using the Rust bindings, install Tesseract on your system.
On Ubuntu/Debian
sudo apt-get install tesseract-ocr tesseract-ocr-eng tesseract-ocr-osd libtesseract-dev libleptonica-dev pkg-config libclang-dev build-essential
On macOS
brew install tesseract pkgconf
Basic OCR implementation
This example demonstrates how to extract text from an image using Tesseract in Rust.
use anyhow::Result;
use tesseract::Tesseract;
fn main() -> Result<()> {
// Initialize Tesseract for English language
let mut ocr = Tesseract::new(None, Some("eng"))?
.set_image("input.png")?;
// Retrieve the extracted text
let text = ocr.get_text()?;
println!("{}", text);
Ok(())
}
Handling different image formats
Preprocessing images can enhance OCR accuracy. The following function converts an image to grayscale, saves it temporarily, and performs OCR. Each call owns a separate temporary directory, which is removed when it leaves scope, including when an operation returns an error.
use anyhow::{Context, Result};
use tesseract::Tesseract;
fn prepare_image_for_ocr(image_path: &str) -> Result<String> {
// Load the image and convert it to grayscale
let img = image::open(image_path)?.grayscale();
// Save the preprocessed image to a temporary file
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path().join("processed.png");
img.save(&temp_path)?;
// Leptonica rewrites /tmp paths on macOS; resolve symlinks before passing the filename.
let temp_path = temp_path.canonicalize()?;
// Perform OCR on the processed image
let mut ocr = Tesseract::new(None, Some("eng"))?
.set_image(temp_path.to_str().context("Temporary path is not valid UTF-8")?)?;
let text = ocr.get_text()?;
Ok(text)
}
Advanced OCR configuration
Tesseract can be fine-tuned by specifying configuration options such as character whitelists or page segmentation modes.
use anyhow::Result;
use tesseract::Tesseract;
fn configure_ocr() -> Result<String> {
let mut ocr = Tesseract::new(None, Some("eng"))?
.set_variable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")?
.set_variable("tessedit_pageseg_mode", "1")?
.set_image("input.png")?;
let text = ocr.get_text()?;
Ok(text)
}
- The setting
tessedit_char_whitelistrestricts recognition to specified characters, reducing potential errors. - Adjusting
tessedit_pageseg_modecan optimize how Tesseract segments the image for various layouts.
Best practices for OCR in Rust
-
Image Preprocessing
- Convert images to grayscale to enhance text visibility.
- Ensure a resolution of at least 300 DPI for clear text.
- Remove noise by applying image filters.
-
Performance Optimization
- Use a separate Tesseract instance for each concurrently processed image.
- Implement caching for frequently processed documents.
- Consider batch processing when handling large volumes of images.
-
Error Handling
Proper error handling ensures your application gracefully handles issues during OCR operations.
use anyhow::{Context, Result};
use tesseract::Tesseract;
fn robust_ocr(image_path: &str) -> Result<String> {
let image_path = std::fs::canonicalize(image_path)
.context("Failed to load image")?;
let mut ocr = Tesseract::new(None, Some("eng"))
.context("Failed to initialize Tesseract")?
.set_image(image_path.to_str().context("Image path is not valid UTF-8")?)
.context("Failed to load image")?;
let text = ocr.get_text()
.context("Failed to perform OCR")?;
Ok(text)
}
Handling multiple languages
Tesseract supports multiple languages. This example demonstrates recognizing text in English,
French, and German. Install all three language models first: on Ubuntu/Debian, add
tesseract-ocr-fra and tesseract-ocr-deu; on macOS, brew install tesseract-lang supplies
additional languages. Run tesseract --list-langs to verify them. For a custom model directory, set
TESSDATA_PREFIX to the directory containing the .traineddata files.
use anyhow::{Context, Result};
use tesseract::Tesseract;
fn multilingual_ocr(image_path: &str) -> Result<String> {
// Initialize Tesseract with multiple languages: English, French, and German
let image_path = std::fs::canonicalize(image_path)?;
let mut ocr = Tesseract::new(None, Some("eng+fra+deu"))?
.set_image(image_path.to_str().context("Image path is not valid UTF-8")?)?;
let text = ocr.get_text()?;
Ok(text)
}
Conclusion
Implementing OCR in Rust with Tesseract presents a robust solution for extracting text from images. Rust's safety and performance, combined with Tesseract's mature OCR capabilities, empower you to build efficient text extraction systems.
For added functionality, you can complement your OCR solution using Transloadit's Document Processing Service.
