Pull to refresh

Распознавание штрих и QR кодов в приложениях UWP

Reading time 5 min
Views 26K

Я не открою Америку, если скажу, что самой популярной библиотекой для распознавания штрихкода является ZXing («Zebra Crossing»). Список поддерживаемых форматов довольно внушителен и включает в себя: EAN-8 и EAN-13, QR Code, UPC-A и UPC-E, Code 39, Code 93, Code 128 и другие.

Есть порт и для WinRT, а значит, библиотеку можно использовать и с универсальной платформой Windows.

PCL для распознавания штрихкодов называется ZXing.Net Название как бы намекает, что можно использовать эту библиотеку еще и с приложениями .Net

На что сразу пришлось обратить внимание. Если делаем простое фото, то обычно не утруждаем себя и просто делаем снимок. Если же мы обрабатываем какой-либо снимок, то необходимо чтобы качество картинки было наилучшим. Поэтому при инициализации камеры необходимо задать максимально возможное разрешение. Для этого используем код подобный на код примера: Camera resolution sample

Кроме того, необходимо определить присутствует у устройства фронтальная или задняя камера. Это несложно:

   var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
   DeviceInformation frontCamera = null;
   DeviceInformation rearCamera = null;
   foreach (var device in devices)
   {
       switch (device.EnclosureLocation.Panel)
       {
           case Windows.Devices.Enumeration.Panel.Front:
               frontCamera = device;
               break;
           case Windows.Devices.Enumeration.Panel.Back:
               rearCamera = device;
               break;
       }
   }

После чего можно инициализировать захват медиа:

   if (rearCamera != null)
   {
     await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings { VideoDeviceId = rearCamera.Id });
   }

Установка разрешения происходит следующим образом:

   public async Task SetResolution()
   {
     System.Collections.Generic.IReadOnlyList<IMediaEncodingProperties> res;
     res = mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo);
     uint maxResolution = 0;
     int indexMaxResolution = 0;
     if (res.Count >= 1)
     {
       for (int i = 0; i < res.Count; i++)
       {
       VideoEncodingProperties vp = (VideoEncodingProperties)res[i];
         if (vp.Width > maxResolution)
         {
           indexMaxResolution = i;
           maxResolution = vp.Width;
         }
       }
     await mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, res[indexMaxResolution]);
     }
   }

Захват и распознавание изображения будет происходить примерно так:

IRandomAccessStream fileStream = new InMemoryRandomAccessStream();
await mediaCapture.CapturePhotoToStreamAsync(Windows.Media.MediaProperties.ImageEncodingProperties.CreateBmp(), fileStream);
string res = await BarcodeDecoder.DecodeStreamToBarcode(fileStream);

Для распознавания потока используется класс BarcodeDecoder, который был взят у Mike Taulty

Код класса скрыт под спойлером
internal static class BarcodeDecoder
   {
       static BarcodeReader barcodeReader;

       static BarcodeDecoder()
       {
           barcodeReader = new ZXing.BarcodeReader();
           barcodeReader.Options.PureBarcode = false;
           barcodeReader.Options.Hints.Add(DecodeHintType.TRY_HARDER, true);
           barcodeReader.Options.PossibleFormats = new BarcodeFormat[] { BarcodeFormat.QR_CODE,BarcodeFormat.All_1D };
           barcodeReader.Options.TryHarder = true;
           barcodeReader.AutoRotate = true;
       }

       public async static Task<string> DecodeStreamToBarcode(IRandomAccessStream photoStream)
       {
           BitmapDecoder bitmapDecoder = await BitmapDecoder.CreateAsync(photoStream);
           BitmapTransform emptyBitmapTransform = new BitmapTransform();

           PixelDataProvider pixelDataProvider = await bitmapDecoder.GetPixelDataAsync(
           BitmapPixelFormat.Rgba8,
           BitmapAlphaMode.Premultiplied,
           emptyBitmapTransform,
           ExifOrientationMode.RespectExifOrientation,
           ColorManagementMode.DoNotColorManage);

           var zxingResult = barcodeReader.Decode(pixelDataProvider.DetachPixelData(),
                        (int)bitmapDecoder.PixelWidth, (int)bitmapDecoder.PixelHeight, BitmapFormat.RGBA32);

           string res = "";
           if (!String.IsNullOrEmpty(zxingResult?.Text)) res = zxingResult?.Text;
           return res;
       }
}


