HoloLensでMediaCaptureを使おう

投稿者: | 2017-08-19

HoloLensにはRGBカメラが搭載されており、以下のAPIを使ってアプリから使用することが可能です。

  • WebCamTexture
  • PhotoCapture
  • MediaCapture

WebCamTextureはUnityで提供されているAPIで、カメラ映した映像をテクスチャとして扱うことができます。これを使えばカメラプレビューをアプリ内に表示するといったことが可能です。
PhotoCaptureもUnityでて提供されているAPIで、こちらは写真を撮影してデバイス内に保存することができます。

一方、MediaCaptureはUWPにて提供されているAPIです。ですのでUnityエディタ上では直接使うことはできませんが、カメラ映像の取得、写真の撮影、動画の撮影はこれ一つで出来るうえ、露出やピントの調整など細やかにカメラを調整することも可能です。また、カメラで撮影した現実世界の映像とアプリ上で表示しているCGオブジェクトを合成した映像も作ることができる、すなわちMixed Reality Captureを独自に実装することも可能です。さらにそのうえ、マルチスレッド対応のasync/awaitが使える非同期メソッドも揃っていますので、パフォーマンス面でも優れています。

ただし、UWP系のAPIですのでUnityエディタ上では直接使用することはできず、プリプロセッサによる条件分岐による実装やUnity用のプラグインを用意してやる必要があります。また、取得した画像や映像をUnity用のテクスチャに変換するコードも書く必要があるので、Unityで提供されているAPIを使うよりも難易度は高くなります。

それでも出来ることはUnity APIよりもたくさんありますので、使い方を覚えて損はないと思います。まずははじめの一歩ということで、Microsoftが公開しているサンプルアプリを使って、UWPアプリとしてMediaCaptureを使う方法をまとめたいと思います。そして次回以降でUnityでMediaCaptureを扱う方法を紹介したいと思います。

題材とするアプリは以下のリポジトリにある「CameraStarterKit」です。

また、MicrosoftからUWPでのカメラを使うためのチュートリアルが用意されていますので、そちらも併せて確認することをおススメします。

Windows デベロッパーセンター -カメラ-

Capabilityの設定

MediaCaptureを使う場合、デバイス上のカメラとマイクを使用できるようにするためマニフェストファイルの編集が必要です。また、今回は撮影した写真や動画をカメラロールに保存するため、ピクチャライブラリにもチェックを入れます。

なお、アプリを動かすOS側もカメラとマイクが使えるよう設定する必要があります。「Win + i」→「プライバシー」から、マイクとカメラの使用を許可するよう設定します。

MediaCaptureを使う前の準備

MediaCaptureでカメラデバイスを扱う場合は、MediaCaptureクラスのインスタンスを作成したあとにカメラデバイスを初期化する必要があります。なお、プライバシー設定でアプリからカメラへのアクセスが禁止されている場合は初期化のメソッドであるInitializeAsyncを呼び出したときに例外が発生します。

private MediaCapture _mediaCapture;

private async Task InitializeCameraAsync()
{
    _mediaCapture = new MediaCapture();

    try {
        await _mediaCapture.InitializeAsync();
    } catch (UnauthorizedAccessException) {
        Debug.WriteLine("The app was denied access to the camera")
    }
}

HoloLensではRGBカメラが1台しか搭載されていないので意識する必要はありませんが、ノートPCやスマートフォンなど複数のカメラを搭載しているデバイスを使用している場合は、MediaCaptureが使うカメラを指定することも可能です。

private MediaCapture _mediaCapture;

private async Task InitializeCameraAsync()
{
    // デバイスに搭載されているカメラの情報をすべて取得する
    var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
    // フロントカメラについてのカメラ情報を取得する
    // フロントカメラが搭載されていない場合は取得したデバイスリストの先頭のものを取得する
    var deviceInformation = allVideoDevices.FirstOrDefault(
        deviceInfo => 
            deviceInfo.EnclosureLocation != null && 
            deviceInfo.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front
    );
    // MediaCaptureの設定を作成する
    var settings = new MediaCaptureInitializationSettings
    {
        // デバイスIDを使ってフロントカメラを指定
        VideoDeviceId = deviceInformation.Id,
    };
    _mediaCapture = new MediaCapture();
    try
    {
        // 設定を使ってMediaCaptureを初期化する
        await _mediaCapture.InitializeAsync(settings);
        _isInitialized = true;  // 初期化完了
    } catch (UnauthorizedAccessException)
    {
        Debug.WriteLine("The app was denied access to the camera");
    }

    if (_isInitialized)
    {
        // USBカメラ等、外部カメラを使っている場合
        if (cameraDevice.EnclosureLocation == null || cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown)
        {
            _externalCamera = true;
        }
        else
        {
            // デバイス内蔵カメラである場合
            _externalCamera = false;
            // フロントパネルカメラである場合は反転フラグを立てておく
            _mirroringPreview = (cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
        }
            // カメラプレビューの開始
            await StartPreviewAsync();
        }
    }
}

