本文へスキップ

セマンティックを照会し、セマンティック・チャンネルをハイライトする方法

ARアプリで再生しながら意味情報を見る

このHow-toガイドでは、以下の内容について説明します。

  • セマンティクスをクエリして、プレイヤーがタッチしたポイントに何が表示されているかを検出する。
  • プレイヤーが最後にタッチしたポイントに基づき、特定のセマンティックチャンネルをハイライトする;
  • セマンティック情報を照会するために利用可能なAPI。

前提条件

ARDK がインストールされたUnityプロジェクトと、セットアップされた基本的な AR シーンが必要です。 詳細については、ARDK 3のインストールと ARシーンの設定を参照してください。

ステップ

1. UI要素の追加

セマンティッククエリーを処理するスクリプトを実装する前に、ユーザーにセマンティック情報を表示するUI要素を準備する必要があります。 この例では、セマンティックチャンネル名を表示するテキストフィールドと、シェーダー出力を処理する RawImage を作成します。

UIエレメントを作成する:

  1. Hierarchyで、ARシーンを右クリックし、 UI にマウスオーバーし、 Raw Image を選択して、 RawImage をシーンに追加します。
  2. この手順を繰り返しますが、 Text-TextMeshPro を選択してテキスト・フィールドを追加します。 TMP Importerポップアップが表示されたら、Import TMP Essentialsをクリックして、テキストフィールドの追加を完了します。
  3. Hierarchyでテキストフィールドを選択し、 Inspectorで、その位置を (0, 0, 0) に設定します。
  4. InspectorRect Transform メニューで、左上隅の四角をクリックして、 Anchor Presets メニューを開きます。 Shiftを押しながらcenterオプションをクリックすると、テキストが画面の中央に固定されます。
Center Transformボタンを強調したUnity UIのイメージ
  1. RawImageを選択し、 Anchor Presets メニューを再度開きます。 Optionキー(WindowsではAltキー)を押しながら右下の正方形を選択し、 RawImage 、正しい位置に配置し、画面全体を覆うように引き伸ばす。
アンカー・プリセット」メニューの「ストレッチ」オプションを強調するUnity UIのイメージ

2. Semantic Segmentation Managerの追加

Semantic Segmentation Managerは、セマンティック・サブシステムへのアクセスを提供し、コードの他の部分がアクセスできるセマンティック予測を提供します。 (詳細については、 Semantics機能ページを参照)

シーンにSemantic Segmentation Managerを追加する:

  1. Hierarchy ウィンドウで右クリックし、 Create Empty を選択して、空の GameObject をシーンに追加します。 名前は Segmentation Manager
  2. 新しい GameObjectを選択し、 Inspector ウィンドウで、 Add Componentをクリックし、"AR Semantic Segmentation Manager" を検索して選択し、コンポーネントとして追加します。
セマンティクス・セグメンテーション・マネージャーを含むゲームオブジェクトの例。

3. Alignment Shaderの追加

意味情報が画面上で正しく配置されるようにするには、ディスプレイ・マトリクスを使い、カメラから返されたバッファを回転させる必要がある。 オーバーレイシェーダーを使って、 ARCameraManager frame update イベントからディスプレイのトランスフォームを取得し、それを使ってUVを正しいスクリーン空間にトランスフォームすることができます。 そして、セマンティック情報を先ほど作成した RawImage にレンダリングし、ユーザーに表示する。

シェーダーを作成する:

  1. シェーダーとマテリアルを作成します:
    1. Project ウィンドウで、 Assets ディレクトリを開きます。
    2. Assets ディレクトリで右クリックし、 Create にマウスオーバーし、 Shader メニューから Unlit Shader を選択します。 SemanticShaderと名前をつけます。
    3. この作業を繰り返しますが、 Create メニューから Material を選択し、新しいマテリアルを作成します。 SemanticMaterialと名付け、SemanticShaderを新しいMaterialにドラッグ&ドロップして関連付けます。
  2. シェーダーのコードを追加する:
    1. Assets ディレクトリから SemanticShader を選択し、 Inspector ウィンドウで、 Open をクリックしてシェーダーコードを編集します。
    2. デフォルトのシェーダーをアライメントシェーダーのコードに置き換える。
