見出し画像

Niantic Lightship ARDK セマンティック セグメンテーションを使ってタップした所から空、地面、建物、水面などの情報を取得する方法

おさらい

Niantic Lightship ARDK や セマンティック セグメンテーションについて、以下の記事でまとめております。

今回は...

セマンティック セグメンテーションの機能を使い、タップした箇所から空、地面、人工地盤、水、建物、葉(木なども含む)芝生を検出するアプリケーションを作成します。


1. Niantic Lightship ARDK ダウンロード

以下から入手してください。今回、使用しているバージョンはv1.1.0になります。入手する際、Niantic Lightshipのアカウント登録が必要です。

2. Niantic Lightship ARDK インポート

UnityEditor のメニューにあるAssets → Import Package → Custom Package...を選び、ダウンロードした ardk-1.1.0.unitypackage をインポートします。

3. 新規でSceneを作成とMain Cameraの削除

新規でSceneを作成後、Main Cameraを削除します。

4. ARSceneManagerをヒエラルキーに追加

画像1

5. ARSceneManagerをUnpack Prefab Completely

画像2

6. ARDepth Manager と ARSemanticSegmentationManager の追加

画像3

ARSceneManagerの子コンポーネント、ARSceneCameraARDepthManager ARSemanticSegmentationManager のスクリプトを追加。

7.  Semantic Segmentationの情報を表示するUIの作成

タップした場所から検出されたSemantic Segmentationの情報を表示するためのUIを作成します。

Canvas

スクリーンショット 2022-01-24 17.49.58

Text

スクリーンショット 2022-01-24 17.51.15

CanvasとTextを設置します。(Textのタップイベントを無効化にするため、Raycast TargetのチェックボックスをOFFにしています。)

Image 

スクリーンショット 2022-01-24 17.52.09

タップした場所を表示するImageも設置します。(Imageのタップイベントを無効化にするため、Raycast TargetのチェックボックスをOFFにしています。)

Dropdown

スクリーンショット 2022-01-24 17.53.07

スクリーンショット 2022-01-24 17.54.29

今回、タップした場所からSemantic Segmentationの情報を取得する処理ロジックを2つ使用して検証します。

1つ目はSemanticBufferProcessor.GetChannelNamesAt(x, y)です。該当の位置から検出された情報(空や地面など)を文字列による配列で返却されます。

2つ目はSemanticBufferProcessor.DoesChannelExistAt(x, y, channelName)です。該当の位置から検出された情報(空や地面など)が第3引数のチャンネル名(skyやgroundなど)に合致しているか否かを真偽値として返却されます。

上記のいずれかの検出方法をDropdownで切り替えるようにします。

8.  Semantic Segmentationの検出処理

スクリーンショット 2022-01-24 18.17.12

Semantic Segmentationの検出処理を実装します。Semantic SegmentationというGame Objectを作成後、Semantic Segmentationというスクリプトを作成し、コンポーネントとして追加します。(スクリプトは以下。SerializeFieldに設定するUIやObjectは上の画像を参考にしてください。)

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using Niantic.ARDK.Extensions;
using Niantic.ARDK.Utilities;

public class SemanticSegmentation : MonoBehaviour
{
   [SerializeField]
   private ARSemanticSegmentationManager _semanticManager;

   [SerializeField]
   private Text _textChannelName;

   [SerializeField]
   private GameObject _imageTapObj;

   [SerializeField]
   private Dropdown _dropDownChannel;

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

       var touch = PlatformAgnosticInput.GetTouch(0);
       if (touch.phase == TouchPhase.Began)
       {
           var names = "";
           switch(_dropDownChannel.value)
           {
               case 0:
                   names = ChannelNames(touch);
                   break;
               case 1:
                   names = ChannelNamesExist(touch);
                   break;
           }
           if (String.IsNullOrWhiteSpace(names))
           {
               names = "Not Found";
           }
           _textChannelName.text = names;
       }
   }

   string ChannelNames(Touch touch)
   {
       int x = (int)touch.position.x;
       int y = (int)touch.position.y;
       string[] channelsNamesInPixel = _semanticManager.SemanticBufferProcessor.GetChannelNamesAt(x, y);

       var sb = new StringBuilder();
       foreach (var i in channelsNamesInPixel)
       {
           sb.Append(i);
           sb.Append(" ");      
       }
       var names = sb.ToString();
       _imageTapObj.transform.position = new Vector3(x, y, 0);
       return names;
   }

   string ChannelNamesExist(Touch touch)
   {
       int x = (int)touch.position.x;
       int y = (int)touch.position.y;
       var sbp = _semanticManager.SemanticBufferProcessor;
       var channelNames = sbp.Channels;
       var sb = new StringBuilder();
       for (int i = 0; i < channelNames.Length; i++)
       {
           if (sbp.DoesChannelExistAt(x, y, channelNames[i]))
           {
               sb.Append(channelNames[i]);
               sb.Append(" ");
           }

       }
       var names = sb.ToString();
       _imageTapObj.transform.position = new Vector3(x, y, 0);
       return names;
   }

   bool IsOverGameObject()
   {
       if (EventSystem.current.IsPointerOverGameObject())
       {
           return true;
       }

       if (Input.touchCount > 0 && EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
       {
           return true;
       }
       return false;
   }
}

9. ビルド&実行

スクリーンショット 2022-01-25 17.54.50

タップした場所の情報をテキストで表示されます。上記の場合、タップした場所からgroundとartifical_groundの2つの情報が取得されました。

スクリーンショット 2022-01-25 17.56.45

スクリーンショット 2022-01-25 17.57.03

スクリーンショット 2022-01-25 17.58.26

木や木の葉、植木などはfoliageの検出される事が多いです。

スクリーンショット 2022-01-25 17.57.49

芝生や草原はgrassだけでなく、groundも同時に検出される事がほとんどです。

スクリーンショット 2022-01-25 17.55.16

スクリーンショット 2022-01-25 17.55.41

空や雲はskyとして認識され、建物全般はbuildingとして認識されます。画像はないようですが、川や噴水はwaterと認識されます。(コップやペットボトルや洗面台に溜まった水だとwaterとして認識することはないと思います。)

検証結果

SemanticBufferProcessor.GetChannelNamesAtを使用した場合、grassやartifical_groundのみ返却されることは、ほとんどなく、groundが含まれている事がほとんどでした。grassやartifical_groundが含まれた配列には、配列の0番目の要素にgroundが格納されている事がほとんどでした。

grassやartifical_groundをピンポイントで条件判定を行いたい場合は、SemanticBufferProcessor.DoesChannelExistAtでgrassやartifical_groundの存在チェックを行った方が良いと思います。

参考

Niantic の セマンティックセグメンテーションは独自のものだと思いますが、通常、COCOの構造と類似していることが多いとアドバイスを頂きました。

画像17

https://discord.com/channels/920503716027711528/923250546763264082/931930142811512952

最後に

OnePlanet XR
OnePlanet XR はAR/MR技術に専門特化したコンサルティングサービスです。豊富な実績を元に、AR/MR技術を活用した新たな事業の立ち上げ支援や、社内業務のデジタル化/DX推進など、貴社の必要とするイノベーションを実現いたします。

ご相談から受け付けております。ご興味ございましたら弊社までお問い合わせください。

OnePlanet Tech Magazine

様々な技術記事を定期的に投稿しています。