なんかいろいろしてみます

Apr 15, 2018 - 3 minute read - HoloLens

HoloLens RS4のResearch modeを使ってみる(Unity利用編)

前回,HoloLens RS4での新機能Research modeの紹介とサンプルを動作させてみました. 今回はResearch modeのC#での利用とUnityでの表示方法について紹介します.

概要

前回のHoloLens RS4のResearch modeを使ってみる(サンプル実行編)では公式のサンプルプロジェクトを実行して赤外線情報を取得しました. サンプルプロジェクトではC++での実行でしたがUnityで利用する場合にはC#での実装が便利です.

今回はResearch modeで取得できるセンサ情報をC#とUnityで利用できるようにしてみたいと思います. ついでに赤外線情報以外のセンサデータも取得してみます.

開発環境

  • 開発環境
    • Windows10 Insider Preview 17127
    • Windows10 SDK Insider Preview 17115
    • Visual Studio2017
    • Unity 2017.4.0f1
  • 実行環境
    • HoloLens RS4 Preview 17123

Research modeのC#での利用

始めはC#でのセンサ情報の取得と表示をUWPで行います. プロジェクトを用意したので以下のプロジェクトをダウンロードして開いてください.

https://github.com/akihiro0105/HoloLensResearchmodeDemo

プロジェクトをビルドしてHoloLensで実行することで様々なセンサデータを画像で確認することができます.

プロジェクト設定

HoloLens RS4のResearch modeは通常では利用できない特殊な機能となっています. そのためアプリ作成時の設定にも一部変更を加える必要があります. 追加内容の詳細は以下のサイトにて説明されています.

https://docs.microsoft.com/ja-jp/windows/uwp/packaging/app-capability-declarations

今回使用するプロジェクトでも内容の確認を行います.

  1. HoloLensResearchmodeDemoのPackage.appxmanifestを開いて機能のWebカメラにチェックボックスがONになっていることを確認してください.
  2. Package.appxmanifestを右クリックして「コードの表示」を選択します.
  3. 今回必要な「rescap」の項目を追加します.

    • Packageを以下のようにrescapを追加
123456
<Package 
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" 
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" 
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" 
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" 
IgnorableNamespaces="uap mp rescap">
  • Capabilitiesに <rescap:Capability Name="perceptionSensorsExperimental" />を追加
12345
<Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="perceptionSensorsExperimental" />
    <DeviceCapability Name="webcam" />
  </Capabilities>

データ取得

センサデータを取得するコードを確認していきます.

カメラの取得と初期設定です.

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243
  private MediaFrameReader mediaFrameReader = null;
  private async void SettingSensorData(int deviceNum,int cameraNum)
        {
            if (mediaFrameReader != null)
            {
                await mediaFrameReader.StopAsync();
                mediaFrameReader.FrameArrived -= FrameArrived;
                mediaFrameReader.Dispose();
                mediaFrameReader = null;
            }

            var mediaFrameSourceGroupList = await MediaFrameSourceGroup.FindAllAsync();
            var mediaFrameSourceGroup = mediaFrameSourceGroupList[deviceNum];
            var mediaFrameSourceInfo = mediaFrameSourceGroup.SourceInfos[cameraNum];
            MediaFrameSourceKind kind = mediaFrameSourceInfo.SourceKind;
            var mediaCapture = new MediaCapture();
            var settings = new MediaCaptureInitializationSettings()
            {
                SourceGroup = mediaFrameSourceGroup,
                SharingMode = MediaCaptureSharingMode.SharedReadOnly,
                StreamingCaptureMode = StreamingCaptureMode.Video,
                MemoryPreference = MediaCaptureMemoryPreference.Cpu,
            };
            try
            {
                await mediaCapture.InitializeAsync(settings);
                var mediaFrameSource = mediaCapture.FrameSources[mediaFrameSourceInfo.Id];
                if (kind == MediaFrameSourceKind.Color)
                {
                    mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(mediaFrameSource, MediaEncodingSubtypes.Argb32);
                }
                else
                {
                    mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(mediaFrameSource, mediaFrameSource.CurrentFormat.Subtype);
                }
                mediaFrameReader.FrameArrived += FrameArrived;
                await mediaFrameReader.StartAsync();
            }
            catch (Exception)
            {
                throw;
            }
        }

カメラの設定を取得するためにMediaFrameSourceGroupから取得しています. Research modeを有効にしたときにカメラのデバイス番号が変更され

  • 0 : センサデータ
  • 1 : RGBカメラデータ

の設定になっています. センサデータは基本的にMediaStreamType.VideoRecordで出力され,MediaFrameSourceKindはそれぞれのセンサによって異なっています.

  • 赤外線データ : MediaFrameSourceKind.Infrared : MediaEncodingSubtypes.L8
  • Depthデータ : MediaFrameSourceKind.Depth : MediaEncodingSubtypes.D16
  • 環境認識カメラ : MediaFrameSourceKind.Color : MediaEncodingSubtypes.Argb32

となっています. センサのデータ形式によって作成するMediaFrameReaderが異なるのですが,今回はデフォルトの設定を利用するようにしています.

設定後FrameArrivedで画像フレームの到着を待つようにしてMediaFrameReaderをスタートさせます.

 1 2 3 4 5 6 7 8 91011121314151617
  private void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
        {
            var reader = sender.TryAcquireLatestFrame();
            if (reader != null)
            {
                var videoMediaFrame = reader.VideoMediaFrame;
                var softwareBitmap = videoMediaFrame.SoftwareBitmap;
                softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
                var task = ImageView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
                  {
                      SoftwareBitmapSource softwareBitmapSource = new SoftwareBitmapSource();
                      await softwareBitmapSource.SetBitmapAsync(softwareBitmap);
                      ImageView.Source = softwareBitmapSource;
                  });
                reader.Dispose();
            }
        }

