Open Source Computer Vision Library (OpenCV) と MediaFrameReader の使用

この記事では、さまざまな画像処理アルゴリズムを提供するネイティブ コード ライブラリである Open Source Computer Vision Library (OpenCV) を、複数のソースから同時にメディア フレームを読み取ることができる MediaFrameReader クラスと共に使用する方法について説明します。 この記事のコード例では、カラー センサーからフレームを取得し、OpenCV ライブラリを使用して各フレームをぼかして、処理された画像を XAML の Image コントロールに表示する方法を示します。

注意

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

この記事は、他の 2 つの記事の内容に基づいています。

  • MediaFrameReader を使ったメディア フレームの処理: この記事では、MediaFrameReader を使用して 1 つまたは複数のメディア フレーム ソースからフレームを取得する方法について詳細な情報を提供し、この記事のサンプル コードの多くについて詳しく説明しています。 具体的には、「MediaFrameReader を使ったメディア フレームの処理」では、ヘルパー クラス FrameRenderer のコード一覧を示します。このクラスは、XAML の Image 要素でのメディア フレームのプレゼンテーションを処理します。 この記事のサンプル コードでもこのヘルパー クラスを使用します。

  • OpenCV でのソフトウェア ビットマップの処理: この記事では、ネイティブ コードの Windows ランタイム コンポーネントである、OpenCVBridge を作成する手順を示します。これは、MediaFrameReader によって使用される SoftwareBitmap オブジェクトと、OpenCV ライブラリによって使用される Mat 型との変換に役立ちます。 この記事のサンプル コードでは、OpenCVBridgeコンポーネントを UWP アプリ ソリューションに追加する手順を実行していることを前提としています。

これらの記事に加えて、この記事で説明したシナリオの完全でエンド ツー エンドの実用的なサンプルを表示およびダウンロードするには、Windows ユニバーサル サンプル GitHub リポジトリにあるカメラ フレームと OpenCV のサンプルをご覧ください。

NuGet パッケージを使用して、OpenCV ライブラリを UWP アプリ プロジェクトに含めると、開発をすぐに開始することができますが、アプリをストアに送信する際に、これらのパッケージがアプリの認定プロセスに合格しない可能性があります。そのため、アプリを送信する前に、OpenCV ライブラリのソース コードをダウンロードして、自分でバイナリをビルドすることをお勧めします。 OpenCV を使った開発に関する情報については、https://opencv.org をご覧ください。

OpenCVHelper ネイティブ Windows ランタイム コンポーネントを実装する

OpenCV を使用したソフトウェア ビットマップの処理に関する記事の手順に従って、OpenCV ヘルパー Windows ランタイム コンポーネントを作成し、UWP アプリのソリューションにコンポーネント プロジェクトへの参照を追加します。

利用可能なフレーム ソース グループを検索する

最初に、メディア フレームを取得するメディア フレーム ソース グループを見つける必要があります。 MediaFrameSourceGroup.FindAllAsync を呼び出して、現在のデバイスで利用可能なソース グループの一覧を取得します。 アプリのシナリオに必要なセンサーの種類を指定するソース グループを選択します。 この例では、RGB カメラからのフレームを提供するソース グループのみが必要です。

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
var selectedGroupObjects = frameSourceGroups.Select(group =>
   new
   {
       sourceGroup = group,
       colorSourceInfo = group.SourceInfos.FirstOrDefault((sourceInfo) =>
       {
           // On Xbox/Kinect, omit the MediaStreamType and EnclosureLocation tests
           return sourceInfo.SourceKind == MediaFrameSourceKind.Color;

       })

   }).Where(t => t.colorSourceInfo != null)
   .FirstOrDefault();

MediaFrameSourceGroup selectedGroup = selectedGroupObjects?.sourceGroup;
MediaFrameSourceInfo colorSourceInfo = selectedGroupObjects?.colorSourceInfo;

if (selectedGroup == null)
{
    return;
}

MediaCapture オブジェクトを初期化する

次に、前の手順で MediaCaptureInitializationSettingsSourceGroup プロパティを設定することによって選択したフレーム ソース グループを使うように、MediaCapture オブジェクトを初期化する必要があります。

