Статьи

Совет дня: узнайте, как быстро сканировать коды с помощью Silverlight ZXing на Windows Phone

Существует популярная библиотека с открытым исходным кодом, которая дает разработчикам возможность сканировать QR-коды и штрих-коды —  ZXing  (произносится «Zebra Crossing»). Существует также популярный порт проекта, который можно использовать в контексте приложений для Windows Phone —  Silverlight ZXing . Очевидно, вы можете и должны получить пакет от  NuGet .

Предполагая, что вы установили и подготовили пакет в своем приложении, давайте посмотрим, как можно сканировать коды. Чтобы убедиться, что я не вмешиваюсь ни в какие другие части моего приложения, я делегировал задачу показа потока с камеры на дополнительную страницу — CapturePage.xaml . Не стесняйтесь добавлять один самостоятельно, если приложение нуждается в этом. Если нет, просто используйте главную страницу.

Скелет самой страницы должен быть очень простым — вам понадобится только VideoBrush, и его можно установить в качестве фона корневой сетки:

<Grid x:Name="LayoutRoot">
    <Grid.Background>
        <VideoBrush x:Name="coreBrush"></VideoBrush>
    </Grid.Background>
</Grid>

Это приведет к несколько искаженному изображению, поэтому вы можете добавить к нему преобразование.

<Grid x:Name="LayoutRoot">
    <Grid.Background>
        <VideoBrush x:Name="coreBrush">
            <VideoBrush.RelativeTransform>
                <CompositeTransform  
                    x:Name="brushTransform" CenterX=".5" CenterY=".5" />
            </VideoBrush.RelativeTransform>
        </VideoBrush>
    </Grid.Background>
</Grid>

В коде позади мы будем работать с 4 основными объектами:

private DispatcherTimer _timer;
private PhotoCameraLuminanceSource _luminance;
private Reader _reader;
private PhotoCamera _photoCamera;

DispatcherTimer будет использоваться для выполнения постоянного сканирования потока и передачи данных к читателю кода. PhotoCameraLuminanceSource используется SilverlightZXing  для обработки входящего потока и преобразования входного буфера для возможности его чтения. Реализация, которую я использую,  доступна здесь .

Чтения экземпляр является основным классом , который позволяет обрабатывать код. Вместо непосредственного использования ограниченного типа, такого как QRCodeReader , я использую интерфейс, который позволит мне при необходимости менять типы. 

ПРИМЕЧАНИЕ . Интерфейс здесь неправильно называется Reader , когда он должен быть IReader . Это установлено в  Руководстве по именованию интерфейса C # .

Наконец, что не менее важно , PhotoCamera используется для получения визуального и двоичного представления входящего потока камеры.

Когда страница загружена, таймер инициализируется и назначается соответствующая функция для обработки события Tick:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    try
    {
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(300);
        timer.Tick += (o, arg) => Scan();

        camera = new PhotoCamera();
        camera.Initialized += OnPhotoCameraInitialized;
        coreBrush.SetSource(camera);

        CameraButtons.ShutterKeyHalfPressed += (o, arg) => camera.Focus();
    }
    catch
    {
        
    }
    base.OnNavigatedTo(e);
}

Важно подчеркнуть тот факт, что я использую интервалы времени 300 мс для захвата изображения. По сути, нет никаких требований относительно того, каким должен быть этот интервал, но вам нужно поиграть, чтобы оценить подходящее значение для вашего конкретного приложения. Примите во внимание тот факт, что, поскольку читателю требуется некоторое время для обработки кода из изображения, между захватом и распознаванием кода будет задержка. 

Существует стандартная процедура инициализации камеры, которая дает мне изображение того, что пользователь видит на экране:

private void OnPhotoCameraInitialized(object sender, CameraOperationCompletedEventArgs e)
{
    try
    {
        int width = Convert.ToInt32(camera.PreviewResolution.Width);
        int height = Convert.ToInt32(camera.PreviewResolution.Height);

        luminance = new PhotoCameraLuminanceSource(width, height);
        reader = new QRCodeReader();

        Dispatcher.BeginInvoke(() =>
        {
            try
            {
                brushTransform.Rotation = camera.Orientation;

                if (timer != null)
                {
                    timer.Start();
                }
            }
            catch
            {
                // camera disposed.
            }
        });
    }
    catch
    {

    }
}

Здесь у вас есть варианты. Прежде всего, я использую QRCodeReader в качестве класса получателя. Ограничение этого типа только одним типом кодов, которые должны быть прочитаны, позволяет мне получать относительно быструю обработку изображений. Я мог бы использовать  com.google.zxing.oned.MultiFormatOneDReader,  который позволил бы мне сканировать любой из поддерживаемых одномерных кодов. Как я упоминал ранее в этой статье, я могу легко назначить новый экземпляр этого типа для читателя, потому что он будет назначением на основе интерфейса, а MultiFormatOneDReader реализует Reader .

В конструкторе по умолчанию вам нужно будет передать словарь, в котором будет указано, какие коды следует сканировать. В текущей реализации вам нужно будет передать словарь с ключом DecodeHintType.POSSIBLE_FORMATS и List <DecodeHintType> в качестве связанного значения, чтобы показать, какие типы сканировать.

ПРИМЕЧАНИЕ. Да, я знаю, что эту реализацию можно оптимизировать, просто используя конструктор на основе флагов (например, что-то похожее используется в .NET Reflection).

Но здесь есть проблема — из-за отставания в производительности Silverlight ZXing существует огромная задержка между захватом и полной обработкой. После сканирования кадра для получения результата от кадра может потребоваться до 5 или 10 секунд, учитывая, что сам кадр был захвачен правильно, что в большинстве случаев не так — вам нужен устойчивый снимок. Хотя механизм обработки будет пытаться декодировать изображение, камера будет на много кадров впереди, что создает ненужные накладные расходы.

Моя рекомендация здесь — если вы используете Silverlight ZXing, убедитесь, что вы ограничиваете типы кодов, которые вы сканируете в течение одного цикла кадра. Для этого используйте специфичные для кода реализации:

  • QRCodeReader
  • Code128Reader
  • EAN13Reader
  • EAN8Reader
  • UPCAReader
  • UPCEANReader
  • UPCEReader

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

Так что же происходит, когда таймер проходит каждую итерацию? Очевидно, сканирование :

private void Scan()
{
    try
    {
        camera.GetPreviewBufferY(luminance.PreviewBufferY);
        var binarizer = new HybridBinarizer(luminance);
        var binBitmap = new BinaryBitmap(binarizer);
        var result = reader.decode(binBitmap);

        timer.Stop();
        VerifyScannedItem(result.Text);
    }
    catch
    {
    }
}

The try/catch block here is necessary because in the current implementation, the reader will throw an exception if a code is not recognized. Because when the code is scanned you might want to take an action, the timer is stopped, after which VerifyScannedItem is invoked. On its completion, the timer can be reset.

As I mentioned in my previous article, make sure to disable the timer when you are navigating away from the page.