search
LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

ARとAIを組み合わせて遊ぶ

ARとAIを組み合わせて遊ぶ

はじめに

拡張現実(AR)と人工知能(AI)を組み合わせて遊ぶとお互いの足りない点を補っていろいろと面白いことができると思います。
いずれも使える技術は揃ってきており、組み合わせて使うための開発環境や実行環境もだいぶ整ってきていると思います。主にスマホでARとAIを組み合わせたアプリを作るのであれば、開発環境、ライブラリ、実行環境は以下のツールがサポートされています。

AR開発ではUIの設計や3Dモデルのあ使いやすさから、Unityで開発することが多いと思います。AR FoundationはAndroid、iOSでの動作をサポートしている(機能差はある)ため、Unityで両端末向けに開発すれば良いでしょう。AIについてはこちらのレポジトリがUnityでTensorflowLiteを動かすライブラリを公開してくれており、これもAndroid、iOS両方で動かすことができます(ありがたや、ありがたや)。
ARアプリでAIを使う利点のひとつは、高度な画像認識や画像変換が可能なことです。AIはパターンマッチングで認識するよりも種類多く、高い精度で物体を識別することが可能です。加えて画風変換や生成等も可能になっています。今回はAR FoundationとTensorflow Liteを組み合わせて、簡単なデモアプリを作ってみます。

作るもの

ネコを認識して、ネコの周りに3Dの立方体を出現させます。
こういうイメージです。

cat detection

やっていることは

  • 物体検知でカメラ内にネコを見つける
  • → 検知したネコの周りに立方体を出現させる

という簡単なものです。

コードはこちらで公開しています。
https://github.com/shibuiwilliam/AR-AI

作り方

環境

  • Unity 2020.1.14f1
  • OS: Macbook Pro

ライブラリ

まずは必要なライブラリをPackage Managerからインストールします。
主に以下のライブラリとバージョンになります。

  • AR Foundation 3.1.6
  • ARCore XR Plugin 3.1.8
  • ARKit XR Plugin 3.1.8
  • Mathematics 1.2.1

プロジェクトに入れているライブラリ一式は以下になります。

packages.png

Tensorflow Liteのみ、こちらのレポジトリから拝借しています。こちらのレポジトリはUnityでTensorflow Liteを動かすならとても便利なライブラリを用意してくれており、大変助かりました。ありがとうございます!

Scene

Sceneを作っていきます。まずはHierarchyにあるCameraを削除し、追加でAR Session Origin、AR Session、AR Cameraを作ります。
AR Session Origin配下にAdd ComponentからAR Plane ManagerとAR Raycast Managerを追加します。
加えてScriptとして AR Placement Manager を作成します。

scene.png

モデル

Tensorflow Liteのモデルはtfhubtfliteサンプルモデルから学習済みのものを入手することができます。
今回は物体検知としてSSDを使います。
SSDは軽量な物体検知モデルで、カメラ画像に対して検知した物体の位置(x,y,width,height)、検知した物体、検知スコアを返します。
検知可能な物体はここにあります。

Script

作成したARPlacementManager.csにコードを書いていきます。
void Start()でモデルをロードし、cameraTextureを初期化します。

using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.UI;
using TensorFlowLite;

[RequireComponent(typeof(ARRaycastManager))]
public class ARPlacementManager : MonoBehaviour
{
    [SerializeField] 
    string detectionFile = "ssd_mobilenet_v1_1_metadata_1.tflite";

    [SerializeField]
    private GameObject arCamera;

    [SerializeField]
    private ARCameraBackground arCameraBackground;

    [SerializeField]
    int interval = 1;

    private ARRaycastManager raycastManager;
    private static List<ARRaycastHit> hits = new List<ARRaycastHit>();

    RenderTexture cameraTexture;
    RawImage cameraView = null;

    SSD detector;

    TextureToTensor textureToTensor;

    DateTime last;

    private void Awake()
    {
        raycastManager = GetComponent<ARRaycastManager>();
    }

    void Start()
    {
        var options = new InterpreterOptions()
        {
            threads = 4,
            useNNAPI = false,
        };

        string path = Path.Combine(Application.streamingAssetsPath, detectionFile);
        detector = new SSD(path);

        textureToTensor = new TextureToTensor();

        cameraTexture = new RenderTexture(Screen.width, Screen.height, 0);
        cameraView.texture = cameraTexture;

        last = DateTime.Now;
    }

物体検知はvoid Update()で1回/秒で実行しています。インターバルを置かなくても良いのですが、画面がカクカクするのを防ぐために回数制限しています。
検知対象のカメラ画像はcameraTextureで取得しています。
今回はネコを検知したいので、ネコラベルの16番を検知したときに検知位置に立方体を出現させています。

    void Update()
    {
        if (arCameraBackground.material != null)
        {
            DateTime now = DateTime.Now;
            if ((now-last).TotalSeconds >= interval)
            {
                Detect();
                last = now;
            }
        }
    }


    private void Detect()
    {
        Graphics.Blit(null, cameraTexture, arCameraBackground.material);

        detector.Invoke(cameraTexture);
        var results = detector.GetResults();

        var catPos = -1;
        var catProb = 0f;
        for (int i = 0; i < results.Length; i++)
        {
            if (results[i].classID == 16)
            {
                if (results[i].score > catProb)
                {
                    catPos = i;
                    catProb = results[i].score;
                }
            }
        }

        if (catPos != -1)
        {
            var x = (results[catPos].rect.x + (results[catPos].rect.width / 2)) * Screen.width;
            var y = (results[catPos].rect.y + (results[catPos].rect.height / 2)) * Screen.height;
            Debug.Log($"X Y: {x}, {y}");
            var pos = GetPosition(x, y);
            Debug.Log($"get position: {pos.x}, {pos.y}, {pos.z}");

            GameObject tmp = GameObject.CreatePrimitive(PrimitiveType.Cube);
            tmp.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
            Debug.Log("Placed object");
            Instantiate(tmp, pos, Quaternion.identity);
        }
    }

    private Vector3 GetPosition(float x, float y)
    {
        var hits = new List<ARRaycastHit>();
        raycastManager.Raycast(new Vector2(x, y), hits, TrackableType.All);

        if (hits.Count > 0)
        {
            var pose = hits[0].pose;
            return pose.position;
        }
        return new Vector3();
    }

}

物体検知で取得するネコの位置はカメラ内の(x, y, width, height)であり、奥行きは取っていません。奥行き含めたAR Session内の位置(x, y, z)は RaycastraycastManager.Raycast(
Vector2 screenPoint, List<ARRaycastHit> hitResults, TrackableType trackableTypes = TrackableType.All)
で変換することができます。
AIに限らず、多くの画像処理が2次元をベースに開発されているため、ARの3次元ベクトルに変換しようとすると、2次元のY軸から3次元のY軸とZ軸を取得することになります。このあたりはメソッドがpublicだと助かりますが、公開されていないと困り処の一つになります。

おわり

というわけでARにAIを組み合わせた簡単なアプリを作りました。本当はもっと凝ったものを作りたかったのですが、時間切れです。年末年始にいろいろ作ります。

 補足

ちょうど一年くらい前にARカメラでセマンティックセグメンテーションを実行して、写っているネコの分身を作るアプリを作っていました。しかしARCoreがv1.20.0に更新されたときに利用していたAPIが消えてしまい、すべてが失われてしまいました。
もったいないので、こういうこともできるということで動画とmedium記事を残しておきます。

cat segmentation

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8