[Universal App] Usando Microsoft OCR

OCR LogoIntroducción

Existen aplicaciones sumamente útiles en la Tienda. Algunas de ellas impactantes. Personalmente me impresionaron alguna aplicación que directamente de una foto o video podía capturar el texto y traducirnoslo directamente a nuestron idioma, o aplicaciones que de una captura nos obtienen el texto de la misma permitiendonos guardarlo o incluso compartirlo. Son aplicaciones muy útiles en categorías diferentes que tienen un denominador común, son capaces de obtener el texto de una imagen.

¿Cómo lo hacen?

OCR

OCR son las siglas de Optical character recognition, o en español reconocimiento óptico de carácteres. Es un proceso dirigido a la digitalización de textos, los cuales identifican automáticamente a partir de una imagen símbolos o caracteres. Las posibilidades obtenidas son muchas y variadas:

  • Obtener el texto de una imagen.
  • Traducir el texto de una imagen.
  • Verificar si una palabra o valor se encuentra en el texto de la imagen.
  • Etc.

Librería Microsoft OCR para Windows Runtime

Recientemente Microsoft ha liberado en NuGet la librería Microsoft OCR para Windows Runtime. Esta librería nos permite en aplicaciones universales digitalizar textos desde imagenes. La librería funciona completamente en local tratando imágenes desde la cámara, local o desde la red soportando hasta 21 idiomas.

Utilizando la librería

Comenzamos creando un nuevo proyecto:

Nueva Aplicación Universal

Nueva Aplicación Universal

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo obtendrá una imagen local gracias al uso del FileOpenPicker para sintetizar la imagen obtenida con la librería Microsoft OCR.

La interfaz de usuario

Comenzamos creando la interfaz de nuestro ejemplo. En la carpeta Views tenekos disponible la vista MainView. Añadimos:

<ScrollViewer>
     <StackPanel>
          <Image Source="{Binding Bitmap}" Stretch="Uniform" MaxHeight="300" Margin="10" />
          <TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="24"/>
     </StackPanel>
</ScrollViewer>

Hemos añadido dos controles básicos para nuestro ejemplo. Por un lado la imagen bindeada a una propiedad Bitmap que mostrará la imagen obtenida con el FileOpenPicker, por otro lado añadimos un sencillo TextBlock que mostrará el resultado de obtener el texto correspondiente a la imagen cargadada.

En la vista principal trendremos además dos botones. Un botón para obtener una imagen local, y otro para utilizar la imagen obtenida para extraer el texto:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="get picture" Icon="Pictures" Command="{Binding FileOpenPickerCommand}" />
          <AppBarButton Label="get text" Icon="Forward" Command="{Binding GetTextCommand}" />
     </CommandBar>
</Page.BottomAppBar>

Los añadimos en la CommandBar de la página. En la viewmodel de la vista tendremos disponibles tanto la imagen como el texto bindeados asi como los dos comandos necesarios para ejecutar las acciones requeridas:

//Variables
private WriteableBitmap _bitmap;
private string _text;

//Commands
private ICommand _fileOpenPickerCommand;
private ICommand _getTextCommand;

public WriteableBitmap Bitmap
{
     get { return _bitmap; }
     set
     {
          _bitmap = value;
          RaisePropertyChanged("Bitmap");
     }
}

