初級チュートリアル: セマンティッククエリ

このチュートリアルでは、セマンティックバッファを確認し、画面上のオブジェクト(例: 草を含むピクセル、建物の有無)を調べる方法を紹介します。次の関数を使用して、簡単なミニゲームを作成します。

注釈

動画で使用しているモッキングシステムはARDK 1.3で更新されています。モックモードの変更点の詳細については、その他の モックモードの動画 をご参照ください。

準備

このチュートリアルでは、ARDKがインポートされ、Unityのシーンが動作していることを前提としています。ARDKパッケージの詳細なインポート方法については、 ARDKを使い始める のページを参照してください。

手順

  1. 新しいシーンを作成する。

    1. Asset ツリーに新しいフォルダを作成し、 BasicSemantics と名前を付けます。

    1. 作成したフォルダに新しいシーンを作成します。フォルダ内を右クリックし、Create(作成) > Scene(シーン) の順に選択し、 BasicSemanticsTutorial と名前を付けます。

  2. ARDKマネージャーを追加する。

    1. カメラオブジェクトに以下のマネージャーを追加します。

      • AR Session Manager

      • AR Camera Position Helper

      • AR Rendering Manager

      • AR Semantic Segmentation Manager

    2. 上記のすべてのマネージャーで、カメラ変数がシーンカメラに設定されていることを確認してください。

  3. カメラ設定を更新する。

    1. カメラの背景は黒に設定してください。

    2. ARDKのモックワールドを無視するために カリングマスク を設定します。

    ../../_images/semantics_step3.png
  4. Unityエディタでテストできるようにセマンティックモッキングをセットアップする。 Unityプロジェクトのテストをデバイス上ではなくUnityエディターで行う場合は、 モックモードでのプレイ を使用して、セマンティックチャンネルで環境をシミュレートします。

    1. ARDKのダウンロードページ からARDKのモック環境パッケージをダウンロードします。

    2. パッケージをUnityプロジェクトにインポートします。

    3. Lightship > ARDK > バーチャルスタジオ ウィンドウの順に移動してMockタブを開き、 Mock Scene(モックシーン) のドロップダウンから、 ParkPond プレハブを選択します。このモック環境には、さまざまなセマンティックチャンネルを使用できるように複数のオブジェクトが設定されています。Unityエディターで実行すると、このプレハブは自動的にシーンにインスタンス化されます。

  5. セマンティックバッファを取得するためのスクリプトをセットアップする。 セマンティックマネージャーを取り込み、セマンティックバッファが更新されたときにコールバックを受け取るスクリプトを作成します。このチュートリアルでは、簡潔にするために、カメラにスクリプトを追加します。スクリプトは、シーン内のどのゲームオブジェクトにも追加することができます。

    QuerySemanticsスクリプト:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
      
    using Niantic.ARDK.AR
    using Niantic.ARDK.Utilities;
    using Niantic.ARDK.Utilities.Input.Legacy;
    
    using Niantic.ARDK.AR.:ARSessionEventArgs;
    using Niantic.ARDK.AR.:Configuration;
      
    using Niantic.ARDK.AR.Awareness;
    using Niantic.ARDK.AR.Awareness.Semantics;
    
    using Niantic.ARDK.Extensions;
      
    public class QuerySemantics : MonoBehaviour
    {
        ISemanticBuffer _currentBuffer;
      
        public ARSemanticSegmentationManager _semanticManager;
        public Camera _camera;
      
        void Start()
        {
            //add a callback for catching the updated semantic buffer
            _semanticManager.SemanticBufferUpdated += OnSemanticsBufferUpdated;
        }
      
        private void OnSemanticsBufferUpdated(ContextAwarenessStreamUpdatedArgs<ISemanticBuffer> args)
        {
            //get the current buffer
            _currentBuffer = args.Sender.AwarenessBuffer;
        }
      
        // Update is called once per frame
        void Update()
        {
            }
    

    エディタ内のQuerySemanticsスクリプト:

    ../../_images/semantics_step5.png
  6. タッチ入力を取得し、セマンティックバッファを使用して、その時点で存在するオブジェクトを判断するためにアップデートを変更する。 このスクリプトは、タッチ入力イベントを受け取り、そのタッチ位置についてバッファにクエリを実行します。これにより、その情報がコンソールに出力されます。

        void Update()
        {
            if (PlatformAgnosticInput.touchCount <= 0) { return; }
    
            var touch = PlatformAgnosticInput.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                //list the channels that are available
                Debug.Log("Number of Channels available " + _semanticManager.SemanticBufferProcessor.ChannelCount);
                foreach (var c in _semanticManager.SemanticBufferProcessor.Channels)
                    Debug.Log(c);
      
      
                int x = (int)touch.position.x;
                int y = (int)touch.position.y;
      
                //return the indices
                int[] channelsInPixel = _semanticManager.SemanticBufferProcessor.GetChannelIndicesAt(x, y);
                  
                //print them to console
                foreach (var i in channelsInPixel)
                    Debug.Log(i);
      
                //return the names
                string[] channelsNamesInPixel = _semanticManager.SemanticBufferProcessor.GetChannelNamesAt(x, y);
      
                //print them to console
                foreach (var i in channelsNamesInPixel)
                    Debug.Log(i);
      
            }
        }
    }
    

