めーぷるのおもちゃばこ

- アイドルになりたいエンジニア女子の制作日記 -

【Unity】AR FoundationでImage Tracking(マーカー認識)をする

はじめに

AR Foundationでのマーカー画像認識 (ImageTracking)の方法についてです🐼
※2020/08/08に修正と追記をしました。

f:id:maplesyrup-cs6:20200808093242g:plain
Image Tracking

  

サンプルプロジェクトはこちら↓
github.com

  

ARFoundationの導入

AR Foundationでマーカー認識をする方法🐼
AR Foundationの最初の導入方法はこちらをご覧ください!
www.wwwmaplesyrup-cs6.work

ここまでできたら、以下マーカーの使用方法です。

  

マーカーを使用する

ヒエラルキーでAR Session Originを選択し、インスペクタでAR Tracked Image Managerコンポーネントを付けます。

f:id:maplesyrup-cs6:20191018100049p:plain
AR Tracked Image Managerを付ける


続いて、Assets下にフォルダを一つ作ります。(名前はなんでも大丈夫です、今回はImageAssetsLibraryとします。)

f:id:maplesyrup-cs6:20191018100325p:plain
Assets下にフォルダを作る


そのフォルダの中で、右クリック>Create>XR>ReferenceImageLibraryを選択。

f:id:maplesyrup-cs6:20191018100507p:plain
ReferenceImageLibraryを追加した様子


同じフォルダ内に、マーカーにしたい画像を入れます。
マーカー画像を入れたら、ReferenceImageLibraryを選択して、インスペクタのAdd Imageからマーカーにしたい画像をセットします。
そしてSpecify Sizeにチェックを入れて、Physical Sizeを入力します。メートルなので、0.1x0.1にしました。マーカーのサイズに合わせて入力してください。
マーカーを2つや3つ登録したいときはさらにAddImageをクリックすることでマーカーを複数登録することができます。

f:id:maplesyrup-cs6:20191018105342p:plain
画像マーカーをセット


AR Session Originに付けたAR Tracked Image ManagerコンポーネントのReference Libraryに先ほどのReferenceImageLibraryをセットし、
その下のMax Number Of Moving Imagesで一度に認識したいマーカーの数を入れます。今回は1にします。
Tracked Image Prefabのところに、マーカー認識した時に出したいオブジェクトをセットします。

f:id:maplesyrup-cs6:20191018105717p:plain
諸々設定します。


これで書き出ししてみると...
出ました!(マーカーちっちゃくて見えてないんですが後ろにあります)

f:id:maplesyrup-cs6:20191018111423j:plain
出ました!


複数マーカーを登録してみたところ、これもちゃんと出せました。

f:id:maplesyrup-cs6:20191018111505p:plain
複数も出ました


   

複数マーカーの使用とマーカーごとにオブジェクトを変更する

Tracked Image Prefabのところにオブジェクトをセットすると、Reference Image Libraryに登録したマーカー全部に同じオブジェクトが出てしまいます。
オブジェクトごとにマーカーを出したい時もありますよね。
Tracked Image Prefabにセットしたオブジェクトを消しましょう。

ReferenceImageLibraryから使用したいマーカーを複数セットし、それぞれの名前を順番に0、1、2....としてください。
この時に、Specify Sizeの変更も忘れずに。(ここを設定し忘れているとビルド時にエラーになります。)
※サイズはメートルなので、1.0で1メートルです。 実際に使用するマーカーのサイズにあわせて入力します。

f:id:maplesyrup-cs6:20191112185525p:plain
マーカーをセットして名前を変更


そして、プロジェクトウィンドウで右クリックからCreate > C# Scriptで ImageRecognition クラスを作ります。
以下のスクリプトを貼り付けてください。
※こちらのスクリプト不具合があったので修正しました。以下のものは修正済みのコードです、後に説明を書き加えているので気になる方はみてください。

using UnityEngine.XR.ARFoundation;  //これを忘れず付け足す

public class ImageRecognition : MonoBehaviour
{
    [SerializeField] private MarkerModelSwitcher _markerModelSwitcher;
    [SerializeField] private ARTrackedImageManager _arTrackedImageManager;

