Nreal Light のイメージトラッキング(画像タグ)実装方法

日本でも 12月1日から一般販売が始まるとアナウンスされた AR グラス Nreal Light。

www.moguravr.com

大昔に注文した開発キットもまだ届いてない人がいっぱいいるのに……とか、8月に先行して一般販売が始まっているはずの韓国で買えたという話がまだ聞こえてこないのに……とか、いろいろ懸念はあるけど、楽しみに待つことにしよう。

気を取り直して。
Nreal Light では画像タグを扱うことができる。
あらかじめアプリケーションに登録された画像が目の前にあるとき、その画像の位置・向き・大きさが位置とクォターニオンのベクトルで得られるので、それらを使ってオブジェクトを表示すると、画像タグにオブジェクトが乗っているように見える。

f:id:n_shuyo:20201110220855j:plain

Nreal の SDK に画像タグを扱う Demo シーン ImageTracking が含まれており、これをビルドすれば簡単に試すことができる。
Nreal Developer の Image Tracking のドキュメントはそのサンプルの解説であり、イメージトラッキングのプログラミング方法は書かれていないため、ドキュメントを読んでも自分のアプリケーションに画像タグ機能を組み込むことはできない。
この記事では Nreal の Demo を参考に、画像タグ認識機能を組み込む方法を説明する。


まず最初に Nreal Developer の Image Tracking のドキュメント にしたがって、認識したい画像タグを格納したデータベースを静的に作成し、コンフィグファイルに登録する。
簡単に説明すると、 Unity プロジェクトのアセットの適当なディレクトリに認識させたい画像たちを置き(複数ディレクトリに分散させることはできない)、それらの画像を選択してから右クリック > Create > NRSDK > TrackingImageDatabase でデータベースファイルが生成される。
アセットの Assets/NRSDK にNRKernelSessionConfig というコンフィグファイルがあるので、その項目 Tracking Image Database に先ほど生成したデータベースファイルをセットすればOK。

f:id:n_shuyo:20201110164353p:plain

データベースの Inspector にて、Width という項目が変更できるようになっているが、これがどういう意味を持っているのかはわからなかった。読める範囲のソースからは参照されておらず、Native DLL はソース非公開……。
Quality は画像に対して計算される値で、平坦な画像だと Quality が小さくなるので、画質というより画像に含まれる特徴量の割合を表しているように思われる。
ドキュメントには Quality が 65 以上となる画像をお勧めとあるが、65 より小さい画像だと認識しにくいという体感はないので、まあまあ大丈夫かな。

作成したデータベースはコンフィグに設定する。Assets > NRSDK の中にある NRKernelSessionConfig を開いて、Inspector にて項目 Tracking Image Database に先ほど作ったデータベースを指定する。
また Image Tracking Mode が ENABLE になっていることも確認しておこう(動的に ENABLE/DISABLE を切り替えることもできる。デモのソースを参照)。


次に、認識した画像にオブジェクトを追随させるために以下のようなコンポーネントを用意する。区別しやすいように MyTrackableImage という名前にしておいた。

using UnityEngine;
using NRKernal;

public class MyTrackableImage : MonoBehaviour
{
    public NRTrackableImage Image;

    public void Update()
    {
        if (Image != null && Image.GetTrackingState() == TrackingState.Tracking)
        {
            Pose center = Image.GetCenterPose();
            transform.position = center.position;
            transform.rotation = center.rotation;
        }
    }
}

画像にかぶせるオブジェクトを Instantiate して、この MyTrackableImage コンポーネントを紐づければ、視界の中で画像が動いたときにオブジェクトが自動的に追随してくれるようになる。
そのあたりを司るコードが以下の MyTrackingImageController だ。適当な空 GameObject でも作って、このコンポーネントを Add しておく。

using System.Collections.Generic;
using UnityEngine;
using NRKernal;

public class MyTrackingImageController : MonoBehaviour
{
    public GameObject TrackingImageVisualizerPrefab;

    private Dictionary<int, MyTrackableImage> m_Visualizers = new Dictionary<int, MyTrackableImage>();
    private List<NRTrackableImage> m_TempTrackingImages = new List<NRTrackableImage>();

