はじめに

今回は、 AR 空間のアンカーをクラウドで共有できる Azure Spatial Anchors を触ってみました。

ARCore や ARKit にも Cloud を介してアンカー共有をする機能は存在しますが、 Azure Spatial Anchors を使えば、 Android 、 iOS 、 Hololens のプラットフォームの垣根を超えて、同じアンカーを複数端末で共有することができます。

※2019年9月18日現在では、まだプレビュー版なので、今後この記事の内容から変わる可能性があります。

開発環境

  • Unity 2019.1.1f1

  • Azure Spatial Anchors 1.1.1

  • ARCore
    • サンプルプロジェクトに元から入っていたので正確なバージョンはわかりませんが、正面カメラに対応しているので、おそらく1.7だと思います。

実装方法

Azure への登録と、 Azure からの取得の実装方法を見ていきましょう。

※ Azure 上での DB 作成などの手順は、詳しく解説してくれているサイトがあったので、今回は省略します。

※ここから下のコードでは、サンプルの最低限の箇所のみを抜き出していきます。

Anchor の作成・登録

private void Update()
{
#if UNITY_ANDROID
    long latestFrameTimeStamp = _nativeSession.FrameApi.GetTimestamp();
    bool newFrameToProcess = latestFrameTimeStamp > _lastFrameProcessedTimeStamp;
    if (newFrameToProcess)
    {
        _cloudSession.ProcessFrame(_nativeSession.FrameHandle);
        _lastFrameProcessedTimeStamp = latestFrameTimeStamp;
    }
#endif
#if UNITY_IOS
    UnityARSessionNativeInterface.ARFrameUpdatedEvent += UnityARSessionNativeInterface_ARFrameUpdatedEvent;
    void UnityARSessionNativeInterface_ARFrameUpdatedEvent(UnityARCamera camera)
    {
        _cloudSession.ProcessFrame(_arkitSession.GetNativeFramePtr());
    }
}
#endif

Hololens では必要ありませんが、 Android 、 iOS では、 ARCore 、 ARKit から CloudSpatialAnchorSession にフレームを提供してもらう必要があります。

private void Start()
{
    CreateNewCloudSession();
}
private void CreateNewCloudSession()
{
    cloudSpatialAnchorSession = new CloudSpatialAnchorSession();
    cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
    cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();
    cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
    cloudSpatialAnchorSession.Start();
}
private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
{
    SessionStatusIndicators[(int)SessionStatusIndicatorType.ReadyForCreate] = args.Status.ReadyForCreateProgress;
    SessionStatusIndicators[(int)SessionStatusIndicatorType.RecommendedForCreate] = args.Status.RecommendedForCreateProgress;
}

CloudSession を作成します。

cloudSpatialAnchorSession.SessionUpdatedRecommendedForCreateProgress 関数(または、 ReadyForCreateProgress 関数)を呼び出す処理(上のコードだと CloudSpatialAnchorSession_SessionUpdated)を登録します。

これで、 cloudSpatialAnchorSession.SessionUpdated のタイミングで、登録に必要な周辺情報(頂点情報?)の収集割合が更新されるようになります。

SpatialAnchorID

コード中の SpatialAnchorsAccountId は上の画像の矢印の部分、

SpatialAnchorKey

SpatialAnchorsAccountKey は上の画像の矢印の部分の文字列が格納されています。

public float ReadyForCreateProgress
{
    get
    {
        float result;
        NativeLibraryHelpers.CheckStatus(this.handle,
        NativeLibrary.sscsessionstatusgetreadyforcreate_progress(this.handle, out result));
        return result;
    }
}
public float RecommendedForCreateProgress
{
    get
    {
        float result;
        NativeLibraryHelpers.CheckStatus(this.handle,
        NativeLibrary.sscsessionstatusgetrecommendedforcreate_progress(this.handle, out result));
        return result;
    }
}

cloudSpatialAnchorSession.SessionUpdated に登録した ReadyForCreateProgress 関数と RecommendedForCreateProgress 関数の中身です。

2つの内のどちらか(RecommendedForCreateProgress の方が精度が高い?)の戻り値が1以上になると登録に必要な周辺情報が集まったことになります。

CloudSpatialAnchor cloudSpatialAnchor = new CloudSpatialAnchor();
cloudSpatialAnchor.LocalAnchor = spawnedObject.GetNativeAnchorPointer();
  • spawnObject AR 空間に生成された GameObject
    • ※ AR 空間に GameObject を生成する方法は省略します。
  • GetNativeAnchorPointer 関数
    • ARKitARCore と通して spawnedObjectPose からローカルアンカーを作成します。
cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(7);

アンカーの保存期限を設定。

↑のコードでは7日後まで保存されます。

この期限を過ぎると、アンカーが見つけられなくなります。