到着した画像データをFrameArrivedで受け取ったあとSoftwareBitmapSourceに変換してUWPのUIのImageに張り付けています. 正常に動作すれば以下の動画のように各センサデータが確認できます.

短距離Depthセンサ

長距離Depthセンサ

短距離赤外線センサ

長距離赤外線センサ

環境認識カメラ左外側

環境認識カメラ左手前

環境認識カメラ右手前

環境認識カメラ右外側

Research modeのUnityでの利用

C#での動作が確認できたのでUnityでも動作を確認していきます. Unity側のデモプロジェクトは以下にあります.

https://github.com/akihiro0105/HoloLensResearchmodeDemoWithUnity

Unityプロジェクトの設定

UWP形式でビルドします.

VisualStudioでの設定

Unity側でビルドしたプロジェクトはResearch modeで必要な.appxmanifestファイルの設定が行われていません. そのためC#プロジェクトと同様に.appxmanifestファイルに以下を追加します.

  • Packageを以下のようにrescapを追加
12345678
<Package 
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" 
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" 
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2" 
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" 
IgnorableNamespaces="uap uap2 mp rescap" 
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
>
  • Capabilitiesに <rescap:Capability Name="perceptionSensorsExperimental" />を追加
12345
<Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="perceptionSensorsExperimental" />
    <DeviceCapability Name="webcam" />
  </Capabilities>

実際の処理はSensorViewControl.cs内に記述されています.

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  private Texture2D tex = null;
    private byte[] bytes = null;

private async void InitSensor()
    {
        var mediaFrameSourceGroupList = await MediaFrameSourceGroup.FindAllAsync();
        var mediaFrameSourceGroup = mediaFrameSourceGroupList[0];
        var mediaFrameSourceInfo = mediaFrameSourceGroup.SourceInfos[0];
        var mediaCapture = new MediaCapture();
        var settings = new MediaCaptureInitializationSettings()
        {
            SourceGroup = mediaFrameSourceGroup,
            SharingMode = MediaCaptureSharingMode.SharedReadOnly,
            StreamingCaptureMode = StreamingCaptureMode.Video,
            MemoryPreference = MediaCaptureMemoryPreference.Cpu,
        };
        try
        {
            await mediaCapture.InitializeAsync(settings);
            var mediaFrameSource = mediaCapture.FrameSources[mediaFrameSourceInfo.Id];
            var mediaframereader = await mediaCapture.CreateFrameReaderAsync(mediaFrameSource, mediaFrameSource.CurrentFormat.Subtype);
            mediaframereader.FrameArrived += FrameArrived;
            await mediaframereader.StartAsync();
        }
        catch (Exception e)
        {
            UnityEngine.WSA.Application.InvokeOnAppThread(() => {
                Debug.Log(e);
            }, true);
        }
    }

    private void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
    {
        var mediaframereference = sender.TryAcquireLatestFrame();
        if (mediaframereference != null)
        {
            var videomediaframe = mediaframereference?.VideoMediaFrame;
            var softwarebitmap = videomediaframe?.SoftwareBitmap;
            if (softwarebitmap != null)
            {
                softwarebitmap = SoftwareBitmap.Convert(softwarebitmap, BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
                int w = softwarebitmap.PixelWidth;
                int h = softwarebitmap.PixelHeight;
                if (bytes==null)
                {
                    bytes = new byte[w * h * 4];
                }
                softwarebitmap.CopyToBuffer(bytes.AsBuffer());
                softwarebitmap.Dispose();
                UnityEngine.WSA.Application.InvokeOnAppThread(() => {
                    if (tex == null)
                    {
                        tex = new Texture2D(w, h, TextureFormat.RGBA32, false);
                        GetComponent<Renderer>().material.mainTexture = tex;
                    }
                    for (int i = 0; i < bytes.Length / 4; ++i)
                    {
                        byte b = bytes[i * 4];
                        bytes[i * 4 + 0] = 0;
                        bytes[i * 4 + 1] = 0;
                        bytes[i * 4 + 2] = 0;
                        bytes[i * 4 + 3] = 255;
                        if (b==0)
                        {
                            bytes[i * 4 + 0] = 128;
                        }
                        if (b==1)
                        {
                            bytes[i * 4 + 0] = 255;
                        }
                    }
                    tex.LoadRawTextureData(bytes);
                    tex.Apply();
                }, true);
            }
            mediaframereference.Dispose();
        }
    }

C#プロジェクト同様にカメラデータからセンサデータを取得,初期化を行っています. 今回はDepthデータを取得して表示しています.

取得されたDepth画像はUnityのTexture2Dで表示するためRGBA32に変換し,Texture2Dとフォーマットを合わせます. UWP側の処理スレッドとUnity側のスレッドが異なるため一旦Byte配列に画像を変換して,Unity側スレッドにてTexture2Dに保存しています. この時Byte配列を編集することで表示の見た目を変更しています.

Depthデータは0~255のbyte配列に格納されていますが,実際にDepthが取れている距離は0~15程度までです. 特に自分の手を表示している距離は0,1程度となっています. そのためこのプロジェクトではDepthデータが0と1の時に表示を行うようにしています.

実行すると以下の動画のように見ることができます.

まとめ

  • C#からResearch mode有効時にアクセスできるセンサデータを取得できた.
  • UnityプロジェクトからもセンサデータにアクセスしてDepthデータを表示することができた.
  • Research modeを利用したプロジェクトは「特殊な用途および制限された用途に関する機能」に分類されるためストアの申請時に制限がある場合があります.