OdoKeep ya está en el App Store. Descárgalo gratis.

Todos los artículos

Reconocimiento de recibos

OCR de recibos sin conexión en Expo: el trabajo empieza después de reconocer el texto

Interpretar recibos en el dispositivo con Expo, Vision y ML Kit: candidatos, comprobaciones aritméticas, unidades explícitas y revisión de datos.

Actualizado el

En este artículo

En un recibo de combustible, 1,899 puede ser el precio unitario. 95, el grado del producto. 04, el número del surtidor. El motor OCR puede leer los tres perfectamente y aun así dejarme un registro equivocado.

Añadí el escaneo de recibos a OdoKeep, mi diario de vehículos en React Native y Expo. El reconocimiento se ejecuta en el teléfono: Vision de Apple en iOS y reconocedores de Google ML Kit incluidos en la aplicación Android. Esta función no sube la fotografía ni el texto reconocido.

El módulo nativo se ocupa de obtener y preparar la imagen, reconocer el texto y gestionar la fotografía temporal. La interpretación está en TypeScript, dentro de lib/receipt-scan. Esa separación permite probar el parser sin cámara ni un teléfono concreto.

Conservar la evidencia junto al texto

El resultado nativo contiene más que una cadena: líneas, cuadros de origen, información de confianza disponible y, cuando el motor las ofrece, transcripciones alternativas. El parser usa las posiciones para relacionar un campo con su lugar en la fotografía.

El proceso sigue aproximadamente estos pasos:

Fotografía
  -> líneas y cuadros del OCR nativo
  -> tokens numéricos con unidades y posiciones
  -> candidatos ordenados por campo
  -> comprobación aritmética
  -> borrador revisado
  -> formulario habitual de combustible o carga

No se guarda nada entre el reconocimiento y el formulario habitual. El escaneo propone valores que el usuario puede corregir y guardar por la misma ruta que una entrada manual.

Ordenar las lecturas antes de elegir un número

La normalización numérica ya plantea dificultades. Un recibo puede usar coma o punto decimal, espacios de agrupación, apóstrofos suizos o distintos sistemas de dígitos. La impresión térmica también puede perder un separador o convertir visualmente un dígito en una letra.

El tokenizador conserva varias lecturas plausibles. No tiene que resolver 1,899 antes de conocer su etiqueta, unidad y contexto. Una reparación posible tiene menor prioridad que una lectura normal, y conserva su posición de origen.

La clasificación empieza por lo impreso. Una unidad explícita respalda la cantidad. Una expresión como EUR/L respalda el precio unitario. La etiqueta de total respalda el importe de referencia.

Fechas, porcentajes, distancias y etiquetas administrativas quedan fuera de los campos de importes. Un número sin etiqueta no se convierte en cantidad porque facilite la multiplicación. La búsqueda conserva como máximo veinte candidatos por campo para limitar las combinaciones.

La multiplicación aporta una comprobación

La aritmética parece sencilla:

quantity * unitPrice = printedTotal

Es útil, pero no equivale a afirmar que el recibo es correcto. Una coincidencia respalda una lectura candidata. No demuestra que la imagen se haya leído bien ni que los tres campos describan la transacción que creo.

El parser compara el producto con una tolerancia de media unidad monetaria mínima, más un margen de coma flotante. Para una moneda con dos decimales, la base es medio céntimo. No usa un porcentaje de la factura: eso permitiría errores OCR mayores en recibos más caros.

Este caso aparece en las pruebas:

31.93 L x 1.899 EUR/L
TOTAL 60.84 EUR

El producto es aproximadamente 60,635. Una tolerancia porcentual amplia podría aceptar 60,84. La comparación basada en la moneda rechaza esa coincidencia.

Otra prueba contiene cantidad y precio legibles, un subtotal que coincide con su producto y un importe pagado inferior:

40 L x 1.80 EUR/L
Subtotal 72 EUR
Discount 4 EUR
Total 68 EUR

El parser mantiene 68 como total impreso de referencia. No convierte 72 en el total final solo porque encaje en la multiplicación. Cantidad y precio siguen siendo útiles aunque el pago incluya un descuento.

OdoKeep calcula el coste guardado multiplicando cantidad y precio unitario y restando, si procede, un descuento revisado. El total impreso no se copia al coste guardado. Tampoco permite completar por división una cantidad o un precio que faltan.

Si solo se reconoce el total, el usuario debe introducir los importes. Si se reconoce la cantidad pero no el precio, este queda vacío. Un resultado parcial puede seguir siendo parcial.

