Обработка растровых изображений с помощью OpenCV

В этой статье объясняется, как использовать класс SoftwareBitmap , который используется различными API среды выполнения Windows для представления изображений с помощью библиотеки компьютерного зрения с открытым исходным кодом (OpenCV), библиотеки машинного кода с открытым исходным кодом, которая предоставляет широкий спектр алгоритмов обработки изображений.

Примеры, приведенные в этой статье, показывают, как создать компонент среды выполнения Windows на нативном коде, который можно использовать в UWP-приложении, включая приложения, созданные на языке C#. Этот вспомогательный компонент предоставляет один метод, Размытие, который будет использовать функцию обработки размытия изображений OpenCV. Компонент реализует частные методы, которые получают указатель на базовый буфер данных изображения, который можно использовать непосредственно библиотекой OpenCV, что упрощает расширение вспомогательного компонента для реализации других функций обработки OpenCV.

Замечание

Метод, используемый компонентом OpenCVHelper, описанным в этой статье, требует, чтобы данные изображения обрабатывались в памяти ЦП, а не в памяти GPU. Таким образом, для API, которые позволяют запрашивать расположение памяти изображений, таких как класс MediaCapture , следует указать память ЦП.

Создание вспомогательного компонента среды выполнения Windows для взаимодействия с OpenCV

1. Добавьте новый проект компонента на собственном коде среды выполнения Windows в ваше решение.

  1. Добавьте новый проект в ваше решение в Visual Studio, щелкнув правой кнопкой мыши на вашем решении в обозревателе решений и выбрав Добавить ->Новый проект.
  2. В категории Visual C++ выберите компонент среды выполнения Windows (универсальная версия Windows). В этом примере назовите проект OpenCVBridge и нажмите кнопку "ОК".
  3. В диалоговом окне "Новый универсальный проект Windows" выберите целевую и минимальную версию ОС для приложения и нажмите кнопку "ОК".
  4. Щелкните правой кнопкой мыши автоматически созданный файл Class1.cpp в обозревателе решений и выберите "Удалить", когда появится диалоговое окно подтверждения, нажмите кнопку "Удалить". Затем удалите файл заголовка Class1.h.
  5. Щелкните правой кнопкой мыши значок проекта OpenCVBridge и выберите Добавитькласс.... В диалоговом окне добавления класса введите "OpenCVHelper" в поле "Имя класса ", а затем нажмите кнопку ОК. Код будет добавлен в созданные файлы классов на следующем шаге.

2. Добавьте пакеты NuGet OpenCV в проект компонента

  1. Щелкните правой кнопкой мыши значок проекта OpenCVBridge в обозревателе решений и выберите пункт "Управление пакетами NuGet...
  2. Когда откроется диалоговое окно диспетчера пакетов NuGet, выберите вкладку Обзор и введите "OpenCV.Win" в поле поиска.
  3. Выберите "OpenCV.Win.Core" и щелкните Установить. В диалоговом окне Предварительный просмотр нажмите кнопку ОК.
  4. Используйте ту же процедуру, чтобы установить пакет OpenCV.Win.ImgProc.

Замечание

OpenCV.Win.Core и OpenCV.Win.ImgProc обновляются нерегулярно и не проходят проверки соответствия магазина приложений, поэтому эти пакеты предназначены только для экспериментирования.

3. Реализация класса OpenCVHelper

Вставьте следующий код в файл заголовка OpenCVHelper.h. Этот код включает файлы заголовков OpenCV для Core и пакет ImgProc, которые мы установили и объявляет три метода, которые будут показаны в следующих шагах.

#pragma once

// OpenCVHelper.h
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>


namespace OpenCVBridge
{
    public ref class OpenCVHelper sealed
    {
    public:
        OpenCVHelper() {}

        void Blur(
            Windows::Graphics::Imaging::SoftwareBitmap^ input,
            Windows::Graphics::Imaging::SoftwareBitmap^ output);


    private:
        // helper functions for getting a cv::Mat from SoftwareBitmap
        bool TryConvert(Windows::Graphics::Imaging::SoftwareBitmap^ from, cv::Mat& convertedMat);
        bool GetPointerToPixelData(Windows::Graphics::Imaging::SoftwareBitmap^ bitmap,
            unsigned char** pPixelData, unsigned int* capacity);
    };
}

Удалите существующее содержимое файла OpenCVHelper.cpp, а затем добавьте следующие директивы include.

#include "pch.h"
#include "OpenCVHelper.h"
#include "MemoryBuffer.h"

После директив include добавьте , используя директивы.

using namespace OpenCVBridge;
using namespace Platform;
using namespace Windows::Graphics::Imaging;
using namespace Windows::Foundation;
using namespace Microsoft::WRL;
using namespace cv;

Затем добавьте метод GetPointerToPixelData в OpenCVHelper.cpp. Этот метод принимает SoftwareBitmap и, через серию преобразований, получает представление COM-интерфейса данных пикселей, через которое можно получить указатель на основной буфер данных в виде массива символов char .

Сначала BitmapBuffer с данными пикселей получается путем вызова LockBuffer, запрашивая буфер чтения и записи, чтобы библиотека OpenCV могла изменять эти пиксельные данные. CreateReference вызывается для получения объекта IMemoryBufferReference. Затем интерфейс IMemoryBufferByteAccess создается как IInspectable, базовый интерфейс всех классов среды выполнения Windows и вызывается QueryInterface, чтобы получить интерфейс IMemoryBufferByteAccess COM, который позволит нам получить буфер данных пикселей в виде массива char. Наконец, заполните массив char путем вызова IMemoryBufferByteAccess::GetBuffer. Если один из шагов преобразования в этом методе завершается неудачей, метод возвращает false, что указывает на невозможность дальнейшей обработки.

