本記事はAzure Spatial Anchorsの調査②の続きであり、最後の記事となります。
Azure Spatial Anchorsを使用して、複数のアンカーを配置したり、アンカーにリレーションをつけたり、アンカーの再現をしたりする検証アプリを作成しました。これらの実装を行う上でのポイントについて紹介していきます。
検証アプリの動画
実装のポイント
①セッションの開始
Azure Spatial Anchorsを使用するためには、セッションを生成しておく必要があります。
private async Task StartSessionAsync()
{
// セッションの作成
if (m_SpatialAnchorManager.Session == null)
{
await m_SpatialAnchorManager.CreateSessionAsync();
}
// セッションの開始
if (!m_SpatialAnchorManager.IsSessionStarted)
{
await m_SpatialAnchorManager.StartSessionAsync();
}
}
②アンカー情報の保存
アンカー情報を保存する上で、下記の2点がポイントとなります。
クラウドにアップロードしたCloudSpatialAnchor(アンカー情報)を再現するためには、CoundSpatialAnchor.Identifier(アンカー識別子)が必要となるため、サーバーorローカル問わずどこかに永続化しておく必要があります。
- CloudSpatialAnchorをクラウドにアップロード
- CoundSpatialAnchor.Identifierを永続化(今回はローカルに保存)
private async Task SaveAnchorAsync(GameObject anchorObject)
{
// CloudNativeAnchorコンポーネントを取得
CloudNativeAnchor cna = anchorObject.GetComponent<CloudNativeAnchor>();
// CloudAnchorのCloud Positionが未生成の場合、生成する
if (cna.CloudAnchor == null) await cna.NativeToCloud();
CloudSpatialAnchor cloudAnchor = cna.CloudAnchor;
// アンカーの有効期限を設定
cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(7);
// 現実空間の特徴点の収集が十分であるかの判定
while (!m_SpatialAnchorManager.IsReadyForCreate)
{
await Task.Delay(330);
float createProgress = m_SpatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
Debug.Log($"Move your device to capture more environment data: {createProgress:0%}");
}
try
{
// クラウドにアンカーを保存
await m_SpatialAnchorManager.CreateAnchorAsync(cloudAnchor);
// ローカルにアンカーIdentifierを保存
SaveAnchorIdentifier(cloudAnchor.Identifier);
Debug.Log($"Saved anchor. Idendifier is {cloudAnchor.Identifier}");
}
catch (Exception exception)
{
Debug.LogException(exception);
Debug.Log("Failed to save anchor " + exception.ToString());
}
}
private void SaveAnchorIdentifier(string identifier)
{
File.AppendAllText("任意のファイルパス", identifier + Environment.NewLine);
}
③保存したアンカーの再現
保存したアンカーを再現するためには下記のような手順が必要となります。
1. ウォッチャーを生成
AnchorLocateCriteria.Identifiersに永続化しておいたCoundSpatialAnchor.Identifierを設定し、ウォッチャーを生成します。
private CloudSpatialAnchorWatcher CreateWatcher()
{
if ((m_SpatialAnchorManager != null) && (m_SpatialAnchorManager.Session != null))
{
AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
// ローカルに永続化したアンカーIdentiferを取得
string[] identifiers = FileUtility.ReadFile();
// AnchorLocateCriteriaにアンカーIdentifierを設定
anchorLocateCriteria.Identifiers = identifiers;
// ウォッチャーを生成
return m_SpatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria);
}
else
{
Debug.Log("Wacher cannot be created.");
return null;
}
}
private string[] ReadFile()
{
if (!File.Exists("任意のファイルパス")) return null;
string readText = File.ReadAllText("任意のファイルパス");
readText = readText.Replace(Environment.NewLine, "\r");
readText = readText.Trim('\r');
var result = readText.Split('\r');
if (result.Length == 1 && result[0].Equals(string.Empty)) result = null;
return result;
}
2. SpatialAnchorManager.AnchorLocatedイベントでアンカーを再現
SpatialAnchorManager.AnchorLocatedイベントは、ウォッチャーに登録したアンカーが探知されたときor探知できなかったときに呼び出されます。 AnchorLocatedEventArgs.Statusを判定し、アンカーが探知できた(=LocateAnchorStatus.Located)ときにアンカーを再現します。このとき、Unityの機能を使用する際は、UnityDispatcher.InvokeOnAppThread()を使用し、Unityのメインスレッドで処理を行う必要があります。
private void Start()
{
// 初期化の際にイベントを登録
m_SpatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated;
}
private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
{
Debug.LogFormat("Anchor recognized as a possible anchor {0} {1}", args.Identifier, args.Status);
if (args.Status == LocateAnchorStatus.Located)
{
// 引数からCloudSpatialAnchorを取得
var cloudAnchor = args.Anchor;
// Unityのメインスレッドで処理
UnityDispatcher.InvokeOnAppThread(() =>
{
Pose anchorPose = Pose.identity;
// アンカーの位置を取得
anchorPose = cloudAnchor.GetPose();
// アンカーを生成
var anchorObject = CreateAnchorObject(anchorPose.position, anchorPose.rotation);
Debug.Log($"Reproduce anchor. Idendifier is {cloudAnchor.Identifier}");
});
}
}
private GameObject CreateAnchorObject(Vector3 worldPos, Quaternion worldRot)
{
// アンカー用のGameObjectを生成
GameObject anchorObject = Instantiate(m_AnchorPrefab.gameObject, worldPos, worldRot);
// CloudNativeAnchorコンポーネントをアタッチ
anchorObject.AddComponent<CloudNativeAnchor>();
// 色を設定
anchorObject.GetComponent<MeshRenderer>().material.color = Color.yellow;
// コライダーを非アクティブ化
anchorObject.GetComponent<BoxCollider>().enabled = false;
return anchorObject;
}
3. SpatialAnchorManager.LocateAnchorsCompletedイベントで完了通知
SpatialAnchorManager.LocateAnchorsCompletedイベントはウォッチャーに登録したすべてのアンカーが処理された(探知されたかどうかを問わず)後に呼び出されます。 このとき、Unityの機能を使用する際は、UnityDispatcher.InvokeOnAppThread()を使用し、Unityのメインスレッドで処理を行う必要があります。
private void Start()
{
// 初期化の際にイベントを登録
m_SpatialAnchorManager.LocateAnchorsCompleted += SpatialAnchorManager_LocateAnchorsCompleted;
}
private void SpatialAnchorManager_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
{
Debug.Log($"アンカーの検索が完了し、{_existingCloudAnchors.Count}個のアンカーが見つかりました。");
UnityDispatcher.InvokeOnAppThread(() =>
{
// do something...
});
}
まとめ
HoloLens 2をマーカーレスでの使用を目指して、Azure Spatial Anchorsの調査を3本の記事に渡り行ってきました。
今まではQRコードを使った位置合わせが主流であったと思いますが、Azure Spatial Anchorsを使うことで現実空間の特徴点をマーカー代わりに使用することが可能となります。
Azure Spatial Anchorsをはじめとした、色々な技術やサービスを組み合わせることで、ユーザーが位置合わせをあまり意識しなくてもMR体験ができる未来もそう遠くないでしょう。






