Reconocer texto en imágenes (OCR) en Rust
Crea un programa de línea de comandos en Rust que reciba el nombre de un archivo de imagen e imprima el texto que reconoce Tesseract. Empieza con una imagen generada que contenga un número de factura conocido y luego prueba con tu propia imagen. El mismo programa incluye una ruta opcional de procesamiento en escala de grises y devuelve un estado de salida distinto de cero cuando no puede reconocer texto.
Este tutorial usa archivos locales y datos de idioma inglés. Un resultado correcto con la muestra comprueba que los bindings de Rust, las bibliotecas nativas y el modelo funcionan juntos; no demuestra la precisión con recibos, escritura a mano ni diseños de página complejos.
Requisitos previos
- Linux y Bash, con Rust y Cargo instalados.
- Tesseract y Leptonica nativos, incluidos sus encabezados y bibliotecas de desarrollo.
pkg-config, un compilador de C/C++ y libclang para los bindings nativos.- Datos de idioma inglés,
eng.traineddata. - ImageMagick 7 y la fuente Liberation Sans para generar la imagen de muestra.
El ejemplo se ejecutó con Rust/Cargo 1.98.1, Tesseract 5.5.3, Leptonica 1.87.0 e ImageMagick
7.1.2-31. Usa ese conjunto de herramientas para este tutorial. La versión mínima de Rust declarada
por el propio crate image es
1.88.0, pero no es una versión mínima probada para este proyecto
completo.
Instalar Tesseract
El crate de Rust no instala Tesseract por sí mismo. Sigue la guía de instalación nativa para tu distribución e instala las bibliotecas de desarrollo, además del motor y el modelo de inglés.
En Ubuntu/Debian, los nombres de los paquetes relevantes incluyen libtesseract-dev,
libleptonica-dev y tesseract-ocr-eng. Los paquetes de la distribución pueden
ofrecer versiones diferentes de las del conjunto de herramientas probado.
Las instrucciones ejecutables de este tutorial cubren Linux, no una configuración verificada de macOS o Windows. Comprueba las dependencias nativas antes de crear el proyecto:
rustc --version &&
cargo --version &&
tesseract --version &&
pkg-config --modversion tesseract lept &&
tesseract --list-langs &&
magick -version
La lista de idiomas debe incluir eng. Si pkg-config no
encuentra tesseract o lept, resuelve la ausencia del paquete
de desarrollo o su ruta de búsqueda antes de compilar. La lista de fuentes de ImageMagick
(magick -list font) debería incluir Liberation-Sans.
Configurar el proyecto
Elige un directorio fuera de un espacio de trabajo de Cargo existente. Pega este bloque para crear un
nuevo proyecto binario. Rechaza un directorio rust-ocr existente y permanece en tu
directorio original:
(cargo new --bin --edition 2021 --vcs none rust-ocr)
Dentro de rust-ocr, reemplaza todo el contenido de
Cargo.toml por:
[package]
name = "rust-ocr"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.104"
image = { version = "=0.25.10", default-features = false, features = ["jpeg", "png"] }
tempfile = "=3.27.0"
tesseract = "=0.15.2"
La primera compilación crea Cargo.lock. Consérvalo con tu aplicación y usa
--locked en las compilaciones posteriores para que Cargo rechace cambios en la
resolución de dependencias. Las bibliotecas nativas y los datos de idioma se instalan por separado;
el archivo de bloqueo no fija sus versiones.
Implementación básica de OCR
Reemplaza rust-ocr/src/main.rs por este programa completo:
use std::path::PathBuf;
use anyhow::{bail, ensure, Context, Result};
use tesseract::{PageSegMode, Tesseract};
const USAGE: &str = "Usage: rust-ocr <image> [--grayscale]";
fn main() -> Result<()> {
let mut arguments = std::env::args_os().skip(1);
let input = PathBuf::from(arguments.next().context(USAGE)?);
let grayscale = match arguments.next() {
None => false,
Some(flag) if flag == "--grayscale" => true,
_ => bail!(USAGE),
};
ensure!(arguments.next().is_none(), USAGE);
let input = input.canonicalize().context("Could not open input image")?;
let temporary_directory = if grayscale {
Some(tempfile::tempdir().context("Could not create temporary directory")?)
} else {
None
};
let image_path = match &temporary_directory {
Some(directory) => {
let decoded = image::open(&input).context("Could not decode input image")?;
ensure!(
matches!(decoded.color(), image::ColorType::L8 | image::ColorType::Rgb8),
"Grayscale mode requires opaque 8-bit PNG or JPEG"
);
let processed = directory.path().join("processed.png");
decoded.grayscale().save(&processed)
.context("Could not write grayscale image")?;
processed.canonicalize().context("Could not resolve temporary image")?
}
None => input,
};
let filename = image_path.to_str().context("Image path must be valid UTF-8")?;
let mut ocr = Tesseract::new(None, Some("eng"))
.context("Could not initialize English OCR; check eng.traineddata")?
.set_image(filename)
.context("Could not decode input image")?;
ocr.set_page_seg_mode(PageSegMode::PsmSingleBlock);
let text = ocr.get_text().context("Recognition failed")?;
ensure!(!text.trim().is_empty(), "No text recognized");
print!("{text}");
Ok(())
}
None permite que Tesseract localice el directorio de modelos instalado;
Some("eng") selecciona explícitamente el inglés. La
API de los bindings carga la imagen con
set_image y devuelve el texto reconocido en UTF-8 mediante
get_text. Los errores se propagan fuera de main en lugar
de imprimirse y tratarse como un resultado exitoso.
Ejecútalo con texto conocido
Desde el directorio que contiene rust-ocr, pega este bloque. Su subshell mantiene
sin cambios tu directorio de trabajo y las opciones del shell, incluso cuando falla un paso:
(
cd rust-ocr &&
cargo build &&
magick -size 900x180 xc:white -font Liberation-Sans -pointsize 64 \
-fill black -gravity center -annotate +0+0 'INVOICE 12345' PNG24:input.png &&
cargo run --quiet --locked -- input.png
)
Las opciones de anotación de ImageMagick crean un PNG opaco de 8 bits con texto legible. En el entorno probado, stdout contiene:
INVOICE 12345
Este comando reemplaza rust-ocr/input.png cuando se vuelve a ejecutar correctamente.
Reserva ese nombre de archivo para la muestra, no para una imagen que quieras conservar. Si la
compilación falla, la cadena && no genera la imagen ni ejecuta un binario
anterior. Tesseract también puede imprimir diagnósticos en stderr.
Manejar distintos formatos de imagen
Para probar la ruta de procesamiento en escala de grises con la misma muestra:
(cd rust-ocr && cargo run --quiet --locked -- input.png --grayscale)
Produce el mismo texto reconocido. Esta ruta decodifica PNG o JPEG mediante
image, convierte los píxeles a escala de grises y escribe un PNG en su propio
directorio temporal sin modificar la entrada. Rechaza deliberadamente las imágenes con canal alfa y
las de 16 bits en lugar de descartar la transparencia o cambiar los rangos de las muestras sin
avisar. Usa imágenes opacas de 8 bits con la orientación correcta para este tutorial; otros formatos
y la corrección automática de la orientación quedan fuera de su alcance.
El objeto TempDir se mantiene activo hasta que termina el reconocimiento. Su
destructor intenta realizar la limpieza tanto en caso de éxito como al devolver errores ordinarios.
Como se documenta en el código fuente de tempfile, una terminación
forzada puede dejar archivos sin eliminar y los errores de limpieza del destructor no se notifican.
Esto no constituye una garantía de borrado seguro.
Configuración avanzada de OCR
El programa selecciona PsmSingleBlock, el modo 6 de Tesseract, porque la muestra es un
solo bloque de texto. No solicita detección de orientación ni del sistema de escritura, por lo que
esta ruta necesita los datos de eng, pero no los de
osd. Elige la segmentación según el diseño real en lugar de asumir que un
modo sirve para todas las imágenes; la guía de calidad de Tesseract
explica las alternativas.
La conversión a escala de grises es una opción para comparar, no una promesa de mayor precisión. Tesseract ya realiza preprocesamiento internamente. Las letras borrosas, la inclinación y el bajo contraste siguen requiriendo una entrada de mejor calidad o un procesamiento específico para la tarea. Una lista de caracteres permitidos también excluiría signos de puntuación legítimos, por lo que el ejemplo no aplica ninguna.
Buenas prácticas de OCR en Rust
Interpreta una salida exitosa como «se reconoció algo de texto», no como «el texto es correcto». Compara los valores importantes con la imagen antes de usarlos. Una entrada en blanco es un error en esta CLI, aunque un resultado vacío puede ser un comportamiento válido para un motor de OCR. Pruébalo explícitamente:
(
cd rust-ocr &&
magick -size 900x180 xc:white PNG24:blank.png &&
cargo run --quiet --locked -- blank.png
)
Este bloque reemplaza la muestra blank.png y termina con un estado distinto de
cero y el mensaje No text recognized. Otros fallos tienen
soluciones diferentes:
- Si falta el nombre del archivo o hay un argumento adicional, se muestra el mensaje de uso. Coloca
--grayscaledespués de la imagen. - Si la imagen no existe, se muestra Could not open input image.
- Si falla la decodificación, se muestra Could not decode input image.
- Si faltan los datos de inglés, se muestra Could not initialize English OCR; check eng.traineddata.
La decodificación no es una comprobación de integridad del archivo. Un JPEG dañado puede aun así decodificarse, especialmente en la ruta de escala de grises, y producir texto incompleto en lugar de un error de decodificación. Contrasta el resultado con el original; esta CLI no certifica que una imagen esté intacta.
Si el modelo de inglés está instalado en una ubicación personalizada, TESSDATA_PREFIX
debe indicar el directorio existente que contiene eng.traineddata, no el archivo en sí.
Así es como Tesseract 5.5.3 lo localiza.
Comprueba el directorio que indica tesseract --list-langs; una variable de entorno que
sobrescriba la configuración con un valor desactualizado puede dirigir el programa de Rust a un
directorio de modelos diferente.
Manejar varios idiomas
Esta CLI selecciona deliberadamente solo el inglés. Instalar modelos adicionales no cambia esa selección. Tesseract admite identificadores de idioma combinados, pero una aplicación multilingüe también necesita esos modelos e imágenes representativas para evaluar sus resultados. Consulta la documentación de configuración de idiomas al ampliarla; la muestra de prueba en inglés no verifica el reconocimiento multilingüe.
Conclusión
Para usar tu propia imagen, reemplaza input.png en el comando de ejecución por su
nombre de archivo y escribe entre comillas las rutas que contengan espacios. Conserva la imagen
original para poder comparar el texto reconocido con ella. Si tu siguiente paso es un flujo de
trabajo de documentos alojado en lugar de una CLI local, consulta el
servicio de procesamiento de documentos de Transloadit.
