Memproses SoftwareBitmaps dengan OpenCV

Artikel ini memperlihatkan cara membuat komponen Windows Runtime C++/WinRT asli yang mengonversi objek SoftwareBitmap ke jenis OpenCVMat. Ini memungkinkan Anda menggunakan algoritma pemrosesan gambar OpenCV yang luas pada bingkai yang diambil dengan API kamera Windows dan menampilkan hasilnya di aplikasi WinUI 3 Anda.

Overview

Kelas SoftwareBitmap adalah format gambar umum yang digunakan oleh API media Windows, sementara OpenCV menggunakan Mat kelas . Untuk menjembatani format ini, Anda membuat komponen runtime C++/WinRT yang:

  1. Menerima input SoftwareBitmap.
  2. Mengonversinya menjadi OpenCV Mat.
  3. Menerapkan pemrosesan gambar yang diinginkan.
  4. Mengembalikan hasil sebagai SoftwareBitmap.

Karena OpenCV adalah pustaka C++ asli, Anda menggunakan proyek C++/WinRT Windows Runtime Component untuk membuat jembatan. Aplikasi C# WinUI 3 Anda mereferensikan komponen ini.

Menyiapkan proyek komponen C++/WinRT

  1. Di Visual Studio, tambahkan proyek komponen Windows Runtime baru (C++/WinRT) ke solusi Anda. Beri nama sesuatu seperti OpenCVBridge.

  2. Unduh paket OpenCV NuGet dengan menjalankan perintah berikut di Konsol Package Manager yang menargetkan proyek jembatan:

    Install-Package OpenCV.Windows -ProjectName OpenCVBridge
    

    Atau, unduh rilis OpenCV dari opencv.org dan konfigurasikan secara manual jalur penyertaan dan jalur pustaka proyek Anda.

  3. Di proyek pch.hjembatan , tambahkan header OpenCV:

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

Membuat kelas runtime OpenCVHelper

Tentukan kelas runtime yang menyediakan metode untuk mengonversi antara SoftwareBitmap dan OpenCV Mat. Buat file OpenCVHelper.idlIDL :

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

Menerapkan konversi

Dalam OpenCVHelper.cpp, terapkan konversi dari SoftwareBitmap ke Mat dan kembali:

#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

Kode di atas menggunakan IMemoryBufferByteAccess untuk mengakses data piksel mentah. Objek input dan output SoftwareBitmap harus menggunakan Bgra8 format piksel. Jika bingkai dari MediaFrameReader menggunakan format yang berbeda, konversikan terlebih dahulu dengan SoftwareBitmap.Convert.

Menggunakan komponen dari C#

Di aplikasi C# WinUI 3 Anda, tambahkan referensi proyek ke OpenCVBridge komponen. Kemudian panggil ProcessBitmap dari kode pemrosesan bingkai Anda:

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

Menambahkan lebih banyak operasi pemrosesan

Anda dapat memperluas OpenCVHelper kelas dengan metode tambahan untuk operasi tertentu. Perbarui IDL dan implementasi:

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