Procesar SoftwareBitmaps con OpenCV

En este artículo se muestra cómo crear un componente nativo de C++/WinRT Windows Runtime que convierte objetos SoftwareBitmap al tipo OpenCVMat. Esto le permite usar los algoritmos de procesamiento de imágenes extensos de OpenCV en fotogramas capturados con las API de cámara de Windows y mostrar los resultados en la aplicación WinUI 3.

Visión general

La SoftwareBitmap clase es el formato de imagen común usado por Windows API multimedia, mientras que OpenCV usa la Mat clase . Para puentear estos formatos, cree un componente en tiempo de ejecución de C++/WinRT que:

  1. Acepta una SoftwareBitmap entrada.
  2. Lo convierte en openCV Mat.
  3. Aplica el procesamiento de imágenes deseado.
  4. Devuelve el resultado como .SoftwareBitmap

Dado que OpenCV es una biblioteca nativa de C++, se usa un proyecto de componente de Windows Runtime de C++/WinRT para crear el puente. La aplicación WinUI 3 de C# hace referencia a este componente.

Configuración del proyecto de componente de C++/WinRT

  1. En Visual Studio, agregue un nuevo proyecto de componente de Windows Runtime (C++/WinRT) a la solución. Asígnele un nombre como OpenCVBridge.

  2. Descargue el paquete NuGet de OpenCV ejecutando el siguiente comando en la consola de Administrador de paquetes destinada al proyecto bridge:

    Install-Package OpenCV.Windows -ProjectName OpenCVBridge
    

    Como alternativa, descargue la versión de OpenCV de opencv.org y configure manualmente las rutas de acceso de inclusión y biblioteca del proyecto.

  3. En el pch.hproyecto puente, añada los archivos de encabezado de OpenCV:

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

Creación de la clase en tiempo de ejecución OpenCVHelper

Defina una clase en tiempo de ejecución que proporcione métodos para convertir entre SoftwareBitmap y OpenCV Mat. Cree el archivo IDL OpenCVHelper.idl:

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

Implementación de la conversión

En OpenCVHelper.cpp, implemente la conversión desde SoftwareBitmap hacia Mat y hacia atrás:

#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

El código anterior usa IMemoryBufferByteAccess para acceder a los datos de píxeles sin procesar. Los objetos de entrada y salida SoftwareBitmap deben usar el formato de Bgra8 píxel. Si los fotogramas de MediaFrameReader usan un formato diferente, conviértelos primero con SoftwareBitmap.Convert.

Uso del componente de C#

En la aplicación WinUI 3 de C#, agregue una referencia de proyecto al OpenCVBridge componente. A continuación, llame a ProcessBitmap desde su código de procesamiento de fotogramas:

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;
    });
}

Adición de más operaciones de procesamiento

Puede ampliar la OpenCVHelper clase con métodos adicionales para operaciones específicas. Actualice el IDL y la implementación:

// 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);
}