bool OpenCVHelper::GetPointerToPixelData(SoftwareBitmap^ bitmap, unsigned char** pPixelData, unsigned int* capacity)
{
    BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
    IMemoryBufferReference^ reference = bmpBuffer->CreateReference();

    ComPtr<IMemoryBufferByteAccess> pBufferByteAccess;
    if ((reinterpret_cast<IInspectable*>(reference)->QueryInterface(IID_PPV_ARGS(&pBufferByteAccess))) != S_OK)
    {
        return false;
    }

    if (pBufferByteAccess->GetBuffer(pPixelData, capacity) != S_OK)
    {
        return false;
    }
    return true;
}

Затем добавьте метод TryConvert , показанный ниже. Этот метод принимает SoftwareBitmap и пытается преобразовать его в объект Mat, представляющий собой матрицу OpenCV, используемую для представления буферов данных изображения. Этот метод вызывает метод GetPointerToPixelData , определенный выше, чтобы получить представление массива символов буфера данных пикселей. Если это выполнено успешно, вызывается конструктор класса Mat , передавая ширину пикселей и высоту, полученную из исходного объекта SoftwareBitmap .

Замечание

В этом примере указывается константа CV_8UC4 в качестве формата пикселей для созданного объекта Mat . Это означает, что SoftwareBitmap, передаваемого в этот метод, должен иметь значение свойства BitmapPixelFormatBGRA8 с предварительно умноженным альфа-каналом, эквивалентом CV_8UC4, для работы с этим примером.

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

bool OpenCVHelper::TryConvert(SoftwareBitmap^ from, Mat& convertedMat)
{
    unsigned char* pPixels = nullptr;
    unsigned int capacity = 0;
    if (!GetPointerToPixelData(from, &pPixels, &capacity))
    {
        return false;
    }

    Mat mat(from->PixelHeight,
        from->PixelWidth,
        CV_8UC4, // assume input SoftwareBitmap is BGRA8
        (void*)pPixels);
    
    // shallow copy because we want convertedMat.data = pPixels
    // don't use .copyTo or .clone
    convertedMat = mat;
    return true;
}

Наконец, этот вспомогательный класс реализует один метод обработки изображений, Blur, который просто использует метод TryConvert , определенный выше, для получения объекта Mat , представляющего исходное растровое изображение и целевое растровое изображение для операции размытия, а затем вызывает метод размытия из библиотеки OpenCV ImgProc. Другой параметр размытия указывает размер эффекта размытия в направлениях X и Y.

void OpenCVHelper::Blur(SoftwareBitmap^ input, SoftwareBitmap^ output)
{
    Mat inputMat, outputMat;
    if (!(TryConvert(input, inputMat) && TryConvert(output, outputMat)))
    {
        return;
    }
    blur(inputMat, outputMat, cv::Size(15, 15));
}

Простой пример SoftwareBitmap OpenCV с помощью вспомогательного компонента

Теперь, когда создан компонент OpenCVBridge, мы можем создать простое приложение на C#, в котором используется метод размытия OpenCV для модификации SoftwareBitmap. Чтобы получить доступ к компоненту среды выполнения Windows из приложения UWP, необходимо сначала добавить ссылку на компонент. В обозревателе решений щелкните правой кнопкой мыши узел References в проекте вашего приложения UWP и выберите Добавить ссылку.... В диалоговом окне диспетчера ссылок выберите Проекты->Решение. Установите флажок рядом с проектом OpenCVBridge и нажмите кнопку ОК.

Приведенный ниже пример кода позволяет пользователю выбрать файл изображения, а затем использовать BitmapDecoder для создания представления SoftwareBitmap изображения. Дополнительные сведения о работе с SoftwareBitmap см. в статье "Создание, изменение и сохранение растровых изображений".

Как обсуждалось ранее в этой статье, класс OpenCVHelper требует, чтобы все предоставленные изображения SoftwareBitmap были закодированы в формате пикселей BGRA8 с предварительно умноженными альфа-значениями. Поэтому, если изображение еще не в этом формате, пример кода вызывает Convert для преобразования изображения в ожидаемый формат.

Затем создается SoftwareBitmap для использования в качестве цели операции размытия. Свойства входного изображения используются в качестве аргументов конструктора для создания растрового изображения с соответствующим форматом.

Создается новый экземпляр OpenCVHelper, и вызывается метод Blur, передавая исходные и целевые растровые изображения. Наконец, создается SoftwareBitmapSource, чтобы назначить выходное изображение элементу управления XAML Image.

Этот пример кода использует API из следующих пространств имен в дополнение к пространствам имен, включенным в шаблон проекта по умолчанию.

using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media.Imaging;
FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;

var inputFile = await fileOpenPicker.PickSingleFileAsync();

if (inputFile == null)
{
    // The user cancelled the picking operation
    return;
}

SoftwareBitmap inputBitmap;
using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
{
    // Create the decoder from the stream
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

    // Get the SoftwareBitmap representation of the file
    inputBitmap = await decoder.GetSoftwareBitmapAsync();
}

if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8
            || inputBitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
{
    inputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
    
SoftwareBitmap outputBitmap = new SoftwareBitmap(inputBitmap.BitmapPixelFormat, inputBitmap.PixelWidth, inputBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);


var helper = new OpenCVBridge.OpenCVHelper();
helper.Blur(inputBitmap, outputBitmap);

var bitmapSource = new SoftwareBitmapSource();
await bitmapSource.SetBitmapAsync(outputBitmap);
imageControl.Source = bitmapSource;