En iOS 11 Apple integró una librería denominada Vision. Esta librería permite utiliza algoritmos siuales para realizar una serie de tareas sobre imágenes y vídeo (detección de texto, de códigos de barras, etc.). Ahora, con iOS 13, Apple ha publicado una nueva librería, VisionKit que permite usar el escáner de documentos del propio sistema (el mismo que usa la aplicación Notas). Ahora vamos a ver cómo puedes desarrollar tu propio OCR en iOS 13 con VisionKit

Inicio del proyecto

Para poder comprobar cómo podemos escanear un documento y reconocer su contenido, creamos un proyecto en Xcode 11 (recuerda que VisionKit solo funciona en iOS 13+). Este proyecto lo puedes encontrar completo en GitHub).

Como vamos a utilizar la cámara del dispositivo para escanear los documentos, el sistema operativo nos mostrará un mensaje en el cual nos pide permiso para utilizar dicha cámara. Si no queremos que se produzca un error y se cierre la aplicación, deberemos notificar a la aplicación que necesitaremos la cámara.

Para ello, en el fichero Info.plist añadimos la clave ‘Privacy – Camera Usage Description‘, junto con un texto que será el que se muestre al usuario al pedirle permiso (por ejemplo: «Para poder escanear documentos debes permitir el uso de la cámara.»).

Info.plist donde se ha añadido la clave de petición de uso de la cámara.
Petición de permiso.

Si se deniega el permiso, cuando queramos escanear, nos aparecerá el siguiente mensaje:

Diseño de la interfaz

Este proyecto constará básicamente de un componente UIImageView, en el que mostraremos el documento escaneado con el texto reconocido, un componte UITextView para mostrar el texto que ha reconocido el escáner, y un componente UIButton para activar el escaneo de documentos. En este proyecto, todo esto lo hare mediante código, sin utilizar storyboards o ficheros .xib.

Diseño de la interfaz del proyecto

En primer lugar creamos el componente ScanButton:

Programación de la interfaz

Luego el componente ScanImageView:

Y, finalmente, el componente OcrTextView:

Ahora los llamamos desde la controllador ViewControlller y los posicionamos en la pantalla:

Presentación del controllador de escaneo (VNDocumentCameraViewController)

Para poder presentar el controlador quenos permitirá escanear el documento hemos de crear y presentar una instancia de la clase VNDocumentCameraViewController.

Al final del método configure añadimos el siguiente código, que nos permite llamar al método scanDocument():

Depués del método configure() creamos el método scanDocument():

Como se puede observar, se ha añadido @objc delante de la función, porque aunque estamos programando en swift, #selector es un método de objective-c.

Además, la clase VNDocumentCameraViewController presenta el protocolo VNDocumentCameraViewControllerDelegate (al que hemos llamado en scanVC.delegate = self), por lo que podremos implementar sus métodos. Esto lo hacemos en una extension de la clase ViewController para tener más organizado el código:

El primer método, documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan), es llamado cuando hemos escaneado una o más páginas y las hemos guardado (Keep Scan primero y luego Save).

El objeto scan (VNDocumentCameraScan) contiene tres parámetros:

  • pageCount: es el número (Int) de páginas escaneadas.
  • imageOfPage(at index: Int): es la imagen (UIImage) de la página en el índice indicado.
  • title: es el título (String) del documento escaneado. Una vez comprobado que se han escaneado uno o más documentos, antes de quitar el controllador, pasamos la imagen escaneada al componente scanImageView.

El segundo método, documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error), es llamado cuando se produce un error al escanear el documento, por lo que es en este punto donde deberemos realizar alguna acción de gestión del error (por ejemplo, en el caso de que el error se deba a que el usuario no ha dado permiso para usar la cámara, podemos mostrarle un mensaje de alerta pidiéndole que active el permiso).

El tercer método, documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController), es llamado cuando se hace clic en el botón Cancel del controlador VNDocumentCameraViewController. Aquí solo quitaremos el controlador.

Reconocimiento de texto

