OCR библиотека для WinRT приложений. Часть 1.

В данной серии постов мы рассмотрим функционал относительно новой библиотеки по распознаванию текста для WinRT приложений.

Первая попытка — захват и обработка кадров из видео

Моя первая мысль  — это попытаться сделать одно из тех “живых” приложений, в которых вы держите телефон и подносите к нему нечто вроде визитной карточки, и в результате наблюдаете элементы, обнаруженные посредством OCR. Моя идея состояла в том, что в приложение будут заложены некоторые регулярные выражения, которые обычно встречаются в английских визитных карточках, и отрисовка маленьких цветных квадратиков вокруг электронной почты, номера телефона и т.п.

С этой целью, я создал пустой Windows Phone 8.1 проект, нашел «Microsoft OCR» на NuGet’ах, добавил пакет, изменил конфигурацию CPU под x86, а затем занялся исследованием API. Это не заняло много времени, т.к. это аккуратная библиотека с не слишком большими участками кода. В итоге я добавил классы, которые отображены на следующей диаграмме:

image

Я думаю, что первым шагом будет получение некоторых растровых изображений и передача их в OCR.

Для этого я планировал использовать регулярное MediaCapture API вместе с CaptureElement который может отображать предварительный просмотр из MediaCapture но, к сожалению для меня, я не могу работать с Windows Phone 8.1 прямо сейчас.

Поэтому, на данный момент, я отступил от своего плана и загорелся менее амбициозной идеей.

Попытка вторая. Получение визиток с  fickR.

Вместо захвата кадров в режиме реального времени будем просто использовать поиск изображений Flickr, для получения некоторых визиток.

Для этого было проделано несколько этапов…

 

Код для доступа на flickR

Набросаем небольшой класс, который поможет нам предоставить url для вызова поиска.

namespace App253.FlickrSearch
{
  using System.Collections.Generic;  
  using System.Text; 

  internal class FlickrSearchUrl
  { 
    static string apiKey = "SORRY, YOU NEED A KEY";
    static string serviceUri = "https://api.flickr.com/services/rest/?method=";
    static string baseUri = serviceUri + "flickr.photos.search&";
    public int ContentType { get; set; }
    public int PerPage { get; set; }
    public int Page { get; set; }
    public List<string> SearchTags { get; private set; }

    private FlickrSearchUrl()
    { }
 
    public FlickrSearchUrl(string searchTag, int pageNo = 0, int perPage = 5, int contentType = 1) : this()
    { 
      this.SearchTags = new List<string>() { searchTag };
      this.Page = pageNo;
      this.PerPage = perPage;
      this.ContentType = contentType;
    }
  
    public override string ToString()
    { 
       // flickR has pages numbered 1..N (I think :-)) whereas this class thinks
       // of 0..N-1.
       StringBuilder tagBuilder = new StringBuilder();
       for (int i = 0; i < this.SearchTags.Count; i++)
       {
         tagBuilder.AppendFormat("{0}{1}", i != 0 ? "," : string.Empty, this.SearchTags[i]);
       }

       return (string.Format(baseUri + "api_key={0}&" + "safe_search=1&" + "tags={1}&" + "page={2}&" + "per_page={3}&" + "content_type={4}", apiKey, tagBuilder.ToString(), this.Page + 1, this.PerPage, this.ContentType));
    }
  }
}

 и после еще один маленький класс для парсинга моделей ответа:

namespace App253.FlickrSearch
{
  using System.Xml.Linq;

  public class FlickrPhotoResult
  {
    public FlickrPhotoResult(XElement photo)
    {
      Id = (long)photo.Attribute("id");
      Secret = photo.Attribute("secret").Value;
      Farm = (int)photo.Attribute("farm");
      Server = (int)photo.Attribute("server");
      Title = photo.Attribute("title").Value;
    }

    public long Id { get; private set; }
    public string Secret { get; private set; }
    public int Farm { get; private set; }
    public string Title { get; private set; }
    public int Server { get; private set; }

    public string ImageUrl
    {
      get
      {
        return (string.Format("http://farm{0}.static.flickr.com/{1}/{2}_{3}_z.jpg", Farm, Server, Id, Secret));
      }
    }
  }
}

собственно сам класс ответа с сервера:

namespace App253.FlickrSearch
{
  using System.Collections.Generic;

