Vuforiaを使ってSharingの接続成功率をあげる

投稿者: | 2017-04-14

次回はSharingTestの記事を書くと約束したな。あれは嘘だ。

先日、Tokyp HoloLensハッカソンに参加し、カードゲームバトルをHoloLens上で再現するアプリを作りました(完成まではいたらず、Sharing部分がメインの実装となりましたが…)。そのときVuforiaを使ってカードの絵柄を認識しようとしたのですが、このマーカー認識を使って共有座標系を作ればSharingができるのではないかと思い試してみました。

予想通りうまくSharingすることができましたので、今回は予定を変更してその結果をまとめたいと思います。

Vuforiaを使ってSharing環境を作る

Vuforiaとは

マーカー式ARアプリ用のライブラリです。二次元のマーカー以外にもキューブや立体をマーカーにすることができます。Unityテクノロジーとタッグを組んでいることもありUnityとの親和性も高く、HoloLens上での動作もサポートされています。

二次元マーカーを基準に共有する座標系を作る

HoloLensにてVuforiaのマーカーを認識すると、ワールド座標系におけるマーカーのPositionとRotationを取得することができます。また、このマーカーを壁に貼り付けておくなどして実世界に固定しておけば、アプリ起動中はこのPositionとRotationは変化しません。ということは複数のHoloLensで壁に貼られたマーカーを認識すると、認識地点が実世界とアプリを結び付ける基準点になると考えられるので、認識地点のローカル座標系を共有してあげれば、Sharingによる3Dモデルの共有が可能となります。

それではShairng環境を構築していきます。まずはVuforiaを使用するために、無料のDevelopper登録が必要です。また、マーカーとして使用する画像の登録はWebから実施する必要があります。VuforiaライブラリのUnityへのインポート、マーカーへの登録は以下の記事が参考になると思います。

HoloLens vuforia ライセンスキーの取得からUnityでの実行

1. Unityを起動し、HoloToolKit-UnityとVuforiaのライブラリ、および登録したマーカーコンポーネントをプロジェクトにインポートします。なお、今回はマーカー画像として、サンプルでSD-Unityちゃんを使っていることもあり、Unityちゃんのロゴを使用させていただきました。

2. HoloToolkitからHoloLensCameraとSharingを、VuforiaからARCameraとImageTarger(マーカーコンポーネント)をシーンに追加します。

3. 追加したARCameraを選択し、[Open Vuforia configuration]からコンフィグ画面を開いて、以下のように設定を行います。

4. 追加したHoloLensCameraを選択し、Near Clipの値を修正します。

5. 再度ARCameraを選択し、インスペクタ上の[Central Anchor Point]にHoloLensCameraをドラッグアンドドロップします。

6. 基準点となるGameObjectをシーンに追加し、非アクティブ状態にします。ここでは名前をVuforiaSharingPointとしました。

7. ImageTargetを選択し、インスペクタ上の[Width]、[Height]を設定します。デフォルトではアスペクト比が固定されているので、どちらか一方を設定すればよいです。また、[Database]と[ImageTarget]もDevelopper Portalに登録した内容に従って設定します。

ここで注意してほしいのが、[Width]と[Height]は実世界上で使用するマーカーの幅/高さと一致させる、という点です。例えば紙に印刷したマーカーが縦20cm、横15cmの場合は、Unity上でもWidth:0.2 Height:0.15と設定する必要があります(Unity上では1m=1)。

Vuforiaは特徴点ベースでマーカを認識しており、特徴点の間隔等を使ってマーカーとカメラの距離を計算していると思われます。HoloLensのデプスセンサーは使用していないと思います。したがって、インスペクタ上の設定値を基準として距離を計算しているため、この値が実世界のマーカーのサイズとずれてしまうと、HoloLensとマーカー間の距離を正しく計算することができません。そうすると複数のHoloLensが共有する地点がズレてしまい、正しくSharingすることができなくなります。(通常のAR表示の場合でいうと、表示する3Dモデルがマーカーよりも奥や手前に表示されてしまいます。)

8. ImageTargetにアタッチされているスクリプト[DefaultTrackableEventHandler]を編集し、マーカーが認識したタイミングでSharingが開始されるようにする。

// 基準点のGameObjectはインスペクタから設定する
public GameObject SharingPoint;
private bool IsSharingStart = false;
/// <summary>
/// マーカーを認識したときにコールされる
/// </summary>
private void OnTrackingFound() {
    if (!IsSharingStart) {
    IsSharingStart = true;
    // マーカーのPositionとRotationを取得する
    Vector3 markerPosition = transform.position;
    Vector3 markerRotation = transform.rotation.eulerAngles;
    // 基準点の座標系としてマーカーの認識地点に設定する
    SharingPoint.transform.position = transform.position;
    SharingPoint.transform.Rotate(markerRotation);
    // 基準点を有効化する
    SharingPoint.SetActive(true);
    // VuforiaSharingPointにアプリケーションロジックを書いたスクリプトをアタッチしておけば、
    // 以降、Sharingが始まる
    }
}