注意

OpenCV でのソフトウェア ビットマップの処理」で解説する OpenCVHelper コンポーネントで使用されている手法では、処理される画像データが GPU メモリではなく CPU メモリに格納されている必要があります。 そのため、MediaCaptureInitializationSettingsMemoryPreference フィールドに MemoryPreference.CPU を指定する必要があります。

MediaCapture オブジェクトが初期化された後、MediaCapture.FrameSources プロパティにアクセスして、RGB フレーム ソースへの参照を取得します。

mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

var colorFrameSource = mediaCapture.FrameSources[colorSourceInfo.Id];

MediaFrameReader を初期化する

次に、前の手順で取得した RGB フレーム ソースの MediaFrameReader を作成します。 適切なフレーム レートを維持するために、センサーの解像度よりも低い解像度のフレームを処理する場合があります。 この例では、省略可能な BitmapSize 引数を、MediaCapture.CreateFrameReaderAsync メソッドに対して指定して、フレーム リーダーによって提供されるフレームのサイズを 640 x 480 ピクセルに変更するよう要求しています。

フレーム リーダーを作成した後、FrameArrived イベントのハンドラーを登録します。 次に、新しい SoftwareBitmapSource オブジェクトを作成します。これは、FrameRenderer ヘルパー クラスが処理済みの画像を表示するために使用します。 次に、FrameRenderer のコンストラクターを呼び出します。 OpenCVBridge Windows ランタイム コンポーネントで定義されている OpenCVHelper クラスのインスタンスを初期化します。 このヘルパー クラスは、各フレームを処理するために FrameArrived ハンドラーで使用されます。 最後に、StartAsync を呼び出して、フレーム リーダーを開始します。

BitmapSize size = new BitmapSize() // Choose a lower resolution to make the image processing more performant
{
    Height = 480,
    Width = 640
};

mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32, size);
mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived_OpenCV;

imageElement.Source = new SoftwareBitmapSource();
_frameRenderer = new FrameRenderer(imageElement);

await mediaFrameReader.StartAsync();

FrameArrived イベントを処理する

フレーム リーダーからの新しいフレームが利用可能になると、FrameArrived イベントが発生します。 フレームが存在する場合は、TryAcquireLatestFrame を呼び出してフレームを取得します。 MediaFrameReference から SoftwareBitmap を取得します。 この例で使用されている CVHelper クラスでは、画像がプリマルチプライ済みアルファを含む BRGA8 ピクセル形式を使用している必要があります。 イベントに渡されたフレームが別の形式である場合は、SoftwareBitmap を正しい形式に変換します。 次に、ぼかし操作のターゲットとして使用される SoftwareBitmap を作成します。 一致する形式のビットマップを作成するために、ソース画像のプロパティがコンストラクターの引数として使用されます。 ヘルパー クラスの Blur メソッドを呼び出してフレームを処理します。 最後に、ぼかし操作の出力画像を PresentSoftwareBitmap メソッドに渡します。これは、画像が初期化された XAML Image コントロールに画像を表示する FrameRenderer ヘルパー クラスのメソッドです。

private void ColorFrameReader_FrameArrived_OpenCV(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{

    var mediaFrameReference = sender.TryAcquireLatestFrame();
    if (mediaFrameReference != null)
    {

        SoftwareBitmap openCVInputBitmap = null;
        var inputBitmap = mediaFrameReference.VideoMediaFrame?.SoftwareBitmap;
        if (inputBitmap != null)
        {
            //The XAML Image control can only display images in BRGA8 format with premultiplied or no alpha
            if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Bgra8
                && inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
            {
                openCVInputBitmap = SoftwareBitmap.Copy(inputBitmap);
            }
            else
            {
                openCVInputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
            }

            SoftwareBitmap openCVOutputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, openCVInputBitmap.PixelWidth, openCVInputBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);

            // operate on the image and render it
            openCVHelper.Blur(openCVInputBitmap, openCVOutputBitmap);
            _frameRenderer.PresentSoftwareBitmap(openCVOutputBitmap);

        }
    }
}