public string Text
{
     get { return _text; }
     set
     {
          _text = value;
          RaisePropertyChanged("Text");
     }
        
public ICommand FileOpenPickerCommand
{
     get { return _fileOpenPickerCommand = _fileOpenPickerCommand ?? new DelegateCommandAsync(FileOpenPickerCommandDelegate); }
}

public ICommand GetTextCommand
{
     get { return _getTextCommand = _getTextCommand ?? new DelegateCommandAsync(GetTextCommandDelegate); }
}
public async Task FileOpenPickerCommandDelegate()
{

}

public async Task GetTextCommandDelegate()
{

}

Hasta ahora sencillo, ¿verdad?.

Añadiendo la librería

Continuamos añadiendo la librería.

La librería la tenemos disponible en NuGet por lo que podemos instalarlo usando Nuget Package Manager. En las referencias del proyecto hacemos clic derecho y seleccionamos la opción Manage NuGet Packages …

Añadir paquete NuGet

Añadir paquete NuGet

En la ventana modal que nos aparece, en la parte superior derecha donde podemos realizar una búsqueda, buscamos por “Microsoft OCR”:

Microsoft OCR Library for Windows Runtime

Microsoft OCR Library for Windows Runtime

Seleccionamos el elemento “Microsoft OCR Library for Windows Runtime” y pulsamos el botón Install. Tras un breve periodo donde se procede a descargar e incluir las librerías en las referencias del proyecto, tendremos lo necesario para comenzar a trabajar con OCR.

FileOpenPicker

Representa un elemento de interfaz que permite al usuario elegir y abrir ficheros. Es una API asíncrona disponible dentro del namespace Windows.Storage.Pickers. La API esta disponible tanto en Windows como en Windows Phone pero con diferencias entre ambas plataformas.

En aplicaciones Windows Store se realiza una llamada asíncrona a la API esperando el resultado directamente. Mientras que en Windows Phone el proceso es algo más complejo. En Windows Phone podemos contar con dispositivos de baja memoria. Por ese motivo, en se utilizan métodos “AndContinue”. Esto quiere decir que al realizar la llamada a la API la aplicación es suspendida e incluso podría llegar a ser terminada. Continuará el proceso al reactivar la aplicación.

Debido a las diferencias entre ambas plataformas, en nuestro ejemplo, vamos a crear una interfaz comun de un servicio FileOpenPicker en el proyecto Shared:

public interface IFilePickerService
{
     void Initialise();
     Task<StorageFile> ShowPickerAsync(FileOpenPicker picker);
}

Realizaremos la implementación adecuada de cada plataforma en el proyecto correspondiente.

Windows Store

Conenzamos por la implementacion en la aplicación Windows Store que es el caso más sencillo. El FileOpenPicker  es invocado llamando al método PickSingleFileAsync que devolverá (asíncronamente) un objeto de tipo StorageFile que representa el fichero seleccionado por el usuario:

public async Task<StorageFile> ShowPickerAsync(FileOpenPicker picker)
{
     StorageFile file = await picker.PickSingleFileAsync();
     return (file);
}

Sencillo y brutalmente potente.

Windows Phone

Continuamos con Windows Phone. En este caso, el FileOpenPicker es invocado mediante el evento PickSingleFileAndContinue. La aplicación pasa a estar suspendida.

public async Task<StorageFile> ShowPickerAsync(FileOpenPicker picker)
{
     _completionSource = new TaskCompletionSource<StorageFile>();

     picker.PickSingleFileAndContinue();

     StorageFile file = await _completionSource.Task;

     return (file);
}

Una vez resumida se obtiene el objeto de tipo StorageFile vía método Activated de la aplicación. La clase FileOpenPickerContinuationEventArgs nos proporciona información acerca del evento Activated cuando se produce por una operación de FileOpenPicker.

private void OnApplicationActivated(CoreApplicationView sender,
            IActivatedEventArgs args)
{
     var continueArgs =
                args as FileOpenPickerContinuationEventArgs;

     if (continueArgs != null)
     {
          _selectedFile = continueArgs.Files[0];

         if (_completionSource != null)
         {
              _completionSource.SetResult(_selectedFile);
              _completionSource = null;
         }
     }
}

Con esto ya tenemos la capacidad en nuestra aplicación, tanto en Windows como en Windows Phone, de permitir seleccionar una imagen al usuario.

Microsoft OCR

Llegamos al eje central del ejemplo. La librería Microsoft OCR nos permite extraer el texto de la imagen obtenida previamente con el FileOpenPicker. La librería Microsoft OCR para Windows Runtime nos permite extraer el texto de una imagen además de poder encontrar patrones como correos o números de teléfono, ideal para pooder ejecutar acciones.

Vamos a crear un servicio (al igual que hicimos con el FileOpenPicker) para inyectarlo posteriormente con inyección de dependencias. La interfaz del servicio es la siguiente:

public interface IOcrService
{
     Task<string> GetText(WriteableBitmap bitmap);
}

Muy muy simple. Tendremos un único método asíncrono que obtendra la imagen y devolverá el texto. Trabajamos con la librería OCR en el namespace WindowsPreview.Media.Ocr. La implementación del servicio para ambas plataformas es la siguiente:

public class OcrService : IOcrService
{
     private OcrEngine _ocrEngine;

     public async Task<string> GetText(WriteableBitmap bitmap)
     {
         string result = string.Empty;

         if (_ocrEngine == null)
             _ocrEngine = new OcrEngine(OcrLanguage.English);

         // Sintetizamos la imagen para extraer el texto (RecognizeAsync)
         var ocrResult = await _ocrEngine.RecognizeAsync((uint)bitmap.PixelHeight, (uint)bitmap.PixelWidth, bitmap.PixelBuffer.ToArray());

         // Si el resultado no contiene líneas no hacemos nada
         if (ocrResult.Lines != null)
             // Si hay líneas, las vamos añadiendo al resultado final
             result = ocrResult.Lines.Aggregate(result, (current1, line) => line.Words.Aggregate(current1, (current, word) => current + word.Text + " "));

         return result;
     }
}

Analicemos con calma el código anterior. Lo primero que hacemos es crear una instancia de OcrEngine. Esta clase es la encargada de proporcionar la capacidad de realizar OCR a nuestra aplicación. Al instanciar el OcrEngine podemos hacerlo sin parámetros o indicando el idioma a utilizar para detectar el texto en la imagen. Hay 21 lenguajes soportados. Dependiendo de la calidad de detección, el rendimiento y otros parámetros podemos establecer la siguiente divisón en grupos:

  • Excelente: Checo, Danés, Holandés, Inglés, Finlandés, Francés, Alemán, Húngaro, Italiano, Noruego, Polaco, Portugués, Español y Sueco.
  • Muy bueno: Chino, Griego, Japones, Ruso y Turco.
  • Bueno: Chino tradicional y Creano.

NOTA: Al incluir la librería Microsoft OCR además de la propia librería se nos añade un archivo de recursos OCR. Estos recursos son utilizados para un correcto reconocimiento del texto correspondiente en el idioma deseado. El archivo de recursos OCR añadido por defecto es en ingles. Si deseamos utilizar otros idiomas a los recursos OCR debemos utilizar la herramienta OCR Resources Generator tool. En el siguiente apartado de este mismo artículo utilizaremos la herramienta.

Continuamos analizando el código de nuestro servicio OCR. Tras instanciar el OcrEngine, utilizamos el método RecognizeAsync. A este método le pasamos la imagen junto con sus dimensiones para extraer el texto. Se devuelve un objeto de tipo OcrResult. Este objeto contiene el texto extraido asi como sus posiciones y tamaños.

El objeto OcrResult cuenta con una colección de objetos de tipo OcrLine que son cada una de las líneas de texto extraidas que a su vez contienen una colección de objetos de tipo OcrWord, que contienen la información de cada una de las palabras extraidas.

Iremos recorriendo las líneas añadiendo cada palabra en una cadena que sera el resultado devuelto por nuestro servicio.

El uso en nuestra viewmodel sera simple:

Text = await _ocrService.GetText(_bitmap);

Donde _bitmap representa la imagen local obtenida. Guardamos en Text el resultado obtenido. En nuestro ejemplo, para simplificar solo obtenemos el texto y nuestro objetivo finaliza aqui. Sin embargo, desde este punto podéis utilidad OCR para una enorme cantidad de situaciones como traducciones, extraer ciertos campos, verificar si la iamgen contiene o no cierta palabra, compartir el contenido, etc.

Continuamos el ejemplo. La librería OCR tiene límites. La dimensión de la imagen utilizada no puede ser inferior de 40 x 40px o superior a 2600 x 2600px. Debemos considerar esto en nuestras aplicaciones. Definimos los límites en constantes:

public const int MinWidth = 40;
public const int MaxWidth = 2600;
public const int MinHeight = 40;
public const int MaxHeight = 2600;

De modo que antes de utilizar nuestro servicio OCR para extraer el texto realicemos la verificación correspondiente:

public async Task GetTextCommandDelegate()
{
     // Verificamos si la imagen cumple con las características necesarias para ser procesada.
     // Las dimensiones soportadas son desde 40 a 2600 pixels.
     if (_bitmap.PixelHeight < MinHeight ||
         _bitmap.PixelHeight > MaxHeight ||
         _bitmap.PixelWidth < MinWidth ||
         _bitmap.PixelWidth > MaxHeight)   
         await _dialogService.ShowAsync("Imagen inválida", "La imagen no esta dentro del tamaño soportado.");
     else    
         Text = await _ocrService.GetText(_bitmap);
}

Todo listo!. Si ejecutamos el ejemplo y probamos:

I, Robot de Isaac Asimov sintetizado

I, Robot de Isaac Asimov sintetizado

Como generar recursos OCR

Hasta este punto tenemos lo necesario para saber como utilizar la librería Microsoft OCR y extraer texto de una imagen. Sin embargo, en el proceso de extracción como ya mencionamos previamente se utilizan unos recursos (MsOcrRes.orp) localizados por idioma que facilitan la calidad del proceso.

Hasta ahora hemos utilizado el ingles (idioma por defecto), pero… ¿que ocurre si intentamos extraer texto en japones?

Bueno, para lograr ese objetivo con l mayor calidad posible tenemos a nuestra disposición la herramienta OCR Resources Generator tool. Si nos paramos a anlizar la estructura de nuestro proyecto, tendremos algo similar a:

<solution_name>
   <project_name>
      OcrResources
         MsOcrRes.orp
   packages
      Microsoft.Windows.Ocr.1.0.0
         build
         lib
         content
            OcrResources
               MsOcrRes.orp
         OcrResourcesGenerator
            OcrResourcesGenerator.exe

Tenemos disponible la herramienta en <solution_name>\packages\Microsoft.Windows.Ocr.1.0.0\OcrResourcesGenerator\OcrResourcesGenerator.exe. Tras ejecutarla veremos una pantalla como la siguiente:

Eligiendo idiomas

Eligiendo idiomas

Nos permite elegir entre los 21 idiomas soportados.Podemos añadir o quitar idiomas y una vez esta a nuestro antojo pulsaremos el botón Generate Resources.

Guardando el recurso generado

Guardando el recurso generado

Nos aparecerá una ventana que nos permite guardar el nuevo archivo de recursos OCR. Una vez guardado bastara con reemplazar el archivo añadido por defecto en \OcrResources\MsOcrRes.orp.

Sencillo, ¿verdad?

Podéis descargar el ejemplo realizado a continuación:

Más información

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s