クリックしてアライメントシェーダーのコードを展開する
シェーダ "Unlit/SemanticShader"
{
プロパティ
{
_MainTex ("_MainTex", 2D) = "white" {}
_SemanticTex ("_SemanticTex", 2D) = "red" {} セマンティックシェーダーの色。
_Color ("_Color", Color) = (1,1,1,1)
}.
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}.
Blend SrcAlpha OneMinusSrcAlpha
// カリングや深度なし
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float3 texcoord :TEXCOORD1;
float4 vertex : SV_POSITION;

};

float4x4 _SemanticMat;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;

//画像を正しい回転とアスペクトに調整する必要があります。
o.texcoord = mul(_SemanticMat, float4(v.uv, 1.0f, 1.0f)).xyz;

return o;
}

sampler2D _MainTex;
sampler2D _SemanticTex;
fixed4 _Color;

fixed4 frag (v2f i) : SV_Target
{
//座標空間の変換
float2 semanticUV = float2(i.texcoord.x / i.texcoord.z, i.texcoord.y / i.texcoord.z);

float4 semanticCol = tex2D(_SemanticTex, semanticUV);
return float4(_Color.r,_Color.g,_Color.b,semanticCol.r*_Color.a);
}.
ENDCG
}.
}
}
  1. 材料のプロパティを設定します:
    1. シェーダーコードを置き換えると、マテリアルにプロパティが入力されます。 これらにアクセスするには、 Assets ディレクトリから SemanticMaterial を選択し、 Inspector ウィンドウを見てください。
    2. マテリアルのカラーとアルファを設定するには、 InspectorColor プロパティの右にあるカラースウォッチをクリックします。 アルファ値を 128 (およそ50%)に設定し、セマンティックカラーフィルターがその下の現実世界のオブジェクトを見るのに十分な半透明になるようにします。 好きな色を選んでください。
材料検査官

4. Query Scriptの作成

プレーヤーがスクリーンにタッチしたときにセマンティック情報を得るには、セマンティックセグメンテーションマネージャーに問い合わせ、プレーヤーがエリアにタッチしたときに情報を表示するスクリプトが必要です。

Query Scriptを作成する:

  1. スクリプトファイルを作成し、Segmentation Managerに追加する:
    1. Project ウィンドウで、 Assets ディレクトリを選択し、ウィンドウ内で右クリックし、 Createにマウスオーバーし、 C# Scriptを選択する。 新しいスクリプトを SemanticQueryingと名前をつけます。
    2. Hierarchyで、 Segmentation Manager GameObjectを選択し、 Inspector ウィンドウで、 Add Componentをクリックします。 script」を検索し、 New Script を選択し、 SemanticQuerying スクリプトを選択します。
  2. スクリプトにコードを追加する:
    1. Assets ディレクトリの SemanticQuerying スクリプトをダブルクリックしてテキストエディタで開き、次のスクリプトをコピーします。 (スクリプトの各部分の動作の詳細については、 Appendix を参照)。
クリックするとSemanticQueryingスクリプトが表示されます。
using Niantic.Lightship.AR.Semantics;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;

public class SemanticQuerying : MonoBehaviour
{
public ARCameraManager _cameraMan;
public ARSemanticSegmentationManager _semanticMan;

public TMP_Text _text;
public RawImage _image;
public Material _material;

private string _channel = "ground";

void OnEnable()
{
_cameraMan.frameReceived += OnCameraFrameUpdate;
}

private void OnDisable()
{
_cameraMan.frameReceived -= OnCameraFrameUpdate;
}

private void OnCameraFrameUpdate(ARCameraFrameEventArgs args)
{
if (!_semanticMan.subsystem.running)
{
return;
} //セマンティックテクスチャを取得する。

//get the semantic texture
Matrix4x4 mat = Matrix4x4.identity;
var texture = _semanticMan.GetSemanticChannelTexture(_channel, out mat);

if (texture)
{
//テクスチャはスクリーンにアライメントされる必要があるので、ディスプレイマトリクスを取得する
//そして、物事を回転/スケーリングするシェーダを使用する。
Matrix4x4 cameraMatrix = args.displayMatrix ?? Matrix4x4.identity;
_image.material = _material;
_image.material.SetTexture("_SemanticTex", texture);
_image.material.SetMatrix("_SemanticMat", mat);
}.
}

private float _timer = 0.0f;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
} //ユニティエディタとオンデバイスの違い

