HoloLens 2 新機能 Scene understanding について調べてみた。

# Scene understanding とは ..
Scene understanding ( Runtime / SDK ) は Mixed Reality の開発者に対して __環境認識アプリケーションの開発を直観的に行うために設計された高いレベルの環境描画の構造 ( 仕組み )__ を提供します。Scene understanding は、既存の Mixed Reality ランタイムの処理内容を統合し、実現されます。 ( 構造化されていない高い精度を持つ環境マッピング [ Spatial mapping ] 、新たなAI駆動のランタイム ) これらの技術を統合することによって、Unity や ARKit / ARCore で使用したことがあるような 3次元の環境描画を実現します。Scene understanding のエントリーポイントは Scene obserber で、アプリケーション内で新たなシーンを計算する際に処理が開始されます。現在、この技術は3つの異なる関連オブジェクトカテゴリを生成することができます。 ( Wireframe, Mesh, Quads )

__実際にサンプルプロジェクトを動かしてみた動画__

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">HoloLens 2 新機能<br>Scene understanding を試してみた。<br><br>Spatial Mapping で取得した<br>環境メッシュデータを処理して、<br>壁、床、天井 etc に分類したり..<br><br>複雑な形状の環境メッシュでも<br>AIの推論機能を駆使して、<br>シンプルな Quad に変換してくれる。<a href="https://twitter.com/hashtag/HoloLens2?src=hash&amp;ref_src=twsrc%5Etfw">#HoloLens2</a> <a href="https://twitter.com/hashtag/MixedReality?src=hash&amp;ref_src=twsrc%5Etfw">#MixedReality</a><a href="https://twitter.com/hashtag/SpatialComputing?src=hash&amp;ref_src=twsrc%5Etfw">#SpatialComputing</a> <a href="https://t.co/ldygDqWldD">pic.twitter.com/ldygDqWldD</a></p>&mdash; Futo Horio (@Futo_Horio) <a href="https://twitter.com/Futo_Horio/status/1232940645062344705?ref_src=twsrc%5Etfw">February 27, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# Scene understanding を使えば..

__Mixed Reality デバイスから取得した非構造な環境センサーデータ ( Spatial mapping ) を直観的かつ容易な開発が実現できるよう抽象化された表現への変換を行ってくれます。__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/11be74df-1d8f-6da5-f482-3cff8de4e7ea.png" width="1200">

≒ Spatial mapping で取得できる環境メッシュデータを利用して、環境メッシュデータ内からオブジェクトを抽出 ( ``SceneObject`` ) 、そのオブジェクトが何であるかを分類 ( ``SceneObjectKind`` ) し、環境メッシュデータが粗く、複雑な箇所はAIを駆使して類推、シンプルな ``Plane`` として開発者が扱いやすいデータ構造とAPIを提供してくれる新機能。これらを実現すべく新たに機能追加されたのが、今回ご紹介する Scene understanding Runtime / SDK です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/d8161a4c-e32f-0fd9-f51a-d232b59c24b9.png" width="340">

上記のような空間マップのミニマップ化を実行することも可能です。

# Scene understanding : 対応デバイス
Scene understanding Runtime を活用するための Scene understanding SDK は NuGet から入手することが可能です。Scene understanding をサポートしているMRデバイスは、現状 __HoloLens 2__ のみです。

| 機能 | HoloLens ( 1st gen ) | HoloLens 2 | Immersive Headsets |
| :---: | :---: | :---: | :---: |
| Scene understanding | × | ✅ | × |

