OpenCV を使用して SoftwareBitmaps を処理する

この記事では、SoftwareBitmap オブジェクトを OpenCV Mat 型に変換するネイティブ C++/WinRT Windows ランタイム コンポーネントを作成する方法について説明します。 これにより、Windows カメラ API でキャプチャされたフレームで OpenCV の広範な画像処理アルゴリズムを使用し、結果を WinUI 3 アプリに表示できます。

Overview

SoftwareBitmap クラスは、Windows メディア API で使用される一般的なイメージ形式ですが、OpenCV では Mat クラスが使用されます。 これらの形式をブリッジするには、次のような C++/WinRT ランタイム コンポーネントを作成します。

  1. SoftwareBitmap入力を受け入れます。
  2. これを OpenCV Matに変換します。
  3. 目的の画像処理を適用します。
  4. 結果を SoftwareBitmapとして返します。

OpenCV はネイティブ C++ ライブラリであるため、C++/WinRT Windows ランタイム コンポーネント プロジェクトを使用してブリッジを作成します。 C# WinUI 3 アプリがこのコンポーネントを参照しています。

C++/WinRT コンポーネント プロジェクトを設定する

  1. Visual Studioで、新しい Windows ランタイム コンポーネント (C++/WinRT) プロジェクトをソリューションに追加します。 OpenCVBridgeのような名前を付けます。

  2. ブリッジ プロジェクトを対象とする パッケージ マネージャー コンソールで次のコマンドを実行して、OpenCV NuGet パッケージをダウンロードします。

    Install-Package OpenCV.Windows -ProjectName OpenCVBridge
    

    または、 opencv.org から OpenCV リリースをダウンロードし、プロジェクトのインクルード パスとライブラリ パスを手動で構成します。

  3. ブリッジ プロジェクトの pch.hで、OpenCV ヘッダーを追加します。

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

OpenCVHelper ランタイム クラスを作成する

SoftwareBitmapと OpenCV Matの間で変換するメソッドを提供するランタイム クラスを定義します。 IDL ファイル OpenCVHelper.idl を作成します。

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

変換を実装する

OpenCVHelper.cppで、SoftwareBitmapからMatへの変換を実装します。

#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

上記のコードでは、 IMemoryBufferByteAccess を使用して生のピクセル データにアクセスします。 入力および出力 SoftwareBitmap オブジェクトは、 Bgra8 ピクセル形式を使用する必要があります。 フレーム MediaFrameReader 別の形式を使用する場合は、最初に SoftwareBitmap.Convertで変換します。

C のコンポーネントを使用する#

C# WinUI 3 アプリで、 OpenCVBridge コンポーネントへのプロジェクト参照を追加します。 次に、フレーム処理コードから ProcessBitmap を呼び出します。

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

処理操作をさらに追加する

特定の操作用の追加のメソッドを使用して、 OpenCVHelper クラスを拡張できます。 IDL と実装を更新します。

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