Ahora, para poder reconocer y extraer el texto de los documentos que hemos escaneado, utilizaremos la librería Vision de Apple, ya integró en iOS 11. En concreto, utilizaremos la clase VNRecognizeTextRequest. Esta clase, tal como indica la documentación, busca y reconoce el texto en una image. Para este proceso necesitaremos una petición (instancia de la clase VNRecognizeTextRequest), en la que podremos definir los parámetros de reconocimiento del texto:

  • customWords. Son un conjunto de palabras definidas por nosotros para complementar las del diccionario y que se utilizaran durante la etapa de reconocimiento (por ejemplo, nombres, marcas, etc.).
  • minimumTextHeight. Es la altura mínima del texto (respecto a la de la imagen) a partir de la cual se producirá el reconocimiento del texto. Tal como indica Apple en su documentación:

Increasing the size reduces memory consumption and expedites recognition with the tradeoff of ignoring text smaller than the minimum height. The default value is 1/32, or 0.03125.

  • recognitionLevel. Permite priorizar la velocidad o la precisión en el reconocimiento del texto. Puede ser .fast (prioriza la velocidad sobre la precisión) o .acurate (se prioriza la precisión, pero es más lento).
  • recognitionLanguages. Lista de lenguajes (por orden de prioridad) que se utilizarán a la hora de reconocer el texto. Por ejemplo, [«es-ES», «en-US»].
  • usesLanguageCorrection. Indica si queremos (true) o no (false) que durante el proceso de reconocimiento del texto se aplique una corrección en el lenguaje. true aumenta la precisión pero reduce el rendimiento del proceso, mientras que false reduce la precisión pero aumenta el rendimiento

En este proyecto aplicaremos, a modo de ejemplo, algunos de estos parámetros:

En este punto, creamos una función configureOCR(), que será la que contenga la funcionalidad de analizar, reconocer y extraer el texto de la imagen:

Esta función la llamaremos en el viewDidLoad() después del método configure(). Lo que hacemos en esta función es crear una instancia de VNRecognizeTextRequest que solo contiene un argumento, completionHandler, que se llama cada vez que se detecta texto en una imagen.

En este punto el proceso que se produce es:

  • Primero comprobamos que request.results contiene una lista de observaciones (del tipo VNRecognizedTextObservation), que corresponden a las líneas, oraciones… que la librería Vision ha detectado.
  • Después, iteramos sobre esta lista de observaciones. Cada una de estas observaciones está formada por una serie de posibles candidatos de lo que puede ser el texto reconocido, cada uno de los cuales con un determinado nivel de confianza. Escogemos el primer candidato, y lo añadimos a una cadena de texto.
  • Finalmente mostramos en el elemento OcrTextView que hemos creado el principio el texto obtenido (recuerda que hay que hacerlo en el hilo principal, por eso utilizamos Dispatch.main.async).

Procesado de la imagen

Finalmente, solo nos queda procesar la imagen capturada por el escáner. Para ellos creamos una función que tomará un parámetro de tipo UIImage (la imagen capturada), y creará una instancia de tipo VNImageRequestHandler, que es a la que pasaremos la intancia ocrRequest que hemos creado al principio:

Tal como indica la documentación, para instanciar este tipo necesitamos utilizar CGImage, y no UIImage (ya que trabaja con Core Graphics), por lo que obtenemos ese parámetro de la imagen que hemos pasado.

También podemos pasar una lista de opciones del tipo VNImageOption (que describen propiedades específicas de la imagen o cómo debe ser tratada), aunque en este caso no pasaremos ninguna.

Finalmente, aplicamos la petición de reconocimiento de texto (ocrRequest). Este método, processImage(_ image: UIImage), lo llamaremos al final del método documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) y justo antes de quitar el controlador con controller.dismiss(animated: true).

Prueba del escáner

Ahora podemos probar la aplicación. Para ello la encendemos y capturamos una imagen.

Como se puede observar, reconoce perfectamente el texto de la imagen.

Conclusión

Como hemos podido ver, gracias a las librerías Vision y VisionKit podemos construir de forma sencilla nuestro propio escáner de documentos en nuestro móvil. Recuerda que puedes descargar el proyecto completo en GitHub.


0 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Sígueme en Feedly
shares