В данном классе распознаются штрихкоды форматов QR_CODE и All_1D. При этом All_1D включает в себя следующие форматы: UPC-A, UPC-E, EAN-8 и EAN-13. Можно добавить еще и другие форматы. Скажем, декодер, который мы используем, поддерживает следующие форматы: UPC-A, UPC-E, EAN-8, EAN-13, Code 39, Code 93, Code 128, ITF, Codabar, MSI, RSS-14 (все варианты), QR Code, Data Matrix, Aztec и PDF-417

Такой способ распознавания хорошо подходит для уже имеющихся изображений. Если же использовать камеру, то придется сделать множество фотографий, пока не получится удачный распознаваемый снимок. Другими словами, первый блин оказался комом, но какой-то полезный опыт был получен.

Распознавание кода на лету


В результате интенсивных поисков был найден следующий репозиторий VideoScanZXingWinRT, который и был взят за основу. Захват изображения происходит из режима превью.

Здесь ко мне немного запоздало пришло осознание того, что камера без возможностей фокусирования не сможет сделать нормальный макроснимок. Теоретически, можно пробовать сканировать штрихкод и телефонами с камерой без автофокуса, но шансы малы. Можете попробовать сами. Камеры же с фокусировкой могут поддерживать автофокусировку, а могут фокусироваться в ручном режиме. Для ручного режима я сделал автофокусировку по таймеру раз в 3 секунды.

Кроме того была отключена подсветка, которая создавала блики и мешала распознаванию

 if (mediaCapture.VideoDeviceController.FlashControl.Supported) mediaCapture.VideoDeviceController.FlashControl.Auto = false;

В результате удаления лишнего и внесения исправлений получился такой вот пример:

Barcode_Scanner_UWP

Приглашаю всех присоединиться к проекту. Буду рад улучшениям и корректировкам.

Для использования в своем проекте необходимо добавить пользовательский элемент управления BarcodeScannerControl.xaml вместе с C# code behind. Затем в код страницы, из которой будет происходить вызов (в примере это MainPage.xaml), добавить Popup с User Control внутри

   <Popup x:Name="BarcodePopup" IsOpen="False" IsLightDismissEnabled="False">
      <Grid>
         <local:BarcodeScannerControl x:Name="barcodecontrol"  Width="{Binding ElementName=MainGrid,Path=ActualWidth}" 
                                Height="{Binding ElementName=MainGrid,Path=ActualHeight}"></local:BarcodeScannerControl>
      </Grid>
   </Popup>

В данном случае ширина и высота пользовательского элемента управления привязаны к размерам элемента контейнера окна с именем MainGrid.

В код C# необходимо добавить 2 метода. Один будет вызван в случае успешного поиска штрихкода, второй в случае ошибки. Сигнатуры методов таковы, что параметром одного является string, параметром второго exception. В примере это void BarcodeFound(string barcode) и void OnError(Exception e).

Теперь можно открыть Popup и выполнить метод, который запускает превью картинки с камеры и сканирование штрихкода:

BarcodePopup.IsOpen = true;
await barcodecontrol.StartScan(BarcodeFound, OnError);

Один момент, о котором не следует забывать – жизненный цикл приложения UWP. В нашем случае, если приложение переходит в состояние «приостановлено», необходимо остановить процесс просмотра картинки с камеры, поиска штрихкода и закрыть Popup. То есть добавить такой вот код:

   private async void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
   {
       var deferral = e.SuspendingOperation.GetDeferral();
       await barcodecontrol.Cleanup();
       BarcodePopup.IsOpen = false;
       deferral.Complete();
   }

   protected override void OnNavigatedTo(NavigationEventArgs e)
   {
       Application.Current.Suspending += App_Suspending;
   }

   protected override void OnNavigatedFrom(NavigationEventArgs e)
   {
       Application.Current.Suspending -= App_Suspending;
   }

Теперь о минусах. Минус один, – библиотека ZXing.Net не обновлялась уже 2 года. Хотя с другой стороны, если работает без багов и не нужны новые функции, то, возможно, ничего страшного в этом нет. Проект неофициальный, поэтому работа над ним идет когда у разработчиков появляется свободное время. Сайт на олдскульном Codeplex вполне себе подает признаки жизни. На нем же можно найти и исходник кода. Хорошая новость, — обновление самой библиотеки планируется в ближайшем будущем, то есть проект не заброшен.
Tags:
Hubs:
+21
Comments 4
Comments Comments 4

Articles