カメラプレビューを表示する

カメラプレビューを画面に表示するためには、表示用のUIを用意する必要があります。カメラデバイスからのストリームを表示するXAMLコントロールとしてCaptureElementがありますので、そちらを使用します。

<CaptureElement Name="PreviewControl" Stretch="Uniform" />

MediaCaptureにはカメラプレビュー用のメソッドとしてStartPreviewAsyncが用意されています。先ほど用意したCaptureElementとMediaCaptureと紐づけてメソッドをコールすればカメラプレビューが表示されます。

private async Task StartPreviewAsync()
{
    // カメラプレビュー中はディスプレイを常にアクティブにする
    _displayRequest.RequestActive();
    // CaptureElementとMediaCaptureをバインド
    PreviewControl.Source = _mediaCapture;
    // フロントパネルカメラである場合は左右反転させる
    PreviewControl.FlowDirection = _mirroringPreview ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
    // カメラプレビューの開始
    await _mediaCapture.StartPreviewAsync();
}

なお、フロントカメラを使用する場合はプレビューが左右反転してしまうので、CaptureElementのプロパティで反転表示させる必要があります。カメラの位置はDeviceInformationから取得できるので、MediaCaptureの初期化中に確認し、フラグをセットしています。

写真を撮影する

MediaCaptureでは写真を撮影するメソッドとして、ファイルシステムに直接保存するものとメモリ上に保存するものが用意されています。ここではメモリ上にいったん保存してからファイルシステムに保存する方法を紹介します。

private async Task TakePhotoAsync()
{
    // メモリへのストリームを取得
    using(var stream = new InMemoryRandomAccessStream()) {
        // 写真を撮影してストリームにキャプチャする、jpegで取得
        await _mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), stream);
        
        try
        {
            // カメラロールフォルダにファイルを作成する
            // 重複した場合は枝番をつけて作成
            var file = await KnownFolders.CameraRoll.CreateFileAsync("SimplePhoto.jpg", CreationCollisionOption.GenerateUniqueName);
            // 撮影した写真をファイルに保存する
            await ReencodeAndSavePhotoAsync(stream, file, photoOrientation);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception when taking a photo: " + ex.ToString());
        }
    }
}

ファイルに直接保存したい場合は、MediaCapture.CapturePhotoToStorageFileAsync()を使用します。

動画を撮影してファイルに保存する

写真と同様に、動画についてもファイルシステムに保存するものとメモリ上に保存するものが用意されています。ここではファイルに保存する方法を紹介します。

private async Task StartRecordingAsync()
{
    try
    {
        // ファイルの作成
        var videoFile = await KnownFolders.CameraRoll
            .CreateFileAsync("SimpleVideo.mp4", CreationCollisionOption.GenerateUniqueName);
        // エンコーディングプロファイルを作成する
        var encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);
        // エンコードと保存先を指定して録画を開始する
        await _mediaCapture.StartRecordToStorageFileAsync(encodingProfile, videoFile);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception when starting video recording: " + ex.ToString());
    }
}

停止もメソッドをコールするだけです。

private async Task StopRecordingAsync()
{
    await _mediaCapture.StopRecordAsync();      // 録画の停止
    Debug.WriteLine("Stopped recording!");
}

HoloLensにデプロイしてみる

こんな感じになりました。

まとめ

MediaCaptureの基本的な使い方をまとめました。冒頭でも書いた通りMediaCaptureはUWP系のAPIですのでUnityエディタから直接使うことはできません。また、MediaCaptureで撮影した映像を表示するには、キャプチャしたバイナリをテクスチャに変換するといった処理も必要となります。次回以降はそのあたりを説明したいと思います。