    private int _presentMarkerNum = -1;

    private void Awake()
    {
        _arTrackedImageManager.trackedImagesChanged += OnImageChanged;
    }

    public void OnImageChanged (ARTrackedImagesChangedEventArgs args)
    {
        string _name;
        int _nameNum;

        foreach (var a in args.updated)
        {
            _name = a.referenceImage.name;
            _nameNum = int.Parse (_name);
            Vector3 markerPos = a.transform.position;
            Quaternion qua = a.transform.rotation;

            if(_markerModelSwitcher.ArObj != null) 
            {
                _markerModelSwitcher.ArObj.transform.position = markerPos;
                _markerModelSwitcher.ArObj.transform.rotation = qua;
            }

            if (a.trackingState == UnityEngine.XR.ARSubsystems.TrackingState.Tracking)
            {
                if (_nameNum == _presentMarkerNum) return;
                // Debug.Log ("MarkerName:::::::::::::::::::::::::::::::::" + _name);
                _markerModelSwitcher.SwitchingObject(_nameNum, markerPos, qua);
                _presentMarkerNum = _nameNum;
            }
        }
    }
}


次に、MarkerModelSwitherクラスを作って、以下を貼り付けます。

public class MarkerModelSwither : MonoBehaviour
    {
        [SerializeField]
        private ARObjectManager _arObjManager;

        private int _pageNum = 0;

        private GameObject _arObj;


        public void SwitchingObject(int pageNum, Vector3 pos, Quaternion rot)
        {
            if (_arObj != null) _arObj.SetActive(false);
            this._pageNum = pageNum;
            _arObj = _arObjManager.GetArObjByNum(_pageNum);
            _arObj.SetActive(true);
            _arObj.transform.position = pos;
            _arObj.transform.rotation = rot;
        }
    }


次にARObjectManagerクラスを作って、以下を貼り付けます。

public class ARObjectManager : MonoBehaviour
{
    [SerializeField]
    private GameObject[] _arObjs;

    private int _ObjNum = 0;

    private void Awake()
    {
        foreach (GameObject g in _arObjs)
        {
            g.SetActive(false);
        }
    }

    public GameObject GetArObjByNum(int _pageNum)
    {
        return _arObjs[_pageNum];
    }
}


3つできたら、Unityに戻ります。
ヒエラルキーのCreateから、Create Emptyで空のゲームオブジェクトを三つ作り、先ほどのスクリプトをそれぞれにセットします。

f:id:maplesyrup-cs6:20191113103012p:plain
空のゲームオブジェクト3つ作る


それぞれのインスペクタで、
ImageRecognitionにはMarkerModelSwitherとAR Session Originをドラッグ&ドロップでセット

f:id:maplesyrup-cs6:20191113105803p:plain
MarkerModelSwitherとAR Session Originをセット


MarkerModelSwitherにはObjManagerをドラッグ&ドロップでセット

f:id:maplesyrup-cs6:20191113114754p:plain
ObjManagerをセット


ObjManagerにはマーカーごとに出したいオブジェクトをセットします。最初にReferenceImageLibraryでマーカーの名前を数字にしました。
上記のスクリプトでその数字とElementの番号を対応させてあります。0番のマーカーを読み込んだら、Element0のオブジェクトを出す、といった具合です。
なので、Element0には0のマーカーにつけたいオブジェクトを、Element1には1のマーカーで出したいオブジェクトを...という感じでマーカーをセットしてください。

f:id:maplesyrup-cs6:20191113110035p:plain
オブジェクトをセット

  
これで書き出してみましょう!
ちなみに、この後書き出したとき筆者は「これで出るはずなのにオブジェクトが出ない!!!」と数分迷った挙句、オブジェクトがでかすぎて自分が中に入り込んでて気づかなかった、というコントを一人でしていました。
1にしていてでかすぎたので、ここではオブジェクトのサイズを0.1に設定しています。皆さんも気をつけてください。

  
こんな感じになるよ🐼🐼🐼マーカーごとに違うオブジェクト出せた!