  internal class FlickrSearchResult
  {
    public int Pages { get; set; }
    public List<FlickrPhotoResult> Photos { get; set; }
  }
}

и, наконец, я собрал небольшой класс-провайдер, который фактически принимает FlickrSearchUrl и выполняет HTTP запрос для получения данные и их дальнейшей сериализации:

namespace App253.FlickrSearch
{
  using System.IO;
  using System.Linq;
  using System.Net.Http;
  using System.Threading.Tasks;
  using System.Xml.Linq;

  internal static class FlickrSearcher
  {
    public static async Task<FlickrSearchResult> SearchAsync(FlickrSearchUrl searchUrl)
    {
      HttpClient client = new HttpClient();
      FlickrSearchResult result = new FlickrSearchResult();

      using (HttpResponseMessage response = await client.GetAsync(searchUrl.ToString()))
      {
        if (response.IsSuccessStatusCode)
        {
           using (Stream stream = await response.Content.ReadAsStreamAsync())
           {
             XElement xml = XElement.Load(stream);
             result.Photos = (from p in xml.DescendantsAndSelf("photo") select new FlickrPhotoResult(p)).ToList();
  
             result.Pages = (int)xml.DescendantsAndSelf("photos").First().Attribute("pages");
           }
         }
       }
  
       return (result);
     }
   }
}

Благодаря всем этим классам теперь я могу выполнять поиск по Flickr по определенному набора поисковых тегов и т.д.

Теперь мне необходимо отобразить эти фотографии …

 

Сделаем FlipView с подгрузкой…

Я хотел сделать своего рода бесконечный поиск по Flickr. Так как делал подобное раньше, решил реализовать ISupportIncrementalLoading. Для поддержки данного функционала мне надо было реализовать свой вспомогательный класс:

namespace App253
{
  using App253.FlickrSearch;
  using System;
  using System.Collections.ObjectModel;
  using System.Linq;
  using System.Threading.Tasks;
  using Windows.Foundation;
  using Windows.UI.Xaml.Data;

  internal class FlickrBusinessCardPhotoResultCollection : ObservableCollection<FlickrPhotoResult>, ISupportIncrementalLoading
  {
     public FlickrBusinessCardPhotoResultCollection()
     {
       this.searchUrl = new FlickrSearchUrl("business card", perPage: 5);
     }

     public bool HasMoreItems
     {
       get
       {
         // initially, we don't know because we haven't asked. once we've asked
         // we'll have a better picture...
         return (!pages.HasValue || this.searchUrl.Page <= pages.Value);
       }
     }

     public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
     {
       return (this.LoadMoreItemsInternalAsync(count).AsAsyncOperation());
     }

     async Task<LoadMoreItemsResult> LoadMoreItemsInternalAsync(uint count)
     {
       LoadMoreItemsResult result = new LoadMoreItemsResult();
       uint pagesRequested = (uint)(count / this.searchUrl.PerPage);
       if ((count % this.searchUrl.PerPage) != 0)
       {
         pagesRequested++;
       }

       for (int i = 0;((i < pagesRequested) && (!this.pages.HasValue || this.searchUrl.Page < this.pages.Value));i++)
       {
         try
         {
           var results = await FlickrSearcher.SearchAsync(this.searchUrl);
           this.searchUrl.Page++;
           pages = results.Pages;

           foreach (var photo in results.Photos)
           {
             this.Add(photo);
           }

           result.Count = (uint)results.Photos.Count();
         }
         catch
         {
           // don't really mind why this failed in this case.
         }        
       }

       return (result);
     }

     int? pages;
     FlickrSearchUrl searchUrl;
   }
}

Весь этот код работает отлично, но затем я встретил «незначительное препятствие». Оно состояло в том, что FlipView не поддерживает набор данных, которые реализуют интерфейс ISupportIncrementalLoading.

Это было небольшим ударом для меня, но я решил двигаться вперед и попробовать создать небольшой контрол на основе FlipView, который, по крайней мере, работал бы для моих целей (поддержка инкрементальной загрузки):

namespace App253
{
  using System;
  using System.Collections;
  using System.Threading.Tasks;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Data;
  /// <summary>
  ///  Trying to make a cheap/cheerful FlipView that does something with a data
  ///  source that implements ISupportIncrementalLoading. Not trying to deal
  ///  with all cases like e.g. where someone explicitly sets the items source
  ///  and so on - just trying to deal with a regular, data-bound flipview.
  /// </summary>
 