    public void Update()
    {
        if (NRFrame.SessionStatus != SessionState.Running) return;
        NRFrame.GetTrackables<NRTrackableImage>(m_TempTrackingImages, NRTrackableQueryFilter.New);
        foreach (NRTrackableImage image in m_TempTrackingImages)
        {
            int idx = image.GetDataBaseIndex();
            m_Visualizers.TryGetValue(idx, out MyTrackableImage visualizer);
            TrackingState state = image.GetTrackingState();
            if (state == TrackingState.Tracking && visualizer == null)
            {
                Pose pose = image.GetCenterPose();
                visualizer = Instantiate(TrackingImageVisualizerPrefab, pose.position, pose.rotation, transform)
                    .AddComponent<MyTrackableImage>();
                visualizer.Image = image;
                m_Visualizers.Add(idx, visualizer);
            }
            else if (state == TrackingState.Stopped && visualizer != null)
            {
                m_Visualizers.Remove(idx);
                Destroy(visualizer.gameObject);
            }
        }
    }
}

あとはこの MyTrackingImageController のインスペクタで、適当に作ってプレハブ化しておいたオブジェクトを Tracking Image Visualizer Prefab にセットすれば完成。
ビルド& Nreal にインストールすれば、データベースに登録しておいた画像を認識した場所に先ほどセットしたオブジェクトが表示される。

f:id:n_shuyo:20201110225604p:plain

このサンプルコードでは、すべての画像に対して Tracking Image Visualizer Prefab に設定された一種類のオブジェクトを表示するが、ピカチュウの絵にはピカチュウの 3D モデルを、マリオの絵にはマリオを、みたいに画像の種類ごとに異なるオブジェクトを表示したいこともあるだろう。
NRTrackableImage.GetDataBaseIndex() によって認識された画像のインデックス番号が得られるので、番号によって分岐することで実現できる。
ただし、データベースの番号は作り直すたびに変わる可能性があるため(おそらくファイル名順)、画像のファイル名をキーにしたいところ。
以下のコードで認識した画像のファイル名を得ることができる。NRTrackingImageDatabase のインスタンスは Start() あたりで取得して private 参照できるようにしておけばいいだろう。

NRSessionConfig config = NRSessionManager.Instance.NRSessionBehaviour.SessionConfig;
NRTrackingImageDatabase imgDB = config.TrackingImageDatabase;

// NRTrackableImage image;
int idx = image.GetDataBaseIndex();
string name = imgDB[idx].Name;


あとは Nreal Light の画像トラッキングについての特徴や要望(文句?)をつらつら書いてみる。

Nreal が認識できる画像は、ある程度大きく、かつ自分の正面に正対していないといけない。壁に貼ったポスターの正面に立って見るイメージだ。
ニンテンドー 3DS の AR カード(53mm×85mm、いわゆる名刺サイズ)や一般的なトレーディングカード(63mmx88mm)などの大きさだと顔の前 10cm くらいまで持ってこないと認識しないし、机に置いた状態のように斜めの角度も認識しにくい。
一度正面から見て認識させると、少し離して、少し斜めになってもある程度追随して認識し続けることはできるが、簡単に見失うのであまり期待しないほうがいい。
ポケモンカードで AR デュエルだ! みたいのを実現するには、やっぱり普通に机に置いてあるカードを認識できるようになってほしいところ。

また、別々の画像なら同時に複数枚認識させることはできるが、同じ画像についてはどれか1つだけしか認識されない。
これもトレーディングカードの類を認識させたい人には悲報だろう。

Nreal がタグとして認識する画像は、アプリケーションをビルドする前に画像タグ用の静的なデータベースに登録しておく必要があり、ビルド後に追加することはできない(少なくとも NRSDK 1.4.8 以前は)。
認識できる画像の種類を後から動的に追加したい要件はとても多くありそうな気もするが、NRTrackingImageDatabase に Add メソッドは生えているものの、#if UNITY_EDITOR でくるまれており、エディタ上で対話的に作成することしか想定されていない様子。

QR コードの認識と読み取りができたら嬉しかったのだが、それもできない*1

Nreal のイメージトラッキングを体験して、「オブジェクトが画像にちゃんと重なってないなー」と感じたなら、効き目が右目なのかもしれない。右目を閉じて左目だけで見てみよう。
Nreal のトラッキングの座標はどうも左目が基準になっているようで、実は左目ではぴったり重なっているが、右目ではものすごくずれている。
このズレは画像が小さいほど顕著で、カードサイズだと以下の写真くらいズレてしまう。

f:id:n_shuyo:20201110223232j:plain

画像が十分大きいとズレは気にならないレベルに収まるので、その点でもやはり「正面のポスター画像」を想定した設計になっているのだろうな、と思わされる。

*1:QR コードを静的な画像として登録してみたら、位置の認識だけはできたが……それじゃないよねw