Processar SoftwareBitmaps com OpenCV

Este artigo mostra como criar um componente de Windows Runtime C++/WinRT nativo que converte objetos SoftwareBitmap no tipo OpenCVMat. Isso permite que você use os amplos algoritmos de processamento de imagem do OpenCV em quadros capturados com as APIs da câmera Windows e exiba os resultados em seu aplicativo WinUI 3.

Visão geral

A SoftwareBitmap classe é o formato de imagem comum usado pelas APIs de mídia Windows, enquanto o OpenCV usa a Mat classe. Para fazer a ponte entre esses formatos, crie um componente de runtime do C++/WinRT que:

  1. Aceita uma SoftwareBitmap entrada.
  2. Converte-o em um OpenCV Mat.
  3. Aplica o processamento de imagem desejado.
  4. Retorna o resultado como um SoftwareBitmap.

Como o OpenCV é uma biblioteca C++ nativa, você usa um projeto C++/WinRT Windows Runtime Component para criar a ponte. Seu aplicativo WinUI 3 em C# faz referência a esse componente.

Configurar o projeto de componente C++/WinRT

  1. Em Visual Studio, adicione um novo projeto do componente Windows Runtime (C++/WinRT) à sua solução. Nomeie-o como OpenCVBridge.

  2. Baixe o pacote NuGet do OpenCV executando o seguinte comando no Console do Gerenciador de Pacotes para o projeto de bridge:

    Install-Package OpenCV.Windows -ProjectName OpenCVBridge
    

    Como alternativa, baixe a versão do OpenCV de opencv.org e configure manualmente os caminhos de inclusão e biblioteca do projeto.

  3. No projeto de bridge pch.h, adicione os cabeçalhos do OpenCV:

    #include <opencv2/core.hpp>
    #include <opencv2/imgproc.hpp>
    #include <robuffer.h>
    #include <windows.foundation.h>
    

Criar a classe de runtime OpenCVHelper

Definir uma classe de runtime que fornece métodos para converter entre SoftwareBitmap e OpenCV Mat. Crie o arquivo OpenCVHelper.idlIDL:

// OpenCVHelper.idl
namespace OpenCVBridge
{
    runtimeclass OpenCVHelper
    {
        OpenCVHelper();
        void ProcessBitmap(
            Windows.Graphics.Imaging.SoftwareBitmap input,
            Windows.Graphics.Imaging.SoftwareBitmap output);
    }
}

Implementar a conversão

Em OpenCVHelper.cpp, implemente a conversão de SoftwareBitmap para Mat e vice-versa:

#include "pch.h"
#include "OpenCVHelper.h"
#include "OpenCVHelper.g.cpp"
#include <opencv2/imgproc.hpp>

using namespace winrt;
using namespace Windows::Graphics::Imaging;

namespace winrt::OpenCVBridge::implementation
{
    void OpenCVHelper::ProcessBitmap(
        SoftwareBitmap const& input,
        SoftwareBitmap const& output)
    {
        // Lock the input buffer for reading
        auto inputBuffer = input.LockBuffer(
            BitmapBufferAccessMode::Read);
        auto inputRef = inputBuffer.CreateReference();

        uint8_t* inputData = nullptr;
        uint32_t inputSize = 0;
        winrt::check_hresult(
            inputRef.as<::Windows::Foundation::
                IMemoryBufferByteAccess>()->GetBuffer(
                    &inputData, &inputSize));

        auto inputDesc =
            inputBuffer.GetPlaneDescription(0);

        // Create a Mat from the input data (use Stride for correct row size)
        cv::Mat inputMat(
            inputDesc.Height,
            inputDesc.Width,
            CV_8UC4,
            inputData,
            inputDesc.Stride);

        // Lock the output buffer for writing
        auto outputBuffer = output.LockBuffer(
            BitmapBufferAccessMode::Write);
        auto outputRef = outputBuffer.CreateReference();

        uint8_t* outputData = nullptr;
        uint32_t outputSize = 0;
        winrt::check_hresult(
            outputRef.as<::Windows::Foundation::
                IMemoryBufferByteAccess>()->GetBuffer(
                    &outputData, &outputSize));

        auto outputDesc =
            outputBuffer.GetPlaneDescription(0);

        cv::Mat outputMat(
            outputDesc.Height,
            outputDesc.Width,
            CV_8UC4,
            outputData,
            outputDesc.Stride);

        // Apply image processing - example: blur
        cv::GaussianBlur(inputMat, outputMat, cv::Size(15, 15), 5);
    }
}

Note

O código acima usa IMemoryBufferByteAccess para acessar os dados de pixel brutos. Os objetos de entrada e saída SoftwareBitmap devem usar o Bgra8 formato de pixel. Se os quadros de MediaFrameReader usarem um formato diferente, converta-os primeiro com SoftwareBitmap.Convert.

Use o componente de C#

No aplicativo WinUI 3 do C#, adicione uma referência de projeto ao OpenCVBridge componente. Em seguida, chame ProcessBitmap a partir do seu código de processamento de quadros:

using OpenCVBridge;
using Windows.Graphics.Imaging;

private readonly OpenCVHelper _openCVHelper = new();

private void ProcessFrameWithOpenCV(
    SoftwareBitmap inputBitmap)
{
    // Ensure the bitmap is in Bgra8 format
    if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
    {
        inputBitmap = SoftwareBitmap.Convert(
            inputBitmap, BitmapPixelFormat.Bgra8);
    }

    // Create an output bitmap with the same dimensions
    var outputBitmap = new SoftwareBitmap(
        BitmapPixelFormat.Bgra8,
        inputBitmap.PixelWidth,
        inputBitmap.PixelHeight,
        BitmapAlphaMode.Premultiplied);

    // Process with OpenCV
    _openCVHelper.ProcessBitmap(inputBitmap, outputBitmap);

    // Display the result
    DispatcherQueue.TryEnqueue(async () =>
    {
        var source =
            new Microsoft.UI.Xaml.Media.Imaging
                .SoftwareBitmapSource();
        await source.SetBitmapAsync(outputBitmap);
        OutputImage.Source = source;
    });
}

Adicionar mais operações de processamento

Você pode estender a OpenCVHelper classe com métodos adicionais para operações específicas. Atualize a IDL e a implementação:

// Add to OpenCVHelper.idl
void ApplyCannyEdges(
    Windows.Graphics.Imaging.SoftwareBitmap input,
    Windows.Graphics.Imaging.SoftwareBitmap output,
    Double threshold1,
    Double threshold2);
// Implementation
void OpenCVHelper::ApplyCannyEdges(
    SoftwareBitmap const& input,
    SoftwareBitmap const& output,
    double threshold1,
    double threshold2)
{
    // ... lock buffers as above ...

    cv::Mat grayMat;
    cv::cvtColor(inputMat, grayMat, cv::COLOR_BGRA2GRAY);

    cv::Mat edges;
    cv::Canny(grayMat, edges, threshold1, threshold2);

    cv::cvtColor(edges, outputMat, cv::COLOR_GRAY2BGRA);
}