Cuando no se supera la comprobación aritmética, la aplicación puede pedir otra lectura con mayor contraste. Compara ambas por la comprobación aritmética y después por los campos de importes recuperados. En un empate conserva la original. Fecha, hora, estación y otros campos ajenos a los importes pueden completar huecos desde la otra lectura, porque ambas proceden de la misma fotografía.

La lectura mejorada también se revisa. Más evidencia cambia el orden de los candidatos, no elimina la posibilidad de corregirlos.

Elegir unidades y monedas de forma explícita

Las unidades producen errores especialmente creíbles. Litros, galones estadounidenses, galones imperiales, kilogramos, metros cúbicos y kilovatios hora tienen sentido en el dominio de combustible y carga. No son intercambiables.

Un recibo que solo dice gal no identifica el tipo de galón. El importador pregunta. Al convertir unidades de volumen compatibles, multiplica la cantidad por el factor y divide el precio por el mismo factor:

convertedQuantity = printedQuantity * factor
convertedUnitPrice = printedUnitPrice / factor

Así conserva el producto antes del redondeo. Los valores convertidos se redondean a un máximo de tres decimales, por lo que el coste recalculado en el formulario puede diferir ligeramente del total impreso. Mantener la unidad original evita ese redondeo.

Los conflictos de dimensión física requieren más cautela. Una cantidad en litros y un precio por kilovatio hora no pueden combinarse. El parser conserva cantidad y unidad, y deja vacío el precio. No reinterpreta una dimensión como otra.

La moneda también se elige explícitamente. Si el recibo indica otra moneda, el usuario puede conservarla. Si prefiere la moneda configurada, el importador conserva la cantidad y borra el precio. No presupone un tipo de cambio. Mostrar después un coste en moneda extranjera corresponde a la capa de conversión de la aplicación, que considera la fecha del registro.

El contexto regional procede de la región del dispositivo, independientemente del idioma de la interfaz. Usar la interfaz en inglés en Portugal no convierte todos los recibos en estadounidenses. Los códigos de moneda y las unidades explícitas pesan más que los valores regionales predeterminados.

Reconocimiento y vida de la fotografía

En iOS, Vision usa reconocimiento preciso con la corrección lingüística desactivada. Las cantidades y las abreviaturas de productos no se benefician de todas las suposiciones que ayudan a corregir prosa. Las sugerencias de idioma se comprueban contra la petición Vision configurada antes de enviarse.

En Android se incluyen los reconocedores latino, chino, devanagari, japonés y coreano descritos en la documentación de ML Kit. Incluirlos aumenta el tamaño del binario y permite usarlos sin conexión. La adquisición mediante el escáner de documentos tiene requisitos independientes de Google Play services y puede necesitar una descarga inicial; el selector de imágenes y el OCR incluido ofrecen una alternativa. Esto no implica compatibilidad con todos los sistemas de escritura.

La orientación se normaliza en los píxeles y los cuadros OCR deben coincidir con la fotografía mostrada. Ambas implementaciones nativas pueden probar rotaciones si la primera lectura es escasa, pero unos pocos fragmentos extra no bastan para justificar un giro.

Las copias temporales pertenecen a una sesión de escaneo. Una sesión cancelada no puede escribir una fotografía tardía después de la limpieza, ni una revisión borrar la imagen de otra. El borrador pertenece al formulario que lo pidió y solo puede consumirse una vez. Volver al formulario no debe sobrescribir las correcciones con el escaneo antiguo.

Probar la interpretación por separado

Las pruebas incluyen dos transcripciones de recibos reales y casos sintéticos de formatos regionales, unidades ambiguas, descuentos, fechas incorrectas, lecturas alternativas y transferencias parciales al formulario. Comprueban qué hace el parser con el texto OCR. No miden una tasa de reconocimiento sobre un corpus de imágenes. Poca luz, impresión deteriorada y cobertura de escrituras siguen necesitando evaluación en dispositivos.

Esta separación ayuda a investigar. Si los caracteres son incorrectos, reviso adquisición y reconocimiento. Si son correctos pero el registro no, reviso la evidencia del parser y la transferencia al formulario. Un JSON aparentemente completo ocultaría la diferencia.

El escaneo de recibos forma parte de OdoKeep. Si llevas registros de combustible o carga, puedes probarlo en la aplicación iOS: fotografiar, revisar lo recuperado y terminar en el formulario habitual.