//Unity Editor vs On Device
if (Input.GetMouseButtonDown(0) || (Input.touches.Length > 0))
{
var pos = Input.mousePosition;

if (pos.x > 0 && pos.x < Screen.width)
{
if (pos.y > 0 && pos.y < Screen.height)
{
_timer += Time.deltaTime;
if (_timer > 0.05f)
{
var list = _semanticMan.GetChannelNamesAt((int)pos.x, (int)pos.y);

if (list.Count > 0)
{
_channel = list[0];
_text.text = _channel;
}
else
{
_text.text = "?";
}.

_timer = 0.0f;
}.
}
}
}
}
}

Script Variablesの割り当て

スクリプトを実行する前に、スクリプトとUIエレメントが互いに会話できるように、Unityで変数を割り当てる必要があります。

変数を割り当てる:

  1. Hierarchyで、 Segmentation Manager GameObjectを選択します。
  2. Inspector ウィンドウで、 SemanticQuerying スクリプトコンポーネント内の変数を、各項目をそれぞれのフィールドにドラッグアンドドロップして割り当てます:
    1. シーンの MainCamera から Camera Man フィールドへ;
    2. SegmentationManager オブジェクトを Segmentation Man フィールドに渡す;
    3. Text-TMP オブジェクトから Text フィールドへ;
    4. RawImage オブジェクトを Image フィールドに渡す;
    5. SemanticMaterial Assets ディレクトリから Material フィールドへ。
スクリプト変数を代入した後のセマンティッククエリースクリプト

ビルドとテスト

スクリプトを追加してコードを入力したら、今度はプレイバックデータセットを使ってテストしてみましょう。 録画したデータセットをUnityエディタで再生するための設定方法については、再生設定方法を参照してください。 Lightship GithubのPlaybackデータセットを使う場合、出力は次のようになるはずだ:

ARアプリでセマンティック情報を見る

また、デバイスに合わせてビルドし、実際の環境でテストすることもできるようになった:

ARアプリでセマンティック情報を見る

Appendix: クエリースクリプトの仕組み(How Does the Query Script Work?)

スクリーンには何が映っていますか?

クエリースクリプトのコアは、各フレームでユーザーがタッチしているか、クリックしているかをチェックする。 タッチ/クリックの位置とかかったフレーム数をチェックすることでタッチ/クリックが合法であることを確認した後、ユーザーが選択したポイントでセマンティックチャンネルを取得し、テキストボックスを使って結果を表示します。

チャンネル名のスニペットを表示するにはここをクリック
using Niantic.Lightship.AR.ARFoundation;
using TMPro;
using UnityEngine;

public class SemanticQuerying : MonoBehaviour
{
public ARSemanticSegmentationManager _semanticMan;
public TMP_Text _text;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
} //ユニティエディターとオンデバイスの違い

//Unity Editor vs On Device
if (Input.GetMouseButtonDown(0) || (Input.touches.Length > 0))
{
var pos = Input.mousePosition;

if (pos.x > 0 && pos.x < Screen.width)
{
if (pos.y > 0 && pos.y < Screen.height)
{
_timer += Time.deltaTime;
if (_timer > 0.05f)
{
var list = _semanticMan.GetChannelNamesAt((int)pos.x, (int)pos.y);

if (list.Count > 0)
{
_channel = list[0];
_text.text = _channel;
}
else
{
_text.text = "?";
}.

_timer = 0.0f;
}.
}
}
}
}
}

セマンティック・クラスのハイライト

GetSemanticChannelTextureを使って、見ているセマンティックチャンネルのテクスチャを取得し、結果を RawImage UI要素に出力することができます。 この関数は Matrix4x4 テクスチャを出力するので、 アライメント・シェーダの追加で説明したように、シェーダで変換します。

クリックするとテクスチャー出力のスニペットが表示されます
using Niantic.Lightship.AR.ARFoundation;
using TMPro;
using UnityEngine;

public class SemanticQuerying : MonoBehaviour
{
public ARSemanticSegmentationManager _semanticMan;
public TMP_Text _text;
public RawImage _image;

void Update()
{
if (!_semanticMan.subsystem.running)
{
return;
}

var list = _semanticMan.GetChannelNamesAt(Screen.width / 2, Screen.height / 2);
_text.text="";
foreach (var i in list)
_text.text += i;

//this will highlight the class it found
if (list.Count > 0)
{
//just show the first one.
_image.texture = _semanticMan.GetSemanticChannelTexture(list[0], out mat);
}
}
}