7. Add additional calls to inspect the semantic buffer. The ISemanticBufferProvider contains a number of useful functions for interrogating the buffers. These functions allow you to check what is in any given pixel on the screen. The example code below shows how to use each function. As you click on the screen it will print a bunch of information to the console about the pixel you have selected and also check to see if it matches a channel you are looking for.

// Update is called once per frame
void Update()
{
    if (PlatformAgnosticInput.touchCount <= 0) { return; }

    var touch = PlatformAgnosticInput.GetTouch(0);
    if (touch.phase == TouchPhase.Began)
    {
        int x = (int)touch.position.x;
        int y = (int)touch.position.y;
        DebugLogSemanticsAt(x, y);
    }
}

//examples of all the functions you can use to interogate the procider/biffers
void DebugLogSemanticsAt(int x, int y)
{
    //list the channels that are available
    Debug.Log("Number of Channels available " + _semanticManager.SemanticBufferProcessor.ChannelCount);
    foreach (var c in _semanticManager.SemanticBufferProcessor.Channels)
        Debug.Log(c);

    Debug.Log("Chanel Indexes at this pixel");
    int[] channelsInPixel = _semanticManager.SemanticBufferProcessor.GetChannelIndicesAt(x, y);
    foreach (var i in channelsInPixel)
        Debug.Log(i);

    Debug.Log("Chanel Names at this pixel");
    string[] channelsNamesInPixel = _semanticManager.SemanticBufferProcessor.GetChannelNamesAt(x, y);
    foreach (var i in channelsNamesInPixel)
        Debug.Log(i);

    bool skyExistsAtWithIndex = _semanticManager.SemanticBufferProcessor.DoesChannelExistAt(x, y, 0);
    bool skyExistsAtWithName = _semanticManager.SemanticBufferProcessor.DoesChannelExistAt(x, y, "sky");
    Debug.Log("Does This pixel have sky using index: " + skyExistsAtWithIndex);
    Debug.Log("Does This pixel have sky using name: " + skyExistsAtWithName);

    //check if a channel exists anywhere on the screen.
    Debug.Log("Does the screen contain sky using index " + _currentBuffer.DoesChannelExist(0));
    Debug.Log("Does the screen contain grounf using index  " + _currentBuffer.DoesChannelExist(1));

    Debug.Log("Does the screen contain sky using name " + _currentBuffer.DoesChannelExist("sky"));
    Debug.Log("Does the screen contain grounf using name  " + _currentBuffer.DoesChannelExist("ground"));

    //if you want to do any masking yourself.
    UInt32 packedSemanticPixel = _semanticManager.SemanticBufferProcessor.GetSemantics(x, y);
    UInt32 maskFromIndex = _currentBuffer.GetChannelTextureMask(0);
    UInt32 maskFromString = _currentBuffer.GetChannelTextureMask("sky");

    Debug.Log("packedSemanticPixel " + packedSemanticPixel);
    Debug.Log("sky mask from index " + maskFromIndex);
    Debug.Log("sky mask from name " + maskFromString);

    //manually masking things.
    if ((packedSemanticPixel & maskFromIndex) > 0)
    {
        Debug.Log("manually using bit mask this pixel is sky");
    }

    int[] channelIndices = { 0, 1 };
    UInt32 maskForMultipleChannelsFromIndices = _currentBuffer.GetChannelTextureMask(channelIndices);
    Debug.Log("Combined bit mask for sky and ground using indices " + maskForMultipleChannelsFromIndices);

    string[] channelNames = { "ground", "sky" };
    UInt32 maskForMultipleChannelsFromName = _currentBuffer.GetChannelTextureMask(channelNames);
    Debug.Log("Combined bit mask for sky and ground using strings " + maskForMultipleChannelsFromName);

}
  1. 特定のアイテム(草、木、水、雲など)を探すようにユーザーに求める簡単なゲームを作成する。 Lightshipのゲームでは、ユーザーが限られた時間内に、ランダムに設定されたアイテムの種類を探します。まず、Debug.Logステートメントを使用せずに、画面上にテキストを追加してみましょう。

    1. テキストが画面上に表示されるように、テキストオブジェクトを追加します。シーンのルートを右クリックし、 Game Object(ゲームオブジェクト) > UI > Text(テキスト) の順に選択します。

      ../../_images/semantics_step8a.png
    2. これをパブリック変数としてスクリプトに渡します:

      	public class QuerySemantics : MonoBehaviour
      	{
      	    ISemanticBuffer _currentBuffer;
      	
      	    public ARSemanticSegmentationManager _semanticManager;
      	    public Camera _camera;
      	
      	    //pass in our text object
      	    public Text _text;
      
      		// ...
      	}
      
  2. ゲームロジックをスクリプトに追加する。 リストからアイテムをランダムに選んで探せるようにタイマーを追加します。X個のアイテムを見つけるという簡単な勝利条件を設定します。

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
      
    using Niantic.ARDK.AR
    using Niantic.ARDK.Utilities;
    using Niantic.ARDK.Utilities.Input.Legacy;
    
    using Niantic.ARDK.AR.:ARSessionEventArgs;
    using Niantic.ARDK.AR.:Configuration;
      
    using Niantic.ARDK.AR.Awareness;
    using Niantic.ARDK.AR.Awareness.Semantics;
    
    using Niantic.ARDK.Extensions;
    
    public class QuerySemantics : MonoBehaviour
    {
        ISemanticBuffer _currentBuffer;
    
        public ARSemanticSegmentationManager _semanticManager;
        public Camera _camera;
    
        public Text _text;
    
        void Start()
        {
            //add a callback for catching the updated semantic buffer
            _semanticManager.SemanticBufferUpdated += OnSemanticsBufferUpdated;
        }
    
        private void OnSemanticsBufferUpdated(ContextAwarenessStreamUpdatedArgs<ISemanticBuffer> args)
        {
            //get the current buffer
            _currentBuffer = args.Sender.AwarenessBuffer;
        }
    
        //our list of possible items to find.
        string[] items = {
            "grass",
            "water",
            "foliage"
        };
    
        //timer for how long the user has to find the current item
        float _findTimer = 0.0f;
        //cooldown timer for how long to wait before issuing another find item request
        float _waitTimer = 2.0f;
    
        //our score to track for our win condition
        int _score = 0;
      
        //the current item we are looking for
        string _thingToFind = "";
        //if we found the item this frame.
        bool _found = true;
        
        //function to pick a randon item to fetch from the list
        void PickRandomItemToFind()
        {
            int randCh = (int)UnityEngine.Random.Range(0.0f, 3.0f);
            _thingToFind = items[randCh];
            _findTimer = UnityEngine.Random.Range(5.0f, 10.0f);
            _found = false;
        }
    
        // Update is called once per frame
        void Update()
        {
            //our win condition you found 5 things
            if (_score > 5)
            {
                _text.text = "Now that I have all of the parts I can make your quest reward. +2 vorpal shoulderpads!";
                return;
            }
    
            //tick down our timers
            _findTimer -= Time.deltaTime;
            _waitTimer -= Time.deltaTime;
            
            //wait here inbetween quests for a bit
            if (_waitTimer > 0.0f)
                return;
    
            //the alloted time to find an item is expired
            if (_findTimer <= 0.0f)
            {
                //fail condition if we did not find it.
                if (_found == false)
                {
                    _text.text = "Quest failed";
    
                    _waitTimer = 2.0f;
                    _found = true;
                    return;
                }
                
                //otherwise pick a new thing to find
                PickRandomItemToFind();
                _text.text = "Hey there adventurer could you find me some " + _thingToFind;
            }
    
            //input functions
            if (PlatformAgnosticInput.touchCount <= 0) { return; }
            var touch = PlatformAgnosticInput.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                //get the current touch position
                int x = (int)touch.position.x;
                int y = (int)touch.position.y;
    
                //ask the semantic manager if the item we are looking for is in the selected pixel
                if (_semanticManager.SemanticBufferProcessor.DoesChannelExistAt(x, y, _thingToFind))
                {
                    //if so then trigger success for this request and reset timers
                    _text.text = "Thanks adventurer, this is just what i was looking for!";
                    _findTimer = 0.0f;
                    _waitTimer = 2.0f;
                    _score++;
                    _found = true;
                }
                else
                {
                    //if not look at what is in that pixel and give the user some feedback
                    string[] channelsNamesInPixel = _semanticManager.SemanticBufferProcessor.GetChannelNamesAt(x, y);
                    string found;
                    if (channelsNamesInPixel.Length > 0)
                        found = channelsNamesInPixel[0];
                    else
                        found = "thin air";
    
                    _text.text = "Nah thats " + found + ", i was after " + _thingToFind;
                }
            }
        }
    }
    
  3. Unityエディタでテストし、必要に応じてより自然なテスト環境を作成する。 テストがより面白くなるように、オブジェクトを「ParkPond」プレハブにさらに追加します。木や建物のためのボックスをいくつかセットアップして、地面や水などに平面を使うことができます。または、既存の環境をインポートして、アノテーション(注釈)を付けることもできます。

  4. デバイス上でテストする。 Unityでモッキングシステムを使用してロジックをテストしているため、スマートフォン上で実行しても予期せぬ動作は発生しないでしょう。プレイヤーはオブジェクトを探す際に環境内を移動する必要があるため、何かを見つけるまでの時間を長くする必要があるかもしれません。