f:id:maplesyrup-cs6:20200808093242g:plain
こんな感じ!


  

※2020/08/08追記  Androidでうまく切り替わらなかった話

  
ImageRecognition.csの中身を修正しました。
せっかくなのでこの部分のスクリプトの解説だけ書いておきます!
修正後のスクリプトはAndroidとiOS両方ちゃんと動きます🐼

//修正前--------------------------
if (_checkImageChangeNum != _nameNum)
{
     Vector3 pos = a.transform.position;
     Quaternion rot = a.transform.rotation;
     _markerModelSwitcher.SwitchingObject(_nameNum, pos, rot);
     _checkImageChangeNum = _nameNum;
}


//修正後--------------------------
if (a.trackingState == UnityEngine.XR.ARSubsystems.TrackingState.Tracking)
{
     if (_nameNum == _presentMarkerNum) return;
     // Debug.Log ("MarkerName:::::::::::::::::::::::::::::::::" + _name);
     _markerModelSwitcher.SwitchingObject(_nameNum, markerPos, qua);
     _presentMarkerNum = _nameNum;
}


修正前の状態だと、Androidでそもそもマーカーの読み込みがうまくいきませんでした。
1つ目のマーカーは読み込むのですが、2つ目のマーカーにかざしてもオブジェクトが切り替わらないという現象がおきました。iOSではうまくうごいていたのですが!
  
そして、以下のif文を付け足すことで解決しました。

if (a.trackingState == UnityEngine.XR.ARSubsystems.TrackingState.Tracking)

  
検証してみた結果を書いておきます!
TrackingStateTrackingLimitedNoneがあります。

Tracking:画像を追跡している状態
Limited:画像が"制限"とみなされている状況(??????)
None:画像が追跡されていない状況
 
 
このTrackingStatusを確認してみたところ、iOSでは画像に向けている時はTrackingと出続けているのですが、Androidでは1つ目のマーカーにかざしているときはTrackingと出続けているのですが、
2つめ以降のマーカーにかざした時、1フレごとに

TrackingStatus: Limited
MarkerName: 1      //1つ前にかざしていたマーカーの番号

TrackingStatus: Tracking
MarkerName: 2     //現在かざしているマーカーの番号

 
が繰り返されていました。

f:id:maplesyrup-cs6:20200808142528p:plain
現物

  
現在かざしているマーカーでトラッキングはされているのに、なぜかLimitedになったときに1つ前のマーカーが認識?されています。
このLimitedはUnityのAR Foundationドキュメント(をGoogle翻訳にかけたもの)には、

画像は追跡されていますが、追跡されていません。
画像が追跡ではなく制限と見なされる状況は、基礎となるARフレームワークによって異なります。 追跡が制限される原因となる可能性のある例は次のとおりです。画像がカメラに見えないようにぼかします。 画像は動画として追跡されません。 これは、たとえば、maxNumberOfMovingImagesを超えた場合に発生する可能性があります。

  
マーカーからカメラを背けているときは両方揃ってLimitedがでるのですが(ここでなんでNoneにならないのかもよくわからない)、他のところでLimitedのでかたがARKit XR Plugin と ARCore XR Pluginで違うらしく、AR Core Pluginではなぜか二つ目以降のマーカーをかざしたときに1フレごとにTrackingとLimitedで交互ででるようになっているっぽいです。
おそらく、一度でも認識した画像は以後、認識対象候補として常に存在していて、それが現在どういう状態か(かざしていたらTracking, かざしていないものはLimited)を教えてくれているということなのかなと思います。なので視界からはずれた一つ前のマーカーがLimitedになっているのかなと。iOSではLimitedがでなかったのは、「現在認識しているもの」のみに注目しているのかもしれないです。
  
で、LimitedとTrackingでそれぞれ認識しているマーカーが違うので2つ目以降のマーカーからオブジェクトが切り替わらなかったわけで、

if (a.trackingState == UnityEngine.XR.ARSubsystems.TrackingState.Tracking)

としてTrackingの瞬間をピックアップしてあげることでオブジェクトが出るようになったわけでした。

なんだよう。