Vuforiaではマーカーを検出する、ロストするといった状態変化を検出したタイミングでイベントを発行します。スクリプトはImageTargetにあらかじめ設定されているのでそちらを編集する、もしくはコピーして新しくアタッチするなどして処理を実装していきます。

このスクリプトでは、HoloLensがマーカーを認識すると、6.で設定したGameObjectのTransformをマーカー認識地点に上書きしたのちに有効化するようにしています。有効化が完了した後はこのGameObjectを基準として位置情報等を通信してやれば3Dモデル等の共有が可能となります。

前回のEasySharingと同じ内容のサンプルを用意しましたのでご利用ください。
https://github.com/dykarohora/VuforiaSharing

なお、少しでもイメージがつかめればと思い動画を用意しようと思ったのですが、Vuforiaを使う場合はマーカー認識用にカメラリソースが使われてしまうので、デバイスポータル等でストリーミングを受信することができず撮影を断念しました…。

アプリケーションロジック部分をちょっとだけ解説

アプリケーションロジックは基準点のGameObject(VuforiaSharingPoint)にスクリプトとして全てアタッチしています。ここではそのうちの一つであるHoloToolKitのRemoteHeadManagerについて少し説明します。

こちらはSharingしているリモートユーザの頭の部分にCubeが重畳表示されるものとなっています。詳細については次回のSharingTestの解説記事で別途説明しようと思いますが、ここではポイントだけ絞って解説しようと思います。

public class RemoteHeadManager : Singleton<RemoteHeadManager>
{
    // リモートユーザの情報をパッケージングした内部クラス
    public class RemoteHeadInfo
    {
        public long UserID;           // リモートユーザのユーザID
        public GameObject HeadObject; // リモートユーザのCameraの位置を描画するためのGameObject
    }
    
    // リモートユーザを管理するためのDictionary
    // ユーザIDをキーとして管理する
    private Dictionary<long, RemoteHeadInfo> remoteHeads = new Dictionary<long, RemoteHeadInfo>();

リモートユーザを表すGameObjectを管理するため、ユーザIDを識別子として紐づけを行いDictionaryで管理しています。続いて、自分のCamera(HoloLens)の位置をリモートユーザに配信する部分ですが、こちらはリアルタイムに送信するため、Update()にて処理します。

private void Update()
{
    // 自分のCamera(頭)の位置を取得
    Transform headTransform = Camera.main.transform;
    // CameraのPositionとRotationを基準点のローカル座標系に変換する
    // このスクリプトは基準点(VuforiaSharingPoint)にアタッチされているので
    // transformプロパティからメソッドをコールすればよい
    Vector3 headPosition = transform.InverseTransformPoint(headTransform.position);
    Quaternion headRotation = Quaternion.Inverse(transform.rotation) * headTransform.rotation;
    
    // リモートユーザにブロードキャストする(詳細は次回)
    CustomMessages.Instance.SendHeadTransform(headPosition, headRotation);
}

続いてリモートユーザからCameraの位置を受信したときの処理です。HoloToolkitでは、SharingServiceからメッセージを受信したときに、アプリ側でイベントが発火するようになっています。そのイベントをハンドリングして、リモートユーザのCameraの位置を更新します。

private void UpdateHeadTransform(NetworkInMessage msg)
{
    // 受信メッセージからユーザIDを取得
    long userID = msg.ReadInt64();
    // 受信メッセージからリモートユーザのCameraのPositionとRotationを取得
    Vector3 headPos = CustomMessages.Instance.ReadVector3(msg);
    Quaternion headRot = CustomMessages.Instance.ReadQuaternion(msg);
    // リモートユーザを管理する辞書から、データを取得し更新
    // リモートユーザを表すGameObjectも基準点(VuforiaSharingPoint)配下に存在するので、
    // そのままlocalPositionとlocalRotationにセットすればよい
    RemoteHeadInfo headInfo = GetRemoteHeadInfo(userID);
    headInfo.HeadObject.transform.localPosition = headPos;
    headInfo.HeadObject.transform.localRotation = headRot;
}

慣れてしまえばアプリケーションロジック部分も割と簡単に書けると思います。

まとめ

Vuforiaを使ってSharing環境を構築する方法をまとめました。Sharingを実現する上で重要なのは、デバイス間で座標系をいかに共有するか、だと思っています。アンカーの共有はその1つの手段であって本質ではないと思います。前回や今回のサンプルでも共有地点にアンカーを取り付けていますが、これはSharing環境を構築するためではなく、共有地点が実空間とずれないようにするといった補助的な効果を期待しているため実施しているだけです。

前回の方法(EasySharing)と今回の方法どちらが好みかは人によると思いますが、私は今回の方法が気に入っています。というのも今回の方法を使えばSharingを開始するタイミングを制御できるので、よりリッチな演出が可能になると思うからです。例えばマーカーを認識しているときはガンダムUCのコンソールのようなサークル形状のUIに「Are you ready?」とかを表示させておいて、その状態で「Ordinal Scale Start Up!!」と発話するとかっこいい感じの演出が入ってSharing開始とか、すごくテンション上がりませんか?誰か作って私に下さい。