次の方法で共有


OpenCV でのビットマップの処理

この記事では、さまざまな種類の Windows ランタイム API で使用されている SoftwareBitmap クラスを、さまざまな画像処理アルゴリズムを提供するオープン ソースの、ネイティブ コード ライブラリである Open Source Computer Vision Library (OpenCV) で使用する方法について説明します。

この記事の例では、C# を使用して作成されるアプリを含め、UWP アプリから使用できるネイティブ コードの Windows ランタイム コンポーネントを作成する方法を段階的に説明していきます。 このヘルパー コンポーネントは、OpenCV の blur 画像処理関数を使用する 1 つのメソッド Blur を公開します。 このコンポーネントは、OpenCV ライブラリから直接使用できる、基になる画像データ バッファーへのポインターを取得するプライベート メソッドを実装します。これにより、ヘルパー コンポーネントを拡張して他の OpenCV 処理機能を実装することが容易になります。

注意

この記事で解説する OpenCVHelper コンポーネントで使用されている手法では、処理される画像データが GPU メモリではなく CPU メモリに格納されている必要があります。 したがって、画像のメモリ位置を要求できる API (MediaCapture クラスなど) では、CPU メモリを指定する必要があります。

OpenCV 相互運用のためのヘルパー Windows ランタイム コンポーネントを作成する

1. ソリューションに新しいネイティブ コードの Windows ランタイム コンポーネント プロジェクトを追加する

  1. ソリューション エクスプローラーで、ソリューションを右クリックして [追加] -> [新しいプロジェクト] の順に選択し、新しいプロジェクトを Visual Studio のソリューションに追加します。
  2. [Visual C++] カテゴリで、[Windows ランタイム コンポーネント (ユニバーサル Windows)] を選択します。 この例では、「OpenCVBridge」というプロジェクト名を入力し、[OK] をクリックします。
  3. [新しい Windows ユニバーサル プロジェクト] ダイアログ ボックスで、アプリのターゲットと最小 OS バージョンを選択して [OK] をクリックします。
  4. ソリューション エクスプローラーで自動生成されたファイル Class1.cpp を右クリックして [削除] を選択し、確認ダイアログが表示されたら [削除] を選択します。 次に Class1.h ヘッダー ファイルを削除します。
  5. OpenCVBridge プロジェクト アイコンを右クリックし、[追加] -> [クラス] の順に選択します。[クラスの追加] ダイアログ ボックスで、[クラス名] フィールドに「OpenCVHelper」と入力し、[OK] をクリックします。 コードは、後の手順で作成されるクラス ファイルに追加されます。

2. OpenCV NuGet パッケージをコンポーネント プロジェクトに追加する

  1. ソリューション エクスプローラーで、OpenCVBridge プロジェクト アイコンを右クリックし、[NuGet パッケージの管理] をクリックします。
  2. [NuGet パッケージ マネージャー] ダイアログが開いたら、[参照] タブを選択し、検索ボックスに「OpenCV.Win」と入力します。
  3. [OpenCV.Win.Core] を選択し、[インストール] をクリックします。 [プレビュー] ダイアログ ボックスで、[OK] をクリックします。
  4. 同じ手順で "OpenCV.Win.ImgProc" パッケージをインストールします。

注意

OpenCV.Win.Core および OpenCV.Win.ImgProc は定期的に更新されておらず、Store のコンプライアンス チェックに合格していないため、これらのパッケージは実験用にのみ使用してください。

3. OpenCVHelper クラスを実装する

OpenCVHelper.h ヘッダー ファイルに以下のコードを貼り付けます。 このコードには、インストールした Core パッケージと ImgProc パッケージの OpenCV ヘッダー ファイルが含まれており、以下の手順で示される 3 つのメソッドを宣言しています。

#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 ディレクティブを追加します。

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 を呼び出すことによって取得されます。LockBuffer は読み取り/書き込みバッファーを要求し、OpenCV ライブラリはそのピクセル データを変更できるようにします。 CreateReference が呼び出され、IMemoryBufferReference オブジェクトが取得されます。 次に、IMemoryBufferByteAccess インターフェイスが、すべての Windows ランタイム クラスの基本インターフェイスである IInspectable としてキャストされ、QueryInterface が呼び出されて IMemoryBufferByteAccess COM インターフェイスが取得されます。これにより、ピクセル データ バッファーを char 配列として取得できます。 最後に、IMemoryBufferByteAccess::GetBuffer を呼び出して char 配列を設定します。 このメソッドの変換手順のいずれかが失敗した場合、メソッドは 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 メソッドを呼び出して、ピクセル データ バッファーの char 配列表現を取得します。 これが成功した場合、Mat クラスのコンストラクターが呼び出され、ソース SoftwareBitmap オブジェクトから取得されたピクセルの幅と高さが渡されます。

注意

この例では、作成された Mat オブジェクトのピクセル形式として CV_8UC4 定数を指定します。 つまり、このメソッドに渡される SoftwareBitmapBitmapPixelFormat プロパティの値は、プリマルチプライ済みアルファを含む BGRA8 (この例で使用する 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;
}

最後に、この例のヘルパー クラス メソッドは、1 つの画像処理メソッド Blur を実装します。これは、上で定義した TryConvert メソッドを使用して、ぼかし操作のソース ビットマップとターゲット ビットマップを表す Mat オブジェクトを取得し、OpenCV ImgProc ライブラリの blur メソッドを呼び出します。 blur のその他のパラメーターで、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 コンポーネントが作成されたので、OpenCV の blur メソッドを使用して SoftwareBitmap を変更するシンプルな C# アプリを作成できます。 UWP アプリから Windows ランタイム コンポーネントにアクセスするには、最初にコンポーネントへの参照を追加する必要があります。 ソリューション エクスプローラーで、UWP アプリ プロジェクトの下にある [参照設定] ノードを右クリックし、[参照の追加] を選択します。[参照マネージャー] ダイアログ ボックスで、[プロジェクト] > [ソリューション] の順に選択します。 OpenCVBridge プロジェクトの横のボックスをオンにし、[OK] をクリックします。

次のコード例では、ユーザーは画像ファイルを選択し、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;