await cloudSpatialAnchorSession.CreateAnchorAsync(cloudSpatialAnchor);

ローカルアンカーを基に新たな Spatial Anchor が作成され cloudSpatialAnchor に格納されます。

HttpClient client = new HttpClient();
var response = await client.PostAsync(appServiceUrl, new StringContent(cloudSpatialAnchor.Identifier));
if (response.IsSuccessStatusCode)
{
    string responseBody = await response.Content.ReadAsStringAsync();
}

Azure 上に作成した AppService に SpatialAnchor の Key を送信する。

appServiceUrlhttps://[アプリケーション名].azurewebsites.net/api/anchor みたいになると思います。

※通信部分等の例外処理は適宜おこなってください。

Anchor の取得

long anchorNumber;
long.TryParse(inputText, out long anchorNumber);

取得したいアンカーの ID を Input Field などで入力し long 型に変換します。

AnchorIdsToLocate.Clear();
AnchorIdsToLocate.AddRange(anchorIds);
anchorLocateCriteria.Identifiers = AnchorIdsToLocate.ToArray();

AnchorLocateCriteria.Identifiers に取得したいアンカーの Key を設定する、

AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
CloudSpatialAnchorWatcher currentWatcher = cloudSpatialAnchorSession.CreateWatcher(anchorLocateCriteria);

AnchorLocateCriteria を生成します。

その後、生成した AnchorLocateCriteria から CloudSpatialAnchorWatcher を生成します。

これで CloudSpatialAnchorWatcherAnchorLocateCriteria.Identifiers に格納された Key に対応したアンカーの位置と向きの探索を開始します。

CloudSpatialAnchorWatcher.Stop() で探索を終了できるので、生成した CloudSpatialAnchorWatcher をメンバー変数に格納しておいた方が良いと思います。

cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;

アンカーの位置と向きを特定した時に呼ばれる cloudSpatialAnchorSession.AnchorLocated に関数を登録します。

private void CloudManager_OnAnchorLocated(object sender, AnchorLocatedEventArgs args)
{
    if (args.Status == LocateAnchorStatus.Located)
    {
        QueueOnUpdate(new Action(() =>
        {
            currentCloudAnchor = args.Anchor;
            Pose anchorPose = Pose.identity;
    #if UNITY_ANDROID || UNITY_IOS
            anchorPose = currentCloudAnchor.GetAnchorPose();
    #endif
            GameObject newGameObject = GameObject.Instantiate(AnchoredObjectPrefab, anchorPose.position, anchorPose.rotation);
            newGameObject.AddARAnchor();
        }));
    }
}

cloudSpatialAnchorSession.AnchorLocated に登録した関数の中身です。

取得したアンカーから、生成する GameObject の位置と向きを設定します。

動かしてみる

いろいろ写ってしまったのでカットしましたが、登録時の周辺情報の収集はそこそこ時間がかかります。

読み込み時の周辺情報の収集は、

  • アンカー作成時と同じ視点から読み込みをすると、あまり時間がかからない。(少ない周辺情報だけで読み込める)

  • アンカー作成時と違う視点から読み込みをすると、時間がかかる時がある。(より多くの周辺情報を必要とする)

ReadyForCreateProgress と RecommendedForCreateProgress の違い

ReadyForCreateProgressRecommendedForCreateProgress を比較してみました。

最大値は、

  • ReadyForCreateProgress : 2.5

  • RecommendedForCreateProgress : 2.0

で、同時に最大値に達しました。

ReadyForCreateProgress の方が早く1を返すようになるので、 RecommendedForCreateProgress の方が Spatial Anchor 作成に必要な周辺情報を多く要求し、精度が高いということだと思われます。

Anchor の削除

Anchor の削除機能の実装方法がわかりませんでした。

await cloudSpatialAnchorSession.DeleteAnchorAsync(currentCloudAnchor);

このコードを実行すると

  • Azure 上の Spatial Anchor AccountAnchor Deleted の命令は送られる

  • CosmosDB からは削除されない

  • アプリ側から Key のロードはできるが、そこから Anchor の特定ができなくなる(?)

おそらく

  • Key 自体は CosmosDB 上に保存される

  • Key に対応した Anchor の情報は別の場所に保存される

  • DeleteAnchorAsync 関数」と、「 cloudSpatialAnchor.Expiration に設定した期限は」別の場所に保存した Anchor の情報のみに影響を与えている

ということだと思います。

DeleteAnchorAsync 時に Spatial Anchor Account に命令が送られているので、 Spatial Anchor Account に保存されている?

おわりに

やっていることはシンプルなので、いろいろと遊ぶことができそうです。

まだまだ触れられていない機能もあるようなので、今後も積極的に使っていきたいです。

削除もなんとかしたいですね。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる