Azure Spatial Anchors V2の新機能 - Coarse Relocalizationの実装と利用

# Coarse Relocalizationを活用する

前回の投稿ではAzure Spatial Anchorsの基本的な仕組みとそのサンプルとしてHoloLens 2のチュートリアルの実装を用いてに紹介しました。

* [Azure Spatial Anchors V2とHoloLens ２のチュートリアルの解説](https://qiita.com/miyaura/items/2fff0f9fb037efb384d4)

今回のAzure Spatial AnchorsのV2から追加されているCoarse Relocalizationの実装方法について説明します。

## 開発環境
開発環境は以下の通りです。
* Windows 10 Pro
* Unity 2019.1.10f1
* Visual Studio 2019
* HoloLens 1
* Mixed Reality Toolkit v2.3.0
* Azure Spatial Anchors v2.1.1

## 公開している資料

* [4. Saving, retrieving using coarse relocalization](https://github.com/TakahiroMiyaura/AzureSpatialAnchorsSamplesOnMRTKV2/blob/master/Docs_Azure-Spatial-Anchors-turotials/mrlearning-asa-ch4.md)

もともとあったHoloLens 2のAzure Spatial Anchorsの手順の追加分として利用できる形にしています。ですので先に以下の作業が終わった状態のプロジェクトが必要になります。

* [1. Getting started with Azure Spatial Anchors](https://docs.microsoft.com/en-US/windows/mixed-reality/mrlearning-asa-ch1?WT.mc_id=MR-MVP-5003104)
* [2. Saving, retrieving, and sharing Azure Spatial Anchors](https://docs.microsoft.com/en-US/windows/mixed-reality/mrlearning-asa-ch2?WT.mc_id=MR-MVP-5003104)
* [3. Displaying Azure Spatial Anchor feedback](https://docs.microsoft.com/en-US/windows/mixed-reality/mrlearning-asa-ch3?WT.mc_id=MR-MVP-5003104)

今回はセンサー情報を利用するのですが、せっかくなのでBeaconも用意すると楽しいと思います。ということで、とりあえずBeaconを作るところも紹介します。
Wifiだけでも問題なく動作しますのでCoarse Relocalizationでの動作を確認したい方はWifiで試してください。
動かすと以下のようなイメージで動作します。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I created the sample &quot;Saving, retrieving using coarse relocalization&quot; of Azure Spatial Anchors.<a href="https://twitter.com/hashtag/HoloLens?src=hash&amp;ref_src=twsrc%5Etfw">#HoloLens</a> <a href="https://twitter.com/hashtag/MixedRealitytoolkit?src=hash&amp;ref_src=twsrc%5Etfw">#MixedRealitytoolkit</a> <a href="https://t.co/BsGYAGBMwU">pic.twitter.com/BsGYAGBMwU</a></p>&mdash; takabrz1@MRTKV2とAzure Spatial Anchorsと格闘中 (@takabrz1) <a href="https://twitter.com/takabrz1/status/1237391977659764736?ref_src=twsrc%5Etfw">March 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


## Coarse Relocalizationを実現するコードの解説

Coarse Relocalizationの機能を利用するためには登録時にセンサー情報をAnchorに埋めこむ必要があります。情報の埋め込み自体はAzure Spatial Anchors SDK内部で処理されますが、センサーを利用するための設定はあらかじめ実施しておく必要があります。
これは、Azure Spatial Anchorsとの接続時に開くセッションに設定します。


#### セッションの開始時の実装

先に紹介したサンプルのコードを抜粋して解説します。セッション開始の実装は基本的に従来の方法と変わりがないのですが、セッション開始時にいくつかの設定を行う必要があります。それぞれ実装を交えて解説したいと思います。
セッション開始はStartAzureSessionForCoarseRelocalizationメソッドを呼出します。そしてその中でCoarse Relocalizationに必要な処理を書きます。
その部分についてはSetNearDeviceメソッドとSetSessionForCoarseRelocalizationメソッドで記載しています。

```csharp:AnchorModuleScript.cs
public async void StartAzureSessionForCoarseRelocalization(){
    Debug.Log("\nAnchorModuleScript.StartAzureSession()");

    // Notify AnchorFeedbackScript
    OnStartASASession?.Invoke();
    Debug.Log("Starting Azure session... please wait...");

    if (cloudManager.Session == null) {
        Debug.Log("Creating Session...");
        // Creates a new session if one does not exist
        await cloudManager.CreateSessionAsync();
    }

    Debug.Log("Settings Near Devices...");
    SetNearDevice();

    Debug.Log("Start Session...");
    // Starts the session if not already started
    await cloudManager.StartSessionAsync();

    Debug.Log("Device Permission Request...");
    // Request permission for using sensors.
    SensorPermissionHelper.RequestSensorPermissions();

    Debug.Log("Coarse Relocalization...");
    SetSessionForCoarseRelocalization();

    Debug.Log("Azure session started successfully");
}
```

Coarse Relocalizationを利用するためにAnchorの設定に関するルールを規定するためのクラス[AnchorLocateCriteria]に必要な設定を行います。Coarse Relocalizationはセンサーデバイス近辺のAnchorを取得する機能のため、NearDeviceCriteriaを利用しルールを規定します。
NearDeviceCriteriaオブジェクトには以下のプロパティの設定を行います。

* DistanceInMeters : 探索半径(m)
* MaxResultCount : アンカーの最大同時取得数

アンカー取得時はセンサーの情報を用いて、DistanceInMetersで設定した範囲内のアンカーを最大MaxResultCount 取得するように動作します。

```csharp:AnchorModuleScript.cs(AnchorLocateCriteriaの設定)
private void SetNearDevice(){

    NearDeviceCriteria nearDeviceCriteria = new NearDeviceCriteria();
    nearDeviceCriteria.DistanceInMeters = coarseRelocalizationSettings.DistanceInMeters;
    nearDeviceCriteria.MaxResultCount = coarseRelocalizationSettings.MaxResultCount;

    anchorLocateCriteria.NearDevice = nearDeviceCriteria;
}
```

SetSessionForCoarseRelocalizationではセンサー情報のうちどの情報を利用するかを設定します。設定するプロパティは以下の4つです。Bluetoothの場合は利用するBeaconを指定するためにUUIDのリストを渡す必要があります。なお、基本的にHoloLensはハードウェアとしてGPSを持っていないので使える情報はWifiかBluetoothに絞られます。

* LocationProvider.Sensors.GeoLocationEnabled : GPS情報を利用するか
* LocationProvider.Sensors.WifiEnabled : Wifi情報を利用するか
* LocationProvider.Sensors.BluetoothEnabled : Bluetooth(iBeacon/EddyStone)を利用するか
* LocationProvider.Sensors.KnownBeaconProximityUuids : Bluetoothを利用する場合はビーコンのUUIDを指定します。

サンプルとして作ったコードはこの有効/無効をトグルボタンで表現しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/babeb54d-30a4-7f17-9d69-13a9b9270e0a.png)


```csharp:AnchorModuleScript.cs(利用するセンサーの有効/無効)
private void SetSessionForCoarseRelocalization(){
    // Set Sensor info
    LocationProvider = new PlatformLocationProvider();

    cloudManager.Session.LocationProvider = LocationProvider;
    LocationProvider.Sensors.GeoLocationEnabled =
        SensorInfoManager.Instance.IsToggle(SensorInfoManager.SensorType.GeoLocation) &&
        SensorPermissionHelper.HasGeoLocationPermission();

    LocationProvider.Sensors.WifiEnabled =
        SensorInfoManager.Instance.IsToggle(SensorInfoManager.SensorType.Wifi) &&
        SensorPermissionHelper.HasWifiPermission();

    LocationProvider.Sensors.BluetoothEnabled =
        SensorInfoManager.Instance.IsToggle(SensorInfoManager.SensorType.Bluetooth) &&
        SensorPermissionHelper.HasBluetoothPermission();

    LocationProvider.Sensors.KnownBeaconProximityUuids = coarseRelocalizationSettings.KnownBeaconProximityUuids;
}

```

#### アンカーの登録

アンカーの登録については従来の方法と変わりはないです。すでにセッションには必要な情報を利用する設定を行っているので、アンカー情報を生成して登録処理を呼出すだけになります。

```csharp:AnchorModuleScript.cs
public void FindAzureAnchorForCoarseRelocalization() {
    Debug.Log("\nAnchorModuleScript.FindAzureAnchor()");

    // Notify AnchorFeedbackScript
    OnFindASAAnchor?.Invoke();

    anchorLocateCriteria.Identifiers = new string[]{};
    Debug.Log($"Anchor locate criteria configured to search Anchors '{anchorLocateCriteria.NearDevice.DistanceInMeters} m from the device, Max Count:{anchorLocateCriteria.NearDevice.MaxResultCount}'");
    
    // Start watching for Anchors
    if ((cloudManager != null) && (cloudManager.Session != null)) {
        currentWatcher = cloudManager.Session.CreateWatcher(anchorLocateCriteria);
        Debug.Log("Watcher created");
        Debug.Log("Looking for Azure anchor... please wait...");
    }
    else {
        Debug.Log("Attempt to create watcher failed, no session exists");
        currentWatcher = null;
    }
}
```

#### アンカーの設置

アンカーの設置についても特に追加作業はありません。セションの設定としてセンサーを利用する形になっているので何もせずにアンカーの取得依頼を行います。

```csharp:AnchorModuleScript.cs
public void FindAzureAnchorForCoarseRelocalization(){
    Debug.Log("\nAnchorModuleScript.FindAzureAnchor()");

    // Notify AnchorFeedbackScript
    OnFindASAAnchor?.Invoke();

    anchorLocateCriteria.Identifiers = new string[]{};
    Debug.Log($"Anchor locate criteria configured to search Anchors '{anchorLocateCriteria.NearDevice.DistanceInMeters} m from the device, Max Count:{anchorLocateCriteria.NearDevice.MaxResultCount}'");
    
    // Start watching for Anchors
    if ((cloudManager != null) && (cloudManager.Session != null)){
        currentWatcher = cloudManager.Session.CreateWatcher(anchorLocateCriteria);
        Debug.Log("Watcher created");
        Debug.Log("Looking for Azure anchor... please wait...");
    }
    else {
        Debug.Log("Attempt to create watcher failed, no session exists");
        currentWatcher = null;
    }
}
```

#### セッションの切断

セッションの切断も特にセンサーを使うからと言って変わるところはありません。普通に切断処理を書きます。

```csharp:AnchorModuleScript.cs
public async void StopAzureSessionForCoarseRelocalization()
{
    Debug.Log("\nAnchorModuleScript.StopAzureSession()");

    // Notify AnchorFeedbackScript
    OnEndASASession?.Invoke();

    Debug.Log("Stopping Azure session... please wait...");

    // Stops any existing session
    cloudManager.StopSession();

    // Resets the current session if there is one, and waits for any active queries to be stopped
    await cloudManager.ResetSessionAsync();

    SetNearDevice();

    SetSessionForCoarseRelocalization();

    Debug.Log("Azure session stopped successfully");
}
```

### Coarse Relocalizationのアンカー設置について

Coarse Relocalizationを活用したアンカー設置もそうなのですが一度に複数のアンカーを取得＆設置する場合、最初の１つの位置が決まるとその時取得したすべてのアンカーは同時に設置される仕様のようです。まとめて取得する対象になっているアンカー同士はリンクしているためそれぞれの相対位置は把握しています。
この仕掛けがあるので、どれか１つ位置が決まるとそこを起点に残りのアンカーが相対的に配置されます。ただ、あまり離れているとデジタル上の相対位置を現実空間の座標はずれる可能性があるので、広い空間で利用する場合はWayFindingのように、見つけたアンカーに対して近くのアンカーを探すということを繰り返してアンカーの探索と設置を行う方がいいと思います。

## Beaconについて

Azure Spatial Anchorsで想定されているBeaconはiBeacon,EddyStoneでUUIDを受け取ることができるものであればさしあたり問題ありません。
市販のものでもいいので使いやすいものを利用しましょう。

今回は、手元に[Switch Science mbed TY51822r3](https://www.switch-science.com/catalog/2574/)があったので、
これでBeaconを作りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/00599d1c-2a24-e0a0-075b-4bf236e49800.png)

mbedが便利なところは開発環境がWebブラウザで完結できることと、バイナリをUSB経由でフォルダーにドラッグ＆ドロップするとデプロイが完了してアプリが動くところですかね。値段はそれなりにするのですが、１個あると色々実験に使えるので便利だと思います。

今回はiBeaconとしてこのmbedを動くようにしたいと思います。
まず、[mbed Compiler](https://os.mbed.com/handbook/mbed-Compiler)にアクセスします。
利用にはユーザ登録が必要です。

ログイン後右上のCompilerボタンを押下するとIDEに移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/b937d835-75d2-5956-17a1-8773dbf0ff24.png)

IDEが開いたら、利用するmbedのデバイスを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/24d0ed38-7457-960b-bfe3-ef2330aeb419.png)

メニューの「デバイス未選択」をクリックすると、次のようにデバイスのリストが表示されます。初回は何も登録していないのでリストにはない状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/cc60a871-8551-dd17-ad82-9a9086624fd9.png)

Add Boardを押すと別のウィンドウでデバイスの検索する画面が表示されます。そのなかで今回使う「TY51822r3」を検索します。
リストにあがったら、クリックして詳細を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/f6a50f01-5aa9-c44c-7193-fcc7855b8c48.png)

ページの中央あたり、右側にAdd your Mbed Compilerというボタンがあるのでそこをクリックすると追加できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/149b54a8-6f40-c333-61e7-9d3f678ba360.png)

追加できれば、先保護のデバイスリストから「TY51822r3」を選択して準備完了です。
正しく選択できると右上の「デバイス未選択」が「TY51822r3」に変わります。
次にiBeacon用の実装を行います。といってもサンプルがあるのでそれをそのままデプロイするだけで実はできてしまいます。
（実運用時はもう少し色々検討が必要だと思いますが）

メニューから「インポート」をクリックすると、インポートウィザードが表示されます。
プログラムタブを選択し検索条件に「BLE iBeacon」を入力して検索します。検索結果に表示されている「BLE_iBeacon」を選択して「インポート」ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/dbfae8ed-481e-7e9d-1994-811b7a75a389.png)

インポートするとワークスペースにBLE_iBeaconが追加されます。フォルダを展開してmain.cppを開いてください。
39行目付近にUUIDを設定している変数があるので、この値を任意の値に変更します。変更後は保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/bd85d0f4-93ab-d76a-821b-8a6ff7c5707b.png)

最後にコンパイルします。メニューのコンパイルをクリックするとコンパイルが始まります。無事にコンパイルが完了するとバイナリがダウンロードされるので、任意の場所に保存します。TY51822r3の場合PCにUSB接続を行って、ダウンロードしたバイナリをmbedのドライブにコピー&ペーストすれば自動的にデプロイと再起動が行われます。再起動後は先ほど指定したUUIDに従ったビーコンとして機能します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/0ad7c0a0-9382-4139-231c-131a769565ad.png)

## まとめ
２回にわたってAzure Spatial Anchorsの主要な機能について紹介しました。
Azure Spatial Anchorsはマルチユーザ、マルチデバイスで共有体験型のXRコンテンツ開発を行うための重要な仕組みの１つにはなると思いますので、興味があれば使ってみてください。