※ これらの機能はPC上で検証を行うことも可能です。
詳しくは「[Where can I get samples scenes?](https://docs.microsoft.com/ja-jp/windows/mixed-reality/scene-understanding-sdk#where-can-i-get-sample-scenes?WT.mc_id=MR-MVP-5003868)」をご確認ください。


# 「 Spatial mapping 」 vs 「 Scene understanding 」 
環境認識アプリケーションの核となるシナリオ ( CGの配置、オクルージョン、ナビゲーションなど ) の多くは、 __Spatial Mapping__ と __Scene Understanding__ のどちらか一方を活用すれば実現することができます。

では、なぜ同様の機能を持つ ( と思われる ) Spatial mapping と Scene understanding が切り分けられているのでしょうか？

「 Spatial mapping 」 と 「 Scene understanding 」 の大きな違いは、 <font color="red">__Spatial mapping データへのアクセス構造とシンプルさ <font color="black">( structure & simplicity )</font> に対する精度 <font color="black">( accuracy )</font> と 遅延 <font color="black">( latency )</font> のトレードオフ__</font> にあります。

## 空間マッピングデータへのアクセス、更新頻度について

それぞれの特徴と空間マッピングデータをどのように活用できるのか整理しました。

- __「 Spatial mapping API 」__ を使用する場合
 - 空間マッピングデータへのアクセス制限 : ``限定的な範囲``
 - ユーザーの周りわずかな範囲のキャッシュされた空間マッピングデータをアプリケーションから利用することが可能
 - ``SurfaceChanged`` イベントを通じて低遅延でのメッシュの更新が可能
- __「 Scene understanding SDK 」__ を使用する場合  
 - 空間マッピングデータへのアクセス制限 : ``無制限``
 - 空間マッピングデータのスナップショットも利用することが可能
 - Query で指定した範囲の空間マッピングデータを全て使用することが可能 ( デフォルト： 10m )


Spatial mapping ランタイムはどこに物体 ( Objects ) が存在しているのか？という空間マッピングデータを提供してくれますが、これらのオブジェクトが何 ( What ) であるのか？という情報に関しては提供してくれません。HoloLens 2 で提供される Scene understanding の能力は、この Spatial mapping で取得した空間マッピングデータにさらに処理を加え、意味のあるオブジェクト単位に分割することで空間マップの理解深めてくれます。Scene understanding ランタイムは、空間マップを取得し、空間マップ内に含まれるオブジェクトを分割 ( SceneObjects ) 、分類 ( SceneObjectKind ) し、マッピングデータだけでは穴が空いた状態になってしまう箇所に対して防水メッシュを作成することが可能です。( ※AIモデルで推論を実行 )

| Spatial mapping | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/a0637ea9-1683-6606-ff1c-8f04a967f5ff.png" width="800"><br/>[Spatial mapping](https://docs.microsoft.com/ja-jp/windows/mixed-reality/spatial-mapping#conceptual-overview?WT.mc_id=MR-MVP-5003868) より画像引用 |
| :---: | :---: |
| __Scene understanding__ | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/09d428ee-e644-f845-8e7f-a30a05369dd6.png" width="800"><br/>__[Scene understanding](https://docs.microsoft.com/ja-jp/windows/mixed-reality/scene-understanding#placement?WT.mc_id=MR-MVP-5003868) より画像引用__ | 

## SceneObjectKind

Scene Understanding は、空間マップ内のオブジェクトを以下7つのカテゴリーにに分類することができます。

| SceneObjectKind | 概要 |
| :--- | :--- |
| Background | __「 他のラベルに属さないオブジェクト 」__ を表すラベル。Unknown とは異なるので注意。 |
| Wall | 物理世界 ( 現実 ) における __「 壁 」__ 表すラベル |
| Floor | 物理世界 ( 現実 ) における __「 床 」__ を表すラベル |
| Ceiling | 物理世界 ( 現実 ) における __「 天井 」__ を表すラベル |
| Platform | ホログラムを配置できる __「 平らな表面 」__ を表すラベル（ ex. テーブル、カウンター etc..) |
| World | __「 ラベル付けが行われない幾何学データ 」__ を表すラベル ( 恐らくメッシュが複雑な箇所で表示される ) |
| Unknown | __「 まだ分類ラベルが作成されていないオブジェクト 」__ を表すラベル。Backgroundとは異なるので注意。現時点での分類カテゴリに属さない Something という位置づけ |

それでは早速、GitHub 上で公開されているサンプルを動かしてみたいと思います。

# 今回検証に使うサンプル
公式ドキュメントで紹介されている unitysample を使用します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/1ba1f286-292e-a2b2-3857-514f2ba7a20d.png" width="600">

- [sceneunderstanding-microsoft/unitysample](https://github.com/sceneunderstanding-microsoft/unitysample) ( GitHub )

# 動作環境

- Unity 2018.4.12.* or greater.
- Visual Studio 2017 or 2019 with Universal Windows Platform components.
- Windows SDK version 10.0.18362.0 or greater.

# サンプル導入手順

1. [sceneunderstanding-microsoft/unitysample](https://github.com/sceneunderstanding-microsoft/unitysample) からソースコードを ``git clone`` する
2. 対象シーン ( ``SceneUnderstandingSample.unity`` ) を開く
3. __【 Hierarchy 】 SceneUnderstandingManager >  【 Inspector 】 [Scene Understanding Data Provider (Script)] >  ✅ Run Device にチェックが入っているかどうか確認。__  
  ( チェックが入っていない場合は、チェックを入れる。)
4. ビルド実行
5. HoloLens 2 へのデプロイ

# SceneUnderstandingManager
サンプル __「SceneUnderstandingManager.unity」__ では、以下機能を試すことが可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/7e7c156f-2b07-d2e6-b1e4-cfc60508865d.png" width="600">


| ボイスコマンド (音声認識) | 処理内容 |
| :---: | :---: |
| Update ( / tap ) | 最新の空間情報に更新します。 | 
| scene objects on / off | シーンオブジェクトの表示/非表示を切り替え |
| scene objects quad | quad mode をオンにする |
| scene objects mesh | mesh mode をオンにする |
| scene objects wireframe | wireframe mode をオンにする | 
| platform on / off | 水平面の大きな境界線の表示/非表示
| background on / off | 背景にあるオブジェクトの表示/非表示 | 
| unknown on / off | unknown オブジェクトの表示/非表示 |
| inferred on / off | 環境データから推測されたオブジェクトの表示/非表示 |
| mesh on / off | ワールドメッシュの表示/非表示 |
| mesh coarse (粗い), mesh medium (中程度), mesh fine (高精細) | ワールドメッシュの LOD ( Level of detail : 細かさの粒度 ) の切り替え |
| minimap on / off | ミニマップの表示/非表示 |
| increase radius ( 広げる ) / decrease radius ( 狭める ) | 取得する環境データのカメラからの半径 |
| save data | Scene understanding データを保存する |
| help on / off | ヘルプの表示/非表示 | 

# SceneUnderstandingSample シーン : 処理内容について
今回、Scene understanding の挙動を理解するために使用したサンプルですが、実際どのようなロジックで動作しているのか？簡単にソースコードを追っかけてみたいと思います。基本的に必要なスクリプトは__「SceneUnderstandingManager」__ にアタッチされています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180432/d346dda1-0bb6-b05b-83cc-904c2caaa6c0.png" width="800">

まず Scene understanding の 初期化を行います。
SceneUnderstandingDataProvider.cs (104行目) にて、デバイスが Scene understanding 対応デバイスであるかどうか __「SceneUnderstanding.SceneObserver.IsSupported()」__ を確認し、__「await SceneUnderstanding.SceneObserver.RequestAccessAsync();」__ にて Scene understanding が必要とする情報へのアクセスを要求する必要します。

```SceneUnderstandingDataProvider.cs
        /// <summary>
        /// Initialization.
        /// </summary>
        private async void Start()
        {
            if (RunOnDevice)
            {
                if (Application.isEditor)
                {
                    Logger.LogWarning("SceneUnderstandingDataProvider.Start: Running in editor with the RunOnDevice mode set is not supported.");
                }

                if (SceneUnderstanding.SceneObserver.IsSupported())
                {
                    var access = await SceneUnderstanding.SceneObserver.RequestAccessAsync();
                    if (access != SceneObserverAccessStatus.Allowed)
                    {
                        Logger.LogError("SceneUnderstandingDataProvider.Start: Access to Scene Understanding has been denied. Reason: " + access);
                        return;
                    }

                    // At the beginning, retrieve only the observed scene object meshes.
                    RetrieveData(BoundingSphereRadiusInMeters, false, true, false, false, SceneUnderstanding.SceneMeshLevelOfDetail.Coarse);

                    // Then, spin off a background thread to continually retrieve SU data.
                    Task.Run(() => RetrieveDataContinuously());
                }
                else
                {
                    Logger.LogError("SceneUnderstandingDataProvider.Start: Scene Understanding not supported.");
                    return;
                }
            }
``` 

次に、RetrieveData関数内のtry-catch句にて、scene understanding のクエリを設定します。SceneUnderstandingDataProvider.cs (167行目) シーンオブジェクトの種類、ワールドメッシュの有無、メッシュの粗さなどを設定し、SceneUnderstandingDataProvider.cs (177行目)__「SceneUnderstanding.SceneObserver.ComputeSerializedAsync(querySettings, boundingSphereRadiusInMeters).GetAwaiter().GetResult();」__ にて新規シーンの計算を実行します。


```SceneUnderstandingDataProvider.cs
        /// <summary>
        /// Calls into the Scene Understanding APIs, to retrieve the latest scene as a byte array.
        /// </summary>
        /// <param name="enableQuads">When enabled, quad representation of scene objects is retrieved.</param>
        /// <param name="enableMeshes">When enabled, mesh representation of scene objects is retrieved.</param>
        /// <param name="enableInference">When enabled, both observed and inferred scene objects are retrieved. Otherwise, only observed scene objects are retrieved.</param>
        /// <param name="enableWorldMesh">When enabled, retrieves the world mesh.</param>
        /// <param name="lod">If world mesh is enabled, lod controls the resolution of the mesh returned.</param>
        private void RetrieveData(float boundingSphereRadiusInMeters, bool enableQuads, bool enableMeshes, bool enableInference, bool enableWorldMesh, SceneUnderstanding.SceneMeshLevelOfDetail lod)
        {
            Logger.Log("SceneUnderstandingDataProvider.RetrieveData: Started.");
            
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            try
            {
                SceneUnderstanding.SceneQuerySettings querySettings;
                querySettings.EnableSceneObjectQuads = enableQuads;
                querySettings.EnableSceneObjectMeshes = enableMeshes;
                querySettings.EnableOnlyObservedSceneObjects = !enableInference;
                querySettings.EnableWorldMesh = enableWorldMesh;
                querySettings.RequestedMeshLevelOfDetail = lod;

                // Ensure that the bounding radius is within the min/max range.
                boundingSphereRadiusInMeters = Mathf.Clamp(boundingSphereRadiusInMeters, _minBoundingSphereRadiusInMeters, _maxBoundingSphereRadiusInMeters);
                
                var serializedScene = SceneUnderstanding.SceneObserver.ComputeSerializedAsync(querySettings, boundingSphereRadiusInMeters).GetAwaiter().GetResult();
                lock(_latestSerializedSceneLock)
                {
                    _latestSerializedScene = new byte[serializedScene.Size];
                    serializedScene.GetData(_latestSerializedScene);
                    _latestSceneGuid = Guid.NewGuid();
                }
            }
            catch(Exception e)
            {
                Logger.LogException(e);
            }
```



# 参考文献

- [Microsoft.MixedReality.SceneUnderstanding.Samples - UnitySample](https://github.com/sceneunderstanding-microsoft/unitysample)
- [Scene understanding SDK overview](https://docs.microsoft.com/en-us/windows/mixed-reality/scene-understanding-sdk?WT.mc_id=MR-MVP-5003868)
- [Spatial mapping](https://docs.microsoft.com/ja-jp/windows/mixed-reality/spatial-mapping?WT.mc_id=MR-MVP-5003868)
- [HoloLens 2 & Scene Understanding](https://valoremreply.com/post/hololens2-sceneunderstanding/?utm_source=social%20media&utm_medium=Rene-Reshare&utm_campaign=hololens2)

# おわりに
やってみた感じ、CG配置するのとオクルージョンをする際に非常に役立つと思います。ただ、体感の精度は非常に良いとまではいかないので実際にCGを配置してみて、どれくらいのUXの向上が見込まれるのかはこれから試してみようと思います。機械翻訳ではなく、自分で英語のドキュメントを翻訳しながら書いたので少しドキュメントの内容とズレている箇所があるかもしれませんが、ご了承いただけますと幸いです。Twitter ( [@Futo_Horio](https://twitter.com/Futo_Horio) ) でも Mixed Reality 関連の情報を発信しているので、そちらもご覧ください。最後までお読みいただき、ありがとうございました。

# 備考
__Runtime ( ランタイム )__ ・・・ アプリケーションの開発・実行の両方の機能を備えたソフトウェアから、開発の機能を除き、実効の機能のみを取り出したプログラムのことである。

__NuGet__ ・・・ .NET Framework に対応する Free & Open source のパッケージマネージャーを指す。