次の方法で共有


OpenCV を使用してビットマップを処理する

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

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

この記事で詳しく説明する OpenCVHelper コンポーネントで使用される手法では、処理するイメージ データが GPU メモリではなく CPU メモリに存在する必要があります。 そのため、 MediaCapture クラスなどのイメージのメモリ位置を要求できる API の場合は、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 プロジェクト アイコンを右クリックし、[Add-Class...] を選択します。[クラス の追加 ] ダイアログボックスで、[クラス名] フィールドに「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 "pch.h"
#include "OpenCVHelper.h"
#include "MemoryBuffer.h"

インクルード ディレクティブの後に、次の 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 を 受け取り、一連の変換を通じて、基になるデータ バッファーへのポインターを char 配列として取得できるピクセル データの COM インターフェイス表現を取得します。

まず、ピクセル データを含む BitmapBuffer を取得するには、 LockBuffer を呼び出し、OpenCV ライブラリがそのピクセル データを変更できるように、読み取り/書き込みバッファーを要求します。 CreateReference は、 IMemoryBufferReference オブジェクトを取得するために呼び出されます。 次に、IMemoryBufferByteAccess インターフェイスは IInspectable としてキャストされ、すべての Windows ランタイム クラスの基本インターフェイスであり、QueryInterface を呼び出して、ピクセル データ バッファーを char 配列として取得できるようにする IMemoryBufferByteAccess COM インターフェイスを取得します。 最後に、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 を受け取り、画像データ バッファーを表すために OpenCV が使用するマトリックス オブジェクトである Mat オブジェクトへの変換を試みます。 このメソッドは、上記で定義 GetPointerToPixelData メソッドを呼び出して、ピクセル データ バッファーの配列表現 文字を取得します。 これが成功すると、 Mat クラスのコンストラクターが呼び出され、ソース SoftwareBitmap オブジェクトから取得したピクセルの幅と高さが渡されます。

次の例では、CV_8UC4定数をピクセル形式として指定して、Matオブジェクトを作成します。 つまり、このメソッドに渡 SoftwareBitmap は、この例で使用するには、BitmapPixelFormat プロパティ値 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;
}

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

次のコード例では、ユーザーがイメージ ファイルを選択し、 BitmapDecoder を使用してイメージの SoftwareBitmap 表現を作成できます。 SoftwareBitmap の操作の詳細については、「ビットマップ イメージの作成、編集、保存」を参照してください。

この記事で既に説明したように、 OpenCVHelper クラスでは、提供されるすべての SoftwareBitmap イメージを事前乗算されたアルファ値を持つ BGRA8 ピクセル形式を使用してエンコードする必要があるため、イメージがまだこの形式でない場合は、 Convert を呼び出してイメージを予期された形式に変換します。

次に、ぼかし操作のターゲットとして使用する SoftwareBitmap が作成されます。 入力イメージのプロパティは、一致する形式のビットマップを作成するコンストラクターの引数として使用されます。

OpenCVHelper の新しいインスタンスが作成され、Blur メソッドが呼び出され、ソース ビットマップとターゲット ビットマップが渡されます。 最後に、出力イメージを XAML イメージ コントロールに割り当てるために SoftwareBitmapSource が作成されます。

このサンプル コードでは、既定のプロジェクト テンプレートに含まれる名前空間に加えて、次の名前空間の 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;