  class IncrementalFlipView : FlipView
  {
    public IncrementalFlipView()
    {
      this.DataContextChanged += OnDataContextChanged;
      this.SelectionChanged += OnSelectionChanged;
    }

    async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      // are we getting near to the end of the items that we have.
      IList list = this.ItemsSource as IList;\

      if (list != null)
      {
        if (list.Count - this.SelectedIndex <= LOAD_THRESHOLD)
        {
          // see if we've got anything more to load.
          await this.LoadNextDataItemsAsync();
        }
      }   
    }
 
    async void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
    {
      if (this.ItemsSource != this.previousItemsSource)
      {
        this.previousItemsSource = this.ItemsSource;
        this.loader = this.ItemsSource as ISupportIncrementalLoading;
        if (this.loader != null)
        {
          await this.LoadNextDataItemsAsync();
        }
    }
 
    async Task LoadNextDataItemsAsync()
    {
      if (this.loader.HasMoreItems)
      {
        await this.loader.LoadMoreItemsAsync(10);
      }
    }

    static readonly int LOAD_THRESHOLD = 3;
    static readonly int LOAD_ITEMS = 10;
    object previousItemsSource;
    ISupportIncrementalLoading loader;
  }
}

Вся логика заключается в том, что необходимо контролировать изменение DataContext (т.е. все будет работать только в том случае, если все делается путем связывания) и всякий раз, когда изменяется свойство мы смотрим на ItemsSource и, если он реализует ISupportIncrementalLoading, пытаемся изначально загрузить первый набор данных, а затем, всякий раз, когда остается только 3 элемента до конца списка — загружать еще 10 элементов.

Далее необходимо создать ViewModel.

namespace App253
{
  using App253.FlickrSearch;
  using System;
  using System.Threading.Tasks;
  using System.Windows.Input;
  using Windows.Foundation;
  using Windows.Graphics.Imaging;
  using Windows.Storage.Streams;
  using Windows.UI;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Media;
  using Windows.UI.Xaml.Shapes;
  using Windows.Web.Http;
  using WindowsPreview.Media.Ocr;
 
  class ViewModel : ViewModelBase
  {
    public ViewModel()
    {
      this.Items = new FlickrBusinessCardPhotoResultCollection();
      this.recogniseCommand = new SimpleCommand(this.OnRecognise);
      this.IsIdle = true;
    }

    public FlickrBusinessCardPhotoResultCollection Items { get; private set; // never called }
    public FlickrPhotoResult SelectedPhoto
    {
      get
      {
        return (this.selectedPhoto);
      }
      set
      {
        this.selectedPhoto = value;
      }
    }

    public string Name
    {
      get
      {
        return (this.name);
      }
      set
      {
        base.SetProperty(ref this.name, value);
      }
    }

    public string Email
    {
      get
      {
        return (this.email);
      }
      set
      {
        base.SetProperty(ref this.email, value);
      }
    }

    public string Phone
    {
      get
      {
        return (this.phone);
      }
      set
      {
        base.SetProperty(ref this.phone, value);
      }
    }

    public ICommand RecogniseCommand
    {
      get
      {
        return (this.recogniseCommand);
      }
    }

    async void OnRecognise()
    {
      this.IsIdle = false;
      try
      {
        if (this.selectedPhoto != null)
        {
        }
      }
      finally
      {
        this.IsIdle = true;
      }
    }

    public bool IsIdle
    {
      get
      {
        return (this.idle);
      }
      private set
      {
        this.recogniseCommand.SetEnabled(value);
        base.SetProperty(ref this.idle, value);
      }
    }
 
    bool idle;
    SimpleCommand recogniseCommand;
    FlickrPhotoResult selectedPhoto;
    string phone;
    string email;
    string name;
  }
}

И затем связать ее с представлением… Но это уже будет в следующей части данной статьи)

Ссылка на источник: OCR Library for WinRT Apps–Recognising Business Cards…

Реклама
Tagged with: , , , ,
Опубликовано в Development, Windows 8.1
2 comments on “OCR библиотека для WinRT приложений. Часть 1.
  1. […] первой части мы начали подготавливать почву для базового […]

  2. […] В первой части мы начали подготавливать почву для базового приложения. Собственно в это части мы его закончим и научимся пользоваться данной библиотекой. […]

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s

%d такие блоггеры, как: