じゅころぐAR

AR/VRメインのブログ。時々ドローン。

Crystal Signal Piで光る通知システムを作る

ARもAIも関係なく、AWSとラズパイの話。

AWSの監視したいけど、メール見落としたら意味ないよね。
ラズパイとかでパトランプ回してる人、いそうじゃない?

という訳で、ググってみた。

www.infiniteloop.co.jp

www.youtube.com

そうそう、こういうやつ・・・なっ!?
なにこれ!カッコいい!!
Amazonで売ってるの? 5,000円?

ポチりました。

お急ぎ便にしてないけど、2日で届いた。

組み立て

基板のはんだ付けが終わっている通常版を買ったので、ラズパイ本体(別売り)にネジ止めするだけです。

組み立ては5分くらいで終わりました。

f:id:jyuko49:20181119013514j:plain

試しにラズパイを電源につないでみると、クリスタルが緑色に!

思った以上にカッコいい!!
なんとなく部屋を暗くしてしまう・・・。

Crystal Signal Piの設定

公式のマニュアル(PDF)の手順の通りに実行しました。

設定が終わったら、ブラウザからラズパイのIPアドレスにアクセスして、Web UIを表示します。

f:id:jyuko49:20181119012112p:plain

アラート実行を試すと、色がRGB値で自由に変更できて点滅のパターンも指定できます。
API経由で叩く場合は、GETパラメータで渡す感じです。
http://192.168.0.8/ctrl/?color=200,0,248&mode=1&repeat=0&period=1000&json=1

リモートで叩けるようにする

AWSやSlackから通知を受けるには、グローバルでアクセス可能なホスト名かIPアドレスが必要になります。

方法は色々ありますが、今回はこちらの記事を参考にルーターの設定とNo-IPで実現しました。
raspberry.mirukome.com

ラズパイのサーバプロセスは既に立ち上がっているので、Apacheのインストールは不要です。

  • No-IPクライアントをラズパイにインストール
  • ラズパイのプライベートIP(192.168.0.XXX)を固定
  • ルーターでラズパイへのポート:80を開放

を行えば、No-IPで作成したホスト名→ルーターグローバルIP→ラズパイのプライベートIPでルーティングされます。

No-IPのインストールにはサインアップが必要で、こちらから作成しています。
無償のホスト名は何もしないと30日で期限切れになってしまうため、定期的にconfirmが必要です。嫌なら有償版に、ってことですね。

疎通テストはWi-Fi接続を切ったiPhoneからNo-IPで作成したホスト名へのリクエストで実施できます。インターネット(4G回線)越しでも確かにつながりました。

AWSからの通知を設定する

AWSで障害が起きた際の通知を想定して、設定&テストしてみました。

f:id:jyuko49:20181120220545p:plain

  • CloudWatchでアラームを作成してSNSに通知
  • LambdaファンクションでSNSトピックをサブスクライブ
  • LambdaファンクションからラズパイにGETでアラート送信

詳細は割愛しますが、CloudWatchのアラームをSlack通知する手順とほぼ同じです。ググるとたくさん出てきます。
Lambdaファンクションを変更して、AWSからラズパイにアラートを投げるようにしました。

'use strict';

const AWS = require('aws-sdk');
const url = require('url');
const http = require('http');

const hookUrl = 'No-IPで作ったホスト名';

function sendAlert(state, callback) {
   
    const options = url.parse(hookUrl);
    if (state === 'ALARM') {
        options.path = '/ctrl/?color=255,0,0&mode=1&repeat=20&period=500&json=1';
    } else if (state === 'OK'){
        options.path = '/ctrl/?color=0,255,0&mode=0&repeat=1&period=500&json=1';
    } else {
        options.path = '/ctrl/?ack=1';
    }
    
    
    const alertReq = http.get(options, (res) => {
        const chunks = [];
        res.setEncoding('utf8');
        res.on('data', (chunk) => chunks.push(chunk));
        res.on('end', () => {
            if (callback) {
                callback({
                    body: chunks.join(''),
                    statusCode: res.statusCode,
                    statusMessage: res.statusMessage,
                });
            }
        });
        return res;
    });

    alertReq.end();
}

function processEvent(event, callback) {
    const message = JSON.parse(event.Records[0].Sns.Message);
    const newState = message.NewStateValue;

    sendAlert(newState, (response) => {
        if (response.statusCode < 400) {
            console.info('Send Alert is successfully');
            callback(null);
        } else if (response.statusCode < 500) {
            console.error(`Error send Alert: ${response.statusCode} - ${response.statusMessage}`);
            callback(null);
        } else {
            // Let Lambda retry
            callback(`Server error when send Alert: ${response.statusCode} - ${response.statusMessage}`);
        }
    });
}


exports.handler = (event, context, callback) => {
    if (hookUrl) {
        processEvent(event, callback);
    } else {
        callback('Hook URL has not been set.');
    }
};
  • 通知された状態がアラームなら、クリスタルが赤く点滅
  • 通知された状態がOKなら、クリスタルを緑に点灯

通知をテストする

監視対象にしているEC2のApacheを停止して応答不可にします。
LBのTarget Groupがヘルスチェックに失敗して、CloudWatchがアラートを上げる仕組み。

まとめ

当初の目的だったアラートに気付きやすくという点では、目に入るところに置いておけば、ほぼ間違いなく気付きます。
少なくとも、メールやSlackよりは見落としにくいです。

あえて欠点を挙げるなら、アラート時じゃなくても光らせたくなってしまう点でしょうか。
頻繁に光らせたいなら、Slack通知をWebhookさせた方が楽しそうです。

DeepLabをMask R-CNNと比べてみた

前回の続きで、物体検知&セグメンテーションのライブラリ調査です。
最終的にはモバイルARで使う想定で評価しています。

↓前回
jyuko49.hatenablog.com

はじめに

Mask R-CNNをカメラでキャプチャしたリアルタイムの動画に適用したところ、すごくカクカクで思うように速度が出ませんでした。
GPU性能を活かせば速くなるらしいのですが、モバイルARで使うとなると、マシンスペックに頼るのは難しいです。

そこで代替のライブラリを探したところ、以下の動画を見つけました。

youtu.be

右下のDeepLabは、Mask R-CNNと同様に検知した物体のセグメンテーションができていて、リアルタイム動画にも追従できそうな雰囲気です。
人や車だけでなく、建物などの背景もマスクしている点も気になったので、試してみることにしました。

DeepLabの特徴

TensorFlowで実装・学習されており、オープンソースとしてGithubで公開されています。
また、Googleの紹介記事があります。

developers-jp.googleblog.com

記事内では、Semantic image segmentation(セマンティック・イメージ・セグメンテーション)と書かれており、画像を"ピクセル単位"で意味的に分割するアルゴリズムのようです。
Mask R-CNNは、画像内の物体を検知して、検知した物体をマスクしていたので、その点で若干の違いがありそうです。ここは、実際に結果を見てみるとイメージしやすいです。

DeepLabの環境設定

まず、Tensorflowのモデルが提供されているGithubからリポジトリごとcloneして、DeepLabのディレクトリに移動します。

github.com

$ git clone https://github.com/tensorflow/models.git
$ cd models/research/deeplab/

ライブラリのインストール

前回と同じで、Python3、Jupyter Notebookを使います。今回はMask R-CNNの実行時に構築した環境をそのまま使いました。

また、tensorflow、matplotlib、numpyなどのライブラリも必要になります。requirement.txtが見当たらないので、不足分はpip install [パッケージ名]で追加します。Mask R-CNNの環境を先に作ってあれば、大体インストールされていると思います。

サンプル実行

Mask R-CNNのときと同様に、Jupyter Notebookで可視化されたサンプルを動かします。

$ jupyter notebook

deeplab_demo.ipynbを選択して、"Run"をクリックしていくと処理の流れが見られます。
結果のサンプルとしては、以下のような画像が表示されます。

f:id:jyuko49:20181116083658p:plain

コードを読んでみる

重要な部分だけ抜き出して見ていきます。

コードの最後に書かれているrun_visualization()がメイン処理です。

def run_visualization(url):
  """Inferences DeepLab model and visualizes result."""
  try:
    f = urllib.request.urlopen(url)
    jpeg_str = f.read()
    original_im = Image.open(BytesIO(jpeg_str))
  except IOError:
    print('Cannot retrieve image. Please check url: ' + url)
    return

  print('running deeplab on image %s...' % url)
  resized_im, seg_map = MODEL.run(original_im)

  vis_segmentation(resized_im, seg_map)


image_url = IMAGE_URL or _SAMPLE_URL % SAMPLE_IMAGE
run_visualization(image_url)

image_urlから画像を読み込んでoriginal_imにセットし、MODEL.run()を実行すると、リサイズされた元画像resized_imとセグメンテーション結果seg_mapが得られます。
その結果をvis_segmentation()に渡して、画面に表示しています。

MODELはコードの前半で定義されているDeepLabModelクラスのインスタンスで、http://download.tensorflow.org/models/からダウンロードしたモデルを使ってTensorflowのSessionを実行しています。

MODEL = DeepLabModel(download_path)

seg_mapからマスク画像を生成する処理は、vis_segmentation()内に記述されています。

seg_image = label_to_color_image(seg_map).astype(np.uint8)
plt.imshow(seg_image)

具体的な処理はlabel_to_color_image()create_pascal_label_colormap()に書かれていますが、長くなりそうなのでここでは割愛。

テストスクリプトの作成

例によって、deeplab_demo.ipynbのコードを参考にして作りました。

静止画(ローカルファイル)

改変した箇所は以下です。

元画像を固定のURLから取得するようになっているので、ファイルパスに変更します。

#image_url = IMAGE_URL or _SAMPLE_URL % SAMPLE_IMAGE
image_path = '[ローカルのファイルパス]'
run_visualization(image_path)

Mask R-CNNのサンプルに倣って、ディレクトリ内の画像をランダムに表示する場合は以下のようにします。

import random

IMAGE_DIR = './test_image'
file_names = next(os.walk(IMAGE_DIR))[2]
image_path = os.path.join(IMAGE_DIR, random.choice(file_names))
run_visualization(image_path)

image_urlで使っていた定数は要らないので、コメントアウトします。

'''
SAMPLE_IMAGE = 'image1'  # @param ['image1', 'image2', 'image3']
IMAGE_URL = ''  #@param {type:"string"}

_SAMPLE_URL = ('https://github.com/tensorflow/models/blob/master/research/'
               'deeplab/g3doc/img/%s.jpg?raw=true')
'''

最後に、画像の読み込み部分を変更します。
ローカルファイルを読み込む場合、 Image.open()にパスをそのまま渡せばOKです。引数名はurlじゃなくてpathだけど直してません。

def run_visualization(url):
  """Inferences DeepLab model and visualizes result."""
  try:
    # f = urllib.request.urlopen(url)
    # jpeg_str = f.read()
    # original_im = Image.open(BytesIO(jpeg_str))
    original_im = Image.open(url)
  except IOError:
    print('Cannot retrieve image. Please check url: ' + url)
    return

完成版のコードサンプルです。

gist.github.com

リアルタイム動画

OpenCVVideoCapture()を使って、取得したフレーム画像にマスクをかける処理をループさせれば実現できます。

import cv2
import time

capure = cv2.VideoCapture(0)

def run_visualization():
  while(True):
    ret, frame = capure.read()
    original_im = cv2.resize(frame,(480,320))

    start_time = time.time()

    resized_im, seg_map = MODEL.run(original_im)
    vis_segmentation(resized_im, seg_map)

    elapsed_time = time.time() - start_time
    print(elapsed_time)

    if cv2.waitKey(1) == 27:
      break
  capure.release()
  cv2.destroyAllWindows()

run_visualization()

画像の表示部分もOpenCVに変更しています。かなりシンプルに書けます。

def vis_segmentation(image, seg_map):
  seg_image = label_to_color_image(seg_map).astype(np.uint8)

  result = cv2.add(image, seg_image)
  cv2.imshow("camera window", result)

動かすとこんな感じ。

完成版のサンプルコードはこちら。

gist.github.com

性能比較

実行環境

処理結果は4パターンの画像をそれぞれ比較。 処理時間の方はMacbookのカメラでキャプチャしたリアルタイムの映像を480 x 320にリサイズして連続処理した際の時間を計測。

Mask R-CNN

処理結果

f:id:jyuko49:20181116230323p:plain f:id:jyuko49:20181112154021p:plain f:id:jyuko49:20181112153856p:plain f:id:jyuko49:20181112154027p:plain

検知した物体をかなり正確にセグメンテーションできています。

処理時間(sec)

2.493619203567505
2.4120068550109863
2.4120633602142334
2.4788520336151123
2.5836117267608643
...

1回の検知に2.4-2.5 secかかっています。
ということは、フレームレートは0.4fps。1fpsを切っているため、リアルタイム動画ではラグが相当大きいです。

DeepLab

処理結果

f:id:jyuko49:20181116233707p:plain f:id:jyuko49:20181116234933p:plain f:id:jyuko49:20181116235210p:plain f:id:jyuko49:20181116235221p:plain

正面や横を向いている物体はキレイに検出できていますが、後ろ向きや座っている人の検出がイマイチです。この点は追加学習させれば改善する部分なのかもしれません。
明らかな差異としては物体の形状が途切れやすい点で、画像のピクセル単位でセグメンテーションを行なっている結果のようです。

処理時間(sec)

0.42249274253845215
0.45106005668640137
0.44458889961242676
0.42955899238586426
0.4366567134857178
...

0.40-0.45 sec前後なので、Mask R-CNNの1/5程度の処理時間です。決して速くはないですが、2.5fpsくらいは出ます。
冒頭で紹介したYoutubeの動画内でも平均2.5fpsと表示されているので、妥当な結果と思われます。

結果まとめ

  • リアルタイム性を重視するならDeepLab
  • リアルタイム性が重要でなく、精度重視ならMask R-CNN

という使い分けになりそうです。

今後

DeepLabの処理速度であれば、モバイルARでもなんとか使えそうという感触です。
実際に同様の研究はされていて、モバイル相当のスペックでもリアルタイム動画に追従はできる様子。

ai.googleblog.com

静止画のRGBに加えて前フレームで生成したマスクを使って、高速に処理しつつ精度を上げてるんですね。なるほど。

一旦は学習済みモデルをそのまま使うことにして、TensorFlowSharp in Unityを先に試そうかなと思っています。

Mask R-CNNを試してみた

AR x AIで使えそうなMask R-CNNというOSSを教えてもらったので動かしてみました。

github.com

Mask R-CNNでできること

Githubリポジトリに載っている画像がわかりやすいです。

https://github.com/matterport/Mask_RCNN/raw/master/assets/street.png

画像の中の物体(人とか車とか)を検知(Object Detection)して、検知した物体を背景と分割(Object Segmentation)する形でマスクしてくれます。

これとよく似た画像って、どこかで見たことがあるような?

japanese.engadget.com

Niantic Real World Platform」の記事で、現実と溶け込んだARの要素技術として物体検知が紹介されていました。
リアルタイムに物体検知を行いつつ、物体との距離も推定してマスクを行うことができれば、リアルなオクルージョンが実現できそうです。
AR以外の用途だと、写真投稿サイトで写り込んだ人物をぼかしたり、自動運転やナビゲーションで人を避けたり、とか。

環境構築

GithubのREADMEをざっくり読むと、Jupyter Notebookを使ってステップごとの結果が可視化できるらしい。

jupyter.org

また、物体検知用に提供されているMS COCOというデータセットを使うらしい。MSはMicrosoftの略。

COCO - Common Objects in Context

github.com

Jupyter Notebookのインストール

$ pip install jupyter

それでは早速入れてみ・・・エラーがいっぱい出ました!
メッセージを眺めるとライブラリのバージョンがどうのこうの言っているので、Pythonのバージョンを確認したところ2系でした。pyenv使っててglobalは古いままだった・・・。

$ python --version
Python 2.7.10

Mask_R_CNNはPython3で動作と明記されているので、3系に切り替えます。

pyenv versionsでインストール済みのバージョンを確認。3.6.6があったので、localのバージョンを切り替えて再度実行。

$ pyenv local 3.6.6
$ pip install jupyter

今度はインストールできたので、以下のコマンドで実行。

$ jupyter notebook

f:id:jyuko49:20181110224038p:plain

何か表示されました。

で、ここからどうすれば?

必要ライブラリのインストール

Getting Startedを見ると、とりあえずdemo.ipynbを動かしてみるのがよいらしい。
拡張子が.ipynbになっているファイルが、Jupyter Notebookの実行ファイルのようです。JSONPythonのコードを突っ込んだようなフォーマットになってます。

Jupyter Notebookを起動して"samples > demo.ipynb"をクリックすると、以下のような画面が表示されました。

f:id:jyuko49:20181111000428p:plain

Runボタンをクリックしていくと次の処理に進みますが、途中でエラーメッセージが出ます。

f:id:jyuko49:20181110234358p:plain

ModuleNotFoundError: No module named 'numpy'

どうやらライブラリが足りない様子です。

これらのインストール方法はREADMEの下の方に書いてありました(もうちょっと上の方に書いておいて欲しさ)

$ pip install -r requirements.txt
$ python setup.py install

requirements.txtにパッケージ名が列記されていて、上記コマンドを実行すれば必要なライブラリがインストールされます。

COCO APIのインストール

前述の手順でインストールしただけだと、以下のエラーが解消されません。

ModuleNotFoundError: No module named 'pycocotools'

上記はCOCO APIのインストールで入るようです。

github.com

Githubからcloneして、とりあえずプロジェクトフォルダ直下に配置。Python APIをインストールします。

$ git clone https://github.com/cocodataset/cocoapi.git
$ cd coco/PythonAPI
$ python setup.py install

これで環境は構築できたはず。
Runコマンドをポチポチしていくと、最終的に以下のような画像が表示されます。(画像は毎回変わる)

f:id:jyuko49:20181111000636p:plain

コードを読んでみる

サンプルスクリプトが何をやっているのか、ざっと見てみてみます。

In[1]

最初のブロックでは、ライブラリのインポートと定数を定義しているだけ。

import os
import sys
import random
import math
import numpy as np
import skimage.io
import matplotlib
import matplotlib.pyplot as plt

# Root directory of the project
ROOT_DIR = os.path.abspath("../")

# Import Mask RCNN
sys.path.append(ROOT_DIR)  # To find local version of the library
from mrcnn import utils
import mrcnn.model as modellib
from mrcnn import visualize
# Import COCO config
sys.path.append(os.path.join(ROOT_DIR, "samples/coco/"))  # To find local version
import coco

%matplotlib inline 

# Directory to save logs and trained model
MODEL_DIR = os.path.join(ROOT_DIR, "logs")

# Local path to trained weights file
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
# Download COCO trained weights from Releases if needed
if not os.path.exists(COCO_MODEL_PATH):
    utils.download_trained_weights(COCO_MODEL_PATH)

# Directory of images to run detection on
IMAGE_DIR = os.path.join(ROOT_DIR, "images")

mask_rcnn_coco.h5が学習済みモデルで、ローカルになければダウンロードしてくるようになっている。
IMAGE_DIRが物体検知を行う画像フォルダのパス。

In[2]: Configurations

CocoConfigクラスを継承して、InferenceConfigクラスで一部プロパティを上書きしている。

class InferenceConfig(coco.CocoConfig):
    # Set batch size to 1 since we'll be running inference on
    # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU
    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

config = InferenceConfig()
config.display()

In[3]: Create Model and Load Trained Weights

モデルの生成部分です。先程作成したconfigを渡してます。

# Create model object in inference mode.
model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config)

# Load weights trained on MS-COCO
model.load_weights(COCO_MODEL_PATH, by_name=True)

コードの最初の方に、

import mrcnn.model as modellib

とあるので、'mrcnn/model.py'が本体のようです。

In[4]: Class Names

検知した物体のクラス名の定義。ラベルを付けるときに使う。

# COCO Class names
# Index of the class in the list is its ID. For example, to get ID of
# the teddy bear class, use: class_names.index('teddy bear')
class_names = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
               'bus', 'train', 'truck', 'boat', 'traffic light',
               'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
               'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
               'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
               'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
               'kite', 'baseball bat', 'baseball glove', 'skateboard',
               'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
               'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
               'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
               'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
               'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
               'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
               'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
               'teddy bear', 'hair drier', 'toothbrush']

データセットが参照できれば以下のコードで置き換えられるらしい(試してないです)

# Load COCO dataset
dataset = coco.CocoDataset()
dataset.load_coco(COCO_DIR, "train")
dataset.prepare()

# Print class names
print(dataset.class_names)

In[5]: Run Object Detection

ここがメインの処理ですね。

# Load a random image from the images folder
file_names = next(os.walk(IMAGE_DIR))[2]
image = skimage.io.imread(os.path.join(IMAGE_DIR, random.choice(file_names)))

# Run detection
results = model.detect([image], verbose=1)

# Visualize results
r = results[0]
visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], 
                            class_names, r['scores'])

画像データをファイルから取得してmodel.detect()を行うと、resultsに結果のデータが入ってくるので、それをvisualize.display_instances()という処理に渡しています。
visualizeはコードの最初の方でimportしてあるライブラリで、mrcnn/visualize.pyに実際の処理が書いてあります。

from mrcnn import visualize

visualize.py内の処理はコード書きながら理解することにします。

使ってみる

サンプルと同じことをやるだけですが、自分でコードを書いてみます。

.ipynb形式はコーディングしづらいので、test.pyを作成して、In[1]〜[5]のコードをコピペしていきます。
ファイルができたら、とりあえず以下の3ヶ所だけ変更しておきます。

#%matplotlib inline  #よくわかんないけどエラーになるのでコメントアウト
ROOT_DIR = os.path.abspath("../")  #実行ファイルからルートへの相対パスに直す
# 画像を加工&表示する部分は自作するのでコメントアウト
#result = visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],
#                        class_names, r['scores'])

コマンドラインで実行します。

$ python test.py

バックエンドがMacOSだとなんちゃらみたいなmatplotlibのエラーが出た場合、以下の記事で解決しました。

qiita.com

結果の画像を表示する

skimage.ioとmatplotlib.pyplotがインポートされているので、これを使います。

result_image = image.copy()

skimage.io.imshow(result_image)
plt.show()

こんな感じで表示されるはず。

f:id:jyuko49:20181112142300p:plain

ウィンドウを閉じると、コマンドラインで実行したプログラムも終了します。

検知結果の中身を確認する

resultsに何が入っているか見たいので、printしてみます。

print(r['rois'])
print(r['masks'])
print(r['class_ids'])
print(r['scores'])

rois

[[ 38 265 493 533]
 [163 482 229 530]
 [250  31 587 782]]

検出した物体を囲う矩形(Bounding box)の頂点座標が入っています。配列の要素数が検知した物体の数になります。

masks

[[[False False False]
  [False False False]
  [False False False]
  ...
  [False False False]
  [False False False]
  [False False False]]

 [[False False False]
  [False False False]
  [False False False]
  ...

(長いので省略)

  [False False False]
  [False False False]
  [False False False]]]

rois内でマスクをかける範囲が配列で入っている。詳しいところはよくわかってない。

class_ids

[ 1 47 61]

検知した物体のクラスに対応するIDが入っています。
class_namesを使うことでラベル文字列を取得できます。

for id in r['class_ids']:
    print(class_names[id])
person
banana
dining table

scores

[0.99927574 0.9297102  0.91985023]

検知の精度がスコアとして入っています。数値が1に近いほど、検出の正確性が高いはず。

コードを書く

resultsの中身がわかったので、resultsを使って結果を描画していきます。

前提として、

for roi in r['rois']:
    # 矩形の描画

for mask in r['masks']:
    # マスクの描画

のようなコーディングだと、ループがたくさんになるので、

N = r['rois'].shape[0]  #配列の大きさ(要素数)
for i in range(N):
    # r['rois'][i]を描画
    # r['masks'][i]を描画

とした方がループが1回で速い(たぶん)

矩形の描画

visualize.draw_box(image, box, color)でimageにboxを重ねることができます。

result_image = visualize.draw_box(result_image, r['rois'][i], rgb)

もしくはOpenCVを使って、

import cv2

result_image = cv2.rectangle(result_image, 
    (r['rois'][i][1],  r['rois'][i][0]), ( r['rois'][i][3],  r['rois'][i][2]), rgb)

みたいな実装でもできます。

ラベルとスコアの描画

OpenCVを使いました。位置は対応する矩形(r['rois'][i])の左上の座標を使っています。

font = cv2.FONT_HERSHEY_SIMPLEX
text = class_names[r['class_ids'][i]] + ':' + str(r['scores'][i])
result_image = cv2.putText(result_image, text,
    (r['rois'][i][1],r['rois'][i][0]), font, 0.8, rgb, 2, cv2.LINE_AA)

マスクの描画

こはちょっと難しいので、visualize.apply_mask(image, mask, color)を使いました。

mask = r['masks'][:, :, i]
result_image = visualize.apply_mask(result_image, mask, color)

実行結果

f:id:jyuko49:20181112153856p:plain f:id:jyuko49:20181112153914p:plain f:id:jyuko49:20181112154021p:plain f:id:jyuko49:20181112154027p:plain

精巧なフィギュアやイラストは、人として認識されるんですね。

他にも、忍ちゃんの髪がバナナと判別されたり、パドックのOの字がフリスビーと判別されたりしていますが、ARで使う場合は、物体が何であるかの検知よりも物体がそこにあることを検知してくれた方が都合良さそうです。
検知(判別はともかく)の精度という意味では、パドックの写真で後ろ向きの人物をすべて検出していてすごい。

  • マスクあり版
  • マスクなし版

マスクなし版はGoogleVision APIをローカルで実行している感じですね。

複数のオブジェクトを検出する  |  Cloud Vision API ドキュメント  |  Google Cloud

完成版のコード

Gistにアップしました。
Mask_RCNN直下のファイルにコピペして、test_imageフォルダを作って画像を置けば動くはず。

今後

大体の動作はわかったので、AR開発で使ってみたいです。
というか、これからのARはAIありきになっていきそう。

リアルタイムの動画でテストする

各フレームに対して同様の処理を行えばよいので、実装自体は難しくないと思う。
フレームレートがどれくらい落ちるかの方が懸念。

Unityで使えるか調べる

ARアプリで使うには、学習済みモデルをUnityで動かしたい。
mask_rcnn_coco.h5はKeras用のファイルのようなので、TensorFlowで使えるようProtocol Bufferで保存して、TensorFlowSharpでごにょごにょする感じでしょうか。

オクルージョンに使う

マスクしている箇所をオクルージョンするシェーダを書けばいいはず。
書ければ。

他の学習モデルも試す

物体検知のモデルとしては、R-CNN、YOLO、SSD(Solid State Driveではない)が有名らしい。
以下の記事を見ると、R-CNNよりもYOLOの方がFPSが出てるので、同様のマスク処理ができるなら両方試した方がいいかもしれない。

qiita.com

追加学習させる

今回は学習済みモデルをそのまま使いましたが、自分で学習データを追加して用途に応じてチューニングできた方が便利です。が、教師データの準備とか大変そうなので優先度は低め。

3D対応を試す

READMEにて、3Dに対応したデータセットとしてMatterport3Dが紹介されています。

github.com

お問い合わせしないとダウンロードできないので試せませんでしたが、3Dでの物体検知は興味があります。

また、別のモデルで単眼カメラの画像からデプスを推定するvid2depthというのもあったので、これと物体検知を組み合わせて使うのも良さそう。

models/research/vid2depth at master · tensorflow/models · GitHub

↓続きです
jyuko49.hatenablog.com

three.jsでWebVRを行うための基本的なところ

久々の投稿デス。

WebXRやろうかなと思ったのですが、手持ちのARCore対応デバイスがAndroid7.xのため試せず・・・で気分転換にWebVRをやりました。

はじめに

three.jsに関する基礎知識を持っている前提で書いているので、three.jsをさわったことがない!という方は、他の記事やサイトも合わせて読んでください。

開発環境については、以下となっております。(WiFiルータ経由でローカル通信)

three.jsでVRMを扱う

VRMLoader.jsを使って、VRMを表示させることができます。
公式のexampleは以下。

VRMはglTF2.0ベースなので、GLTFLoader.jsでも読み込みは可能です。
上記のVRMLoader.jsも内部でGLTFLoader.jsをラップしています。

UniVRMでエクスポートする

まず、VRMモデルを用意します。
今回は公式の手順に従い、FBXファイルをUniVRMでエクスポートしてみました。

VRMファイルを作ってみたい - dwango on GitHub

モデルはいつもの(クエリちゃんSDモデル)です。

assetstore.unity.com

手順の通り、Humanoidリグが設定されているモデルをエクスポートするだけなので簡単。

f:id:jyuko49:20181008222525p:plain

こんな感じで、three.jsで使えるようになります。

f:id:jyuko49:20181008223306p:plain

ボーンアニメーションで動かす

FBXからエクスポートしたVRMにはアニメーションが付いていないので、何もしないとTポーズのままになります。
それだと面白くないので、ボーンを使って動きを付けます。

ボーンを確認する方法として、LoadしたVRMモデルの情報をconsoleに表示させました。

var loader = new THREE.VRMLoader();
loader.load( 'SD_QUERY_01.vrm', function (vrm) {
  console.log(vrm);
  ...
}

MacWebブラウザでページを開いてconsoleを見ると、VRMの構造が確認できます。

f:id:jyuko49:20181008230138p:plain

scene内にモデルが含まれるので、sceneのchildrenを辿っていくと、SkinnedMeshが見つかるはずです。SkinnedMeshはskeletonプロパティにbonesを持っていて、このbonesを回転させれば、モデルが動きます。

ボーンアニメーションについては、カヤックさんのブログがわかりやすかったです。

techblog.kayac.com

とりあえず、動かしてみました。

中央にいるSDクエリちゃんの回転と首振りがボーンアニメーションです。

一方、隣でキレキレのダンスを披露しているコロちゃん(コロコロ*1 )は、元のモデルがMMDMMDLoader.jsでVMDファイルをAnimationClipとしてLoadしています。
複雑なモーションの場合、ボーンをスクリプトで直接動かすのはつらいので、何らかの手段でAnimationClipを作った方がいいです。

three.jsのシーンをWebVRに対応させる

exampleを参考にthree.jsのシーンをVRで表示させます。

通常のスクリプトから以下の点を変更すればOKのよう。

// WebVR.jsを読み込む
<script src="examples/js/vr/WebVR.js"></script>

<script>
  renderer = new THREE.WebGLRenderer( { antialias: true } );

  // rendererでVRを有効にする
  renderer.vr.enabled = true;

  // createButtonで"Enter VR"ボタンを表示する
  document.body.appendChild(WEBVR.createButton(renderer));

  function animate() {
    // setAnimationFrameではなく、setAnimationLoopでループさせる
    renderer.setAnimationLoop(animate);
    render();
  };
</script>

Daydreamで表示させるとこんな感じになります。

Daydreamコントローラーを使う

Gamepad Extensionsを有効化する

three.jsのexamplesでは、renderer.vr.getController()でDaydreamコントローラーの情報が取れているのですが、ローカルで実行すると動きませんでした。

GoogleのDevelopers Guideによると、ChromeブラウザでGamepad API 拡張機能を有効にしないと動かないようです。 Adding Input to a WebVR Scene  |  Web Fundamentals  |  Google Developers

Chromeブラウザのアドレスバーにchrome://flagsと入力して、設定画面を開き、"Gamepad Extensions"、"WebXR Gamepad Support"をEnabled(有効)にすると動くようになります。

f:id:jyuko49:20181009083731j:plain

Ray Inputで代用する

開発者向けであれば、上記の設定を行えばいい話ですけど、一般のユーザに自身のデバイスでデモを体験させたいときなど、設定を変えてもらうのはちょっと手間です。

代案として、先程のGoogleの記事に記載されていたRay Inputであれば、拡張機能の設定なしに動きます。

github.com

Daydream(モバイルVR)の場合、オブジェクト選択に使うRayがコントローラーの向きに関わらず、視点の中心になりました。
click等はDaydreamコントローラーの操作がイベントとして取れます。

空間内の移動を実装する

three.jsで移動を実装する場合、cameraオブジェクトのposition、rotationを操作すれば視点が変わり、プレイヤーが移動しているように見えます。
ただ、WebVRの場合はcameraがVRバイスと連動するため、cameraのプロパティを直接操作しても視点が変わりません。

// three.jsでは移動するけど、WebVRだと動かない・・・
camera.position.z -= 0.1;

これの解決策として、cameraの親オブジェクトを作ってあげて、camera直接ではなく親オブジェクトの方を動かすと位置が変わります。

// Object3Dでplayerを作り、cameraを親子関係にする
var player;
player = new THREE.Object3D();
scene.add(player);
player.add(camera);

// playerを動かすとcameraも動いて視点が変わる
player.position.z -= 0.1;

playerの移動をコントローラー入力のイベントに連動させれば、3DoFデバイスでも空間上の前進、後退くらいは実現できます。

まとめ

今回の内容でthree.jsのシーンをWebVR対応させ、キャラクター(VRM)を表示させて、コントローラーで入力や移動を行うところまで実装できました。
若干の差異はあれど、通常のthree.jsとほぼ変わらないので、元々three.jsをやっていた人なら簡単にVR開発ができます。

three.jsの場合、シーン構成からアニメーションまですべてスクリプトで完結する点とライブラリ・サンプルが豊富という点で、UnityやUEにはない安心感がありますね。(圧倒的ホームグラウンド感!)

Webなので、ネットワークを介したコンテンツの共有が楽ですし、VR内でのWebブラウジングからスムーズにVRコンテンツに移行できるので、安価なVRバイスが普及していけば用途も広がりそうな感じはします。

*1:gdgd妖精sのBD特典モデル(Copyright (c) 2013 gdgd妖精s(ぐだぐだフェアリーーーズ), 2代目gdgd妖精s)、改造可・再配布・商用利用不可

ARKitのARObjectAnchorをUnityで使ってみる

ARKit2の新機能として追加されたARObjectAnchorをUnityで試してみます。

blogs.unity3d.com

ARObjectAnchorとは

現実の物体(オブジェクト)を検知して、Anchorの作成・トラッキングを行う機能です。
予め作成しておいたARReferenceObjectを元に検知を行います。

f:id:jyuko49:20180805172842p:plain

開発環境

ARKit2で追加された機能のため、iOS12およびXcode10が必要になります。
本記事執筆時点ではβ版のため、Apple Developerからダウンロードしました。

Unity ARKit Pluginのインストール

Unity ARKit PluginはBitbucketのから"arkit2.0_beta"ブランチをダウンロードします。

Unity-Technologies / Unity-ARKit-Plugin / Downloads — Bitbucket

ダウンロード後、Assets以下のUnityARKitPluginをUnityプロジェクトのAssetsにフォルダごとコピーして使います。

f:id:jyuko49:20180805171735p:plain

ARKitを使うのに必要なUnityの設定については割愛します。"Camera usage description"を忘れなければ、なんとかなると思います。

ARReferenceObjectを作成する

検知したいオブジェクトをスキャンして、ARReferenceObjectを作成します。
ここが上手くできないと検知の精度が落ちるので、重要な作業です。

UnityARKitPluginにExamplesとして付属しているUnityObjectScannerをビルドすると、スキャン用のアプリが起動します。

UnityARKitPlugin > Examples > ARKit2.0 > UnityObjectScanner

BoundingBoxを対象オブジェクトに合わせる

アプリ起動後の手順として、まずはBoundingBoxでスキャン対象のオブジェクトを囲み、Axisをオブジェクトの中心に合わせます。

f:id:jyuko49:20180805222935p:plain

基本的な操作方法は、以下の通り。

BoundingBoxの移動:オブジェクト付近の平面をタップ
BoundingBoxの伸縮:BoundingBoxの面をタップしてドラッグ
BoundingBoxの平行移動:Axisをタップしてドラッグ
BoundingBoxの回転:画面を二本指タップした状態で回転

対象オブジェクトをスキャンする

BoundingBoxの位置を合わせたら、その状態で対象オブジェクトを複数の方向からスキャンします。"Create Objects"、"Detect Objects"のボタンを押す必要はありません。カメラをかざすことで、オブジェクト上のPointCloudが特徴点として検出されていきます。

ARReferenceObjectを保存する

スキャンが完了したら、アプリ上のUIでARReferenceObjectを作成・保存します。

"Create Objects"でBoundingBox内の特徴点からARReferenceObjectが作られ、リストに追加されます。 この処理は多少時間がかかることがあり、画面中央のウィンドウにファイル名のリスト(objScan_0,objScan_1,...)が表示されたら、作成完了した合図です。
最後に、"Save Objects"でARReferenceObjectをファイルとしてデバイスに保存します。これを忘れると、せっかくスキャンした結果を利用できなくなります。

f:id:jyuko49:20180805225206p:plain

他のボタンは以下の用途に使えます。スキャンだけを行う場合、必須ではないです。

"Detect Object"は検知モードのON/OFFです。スキャン結果を用いた検知のテストに使えます。
"Clear Objects"は"Create Objects"で作成したARReferenceObjectを全て削除します。

ARReferenceObjectをコピーする

iTunesのファイル共有からコピーするのが簡単です。

スキャンを行ったデバイスMacに接続して、iTunesでファイル共有メニューを開きます。

f:id:jyuko49:20180805231521p:plain

アプリが作成したファイルに"ARReferenceObjects"という名称のフォルダがあり、ARReferenceObjectが保存されています。
フォルダごとMacにコピーしてFinderで開くと、拡張子が.arobjectのファイルがあるはずです。
このファイルをUnityにコピーして使います。

f:id:jyuko49:20180805232614p:plain

ARKitScannerを使ったARReferenceObjectの作成

スキャンを行う方法としてはもう一つ、Apple公式のサンプルコードをXcodeでビルドしたARKit Scannerが使えます。

Scanning and Detecting 3D Objects | Apple Developer Documentation

スキャンの流れはUnityのサンプルと同じですが、BoundingBoxの作成、Scan、Test、ファイルの保存・共有とステップを踏んでいくUIとなっており、UnityObjectScannerよりもわかりやすいです。

f:id:jyuko49:20180805221927p:plain

スキャンの進捗に応じてパーセンテージが表示され、スキャンを行った面はBoundingBoxが着色される機能もあるため、全方向から満遍なくスキャンを行えます。
100%になっていなくても"Finish" > "Cancel"(Continueのキャンセル)でスキャンを終了できます。

f:id:jyuko49:20180805221955p:plain

最終的に作成されるのは.arobjectファイルなので、Unityでも使えます。
ファイルの転送にAirDropが使える点も便利です。

ARObjectAnchorを使う

作成したARReferenceObjectを使って、ARObjectAnchorの作成、コンテンツの重ね合わせを行います。

Unity ARKit PluginでARObjectAnchorを使うには、UnityARCameraManagerの"Detection Objects"ARReferenceObjectsSetAssetをセットします。

f:id:jyuko49:20180805235927p:plain

ARReferenceObjectsSetAssetは、プロジェクトウィンドウで右クリックから"Create" > "UnityARKitPlugin" > "ARReferenceObjectsSetAsset"で作成できます。

f:id:jyuko49:20180805165517p:plain

ARReferenceObjectsSetAssetには、検知対象のARReferenceObjectAssetを複数セットできます。

f:id:jyuko49:20180806001817p:plain

ARReferenceObjectAssetもプロジェクトウィンドウで右クリックから"Create" > "UnityARKitPlugin" > "ARReferenceObjectAsset"で作成できます。

f:id:jyuko49:20180806002206p:plain

ARReferenceObjectsAssetには、スキャンで作成した.arobjectファイルをARReferenceObjectとしてセットします。

ここまでで、ARSessionに検知対象のARReferenceObjectが設定され、ARObjectAnchorが作成されるようになります。

作成されたARObjectAnchorを使ってARコンテンツを重ねるにはスクリプトを使います。UnityARObjectAnchorで利用されているGenerateObjectAnchor.csが参考になります。

まず、Start()でARObjectAnchor作成時、更新時に実行するメソッドを定義します。

void Start () {
    UnityARSessionNativeInterface.ARObjectAnchorAddedEvent += AddObjectAnchor;
    UnityARSessionNativeInterface.ARObjectAnchorUpdatedEvent += UpdateObjectAnchor;
}

ARSessionでオブジェクトが検知されると、定義したメソッドが呼ばれ、ARObjectAnchorが渡されます。
作成時に呼ばれるメソッド(AddObjectAnchor())では、ARObjectAnchorからposition、rotationを取得して、GameObjectを生成(Instantiate)しています。

void AddObjectAnchor(ARObjectAnchor arObjectAnchor)
{
    Debug.Log ("object anchor added");
    if (arObjectAnchor.referenceObjectName == referenceObjectAsset.objectName) {
        Vector3 position = UnityARMatrixOps.GetPosition (arObjectAnchor.transform);
        Quaternion rotation = UnityARMatrixOps.GetRotation (arObjectAnchor.transform);

        objectAnchorGO = Instantiate<GameObject> (prefabToGenerate, position, rotation);
    }
}

検知したオブジェクトの位置が更新されると、更新されたARObjectAnchorが渡されます。
更新時のメソッド(UpdateObjectAnchor())では、生成したGameObjectの位置をARObjectAnchorの更新に合わせて上書きしています。

void UpdateObjectAnchor(ARObjectAnchor arObjectAnchor)
{
    Debug.Log ("object anchor added");
    if (arObjectAnchor.referenceObjectName == referenceObjectAsset.objectName) {
        objectAnchorGO.transform.position = UnityARMatrixOps.GetPosition (arObjectAnchor.transform);
        objectAnchorGO.transform.rotation = UnityARMatrixOps.GetRotation (arObjectAnchor.transform);
    }
}

基本的には、上記のサンプルコードと同様にGameObjectの生成、位置の更新を行えばARコンテンツが現実のオブジェクトに重ねて表示され、対象が動いても追従するようになります。
複数のオブジェクトを検出しつつ、異なるARコンテンツを表示したい場合は、ARReferenceObjectAssetに設定したObjectNameがARObjectAnchorにセットされるため、何を検知したかを名称によって制御できます。

アプリで動かしてみる

まず、UnityARObjectAnchorと同様にAxisを表示してみます。

スキャンしたARReferenceObjectの精度にもよりますが、検知に数秒かかるようなこともなく、オブジェクトにカメラをかざすとすぐに検知してくれます。

続いて、ARコンテンツを3D Textに変更して、追従のテストを行ってみました。

検知対象のオブジェクトが移動すると、テキストも移動していることがわかります。

まとめ

ARKit2で追加されたARObjectAnchorは、現実の物体をARで拡張するのに適した機能です。
ARReferenceObjectのスキャンがやや面倒ではありますが、アプリの操作に慣れれば数分で作成できます。一度作成してしまえば、どのアプリでも使えますし、ファイルを共有することで同じ物を持っている人も使えます。

用途としてイメージしやすいのは、今回試したようにフィギュアや模型にエフェクトやUIを重ねる使い方でしょうか。市販されている製品であれば、ARReferenceObjectをインターネットで配布・共有できる点も魅力です。
また、物体の移動を検知できるので、チェスのような物の配置によって局面が変わるようなゲームにも使えそうです。

立体的な特徴が少なく検知が難しい場合、平面画像を検知・追従するARImageAnchorが代用できます。

『Unreal Engine 4 ゲーム開発入門』はこれからUEを始める人にオススメしたい

前回に引き続きUEを勉強中ですが、購入した書籍がよかったのでレビュー記事を書きました。

"リズちゃんが表紙のUnreal Engine入門書"、略して"リズ本"!!
UEを始めるなら、これは買うしかないと思っていました。
※書籍版とKindle版があります

「リズちゃん? 誰?」という方はこちら
弊ブログでも度々登場しているクエリちゃんの妹です。

オススメする理由

クエリちゃん推しだからだけではないよ。本当だよ。

著者が専門学校の先生

著者の荒川さんは普段からゲーム開発を教えている方だそうで、説明が非常にわかりやすいです。

UEだけではなく、Unityの入門書も執筆されていますね。

www.amazon.co.jp

画面キャプチャが多い

フルカラーになっており、画面キャプチャによる解説が多いです。
紙面の半分が画面キャプチャと言っても過言ではないです。

UEがブループリントで開発できる点もキャプチャでの説明に合っていて、コード(スクリプト)例などは全然出てこないですし、迷ったらキャプチャの通りに操作すればいいです。

開発に必要な要素が詰まっている

UEの操作方法に始まり、ブループリント、アニメーション、コリジョン、ナビメッシュ、UI作成、パッケージ化まで、初めてアプリを作るのに最低限必要な要素をしっかり押さえている感じです。

Webサイトで情報が更新されている

バージョン対応、正誤表、追加情報などが著者の運営するサイトで公開されています。

uebeginner.jp

サンプルプロジェクトの完成品やモデルデータもここからダウンロードできます。
また、書籍には収録されていなかった音関連(BGM、ボイス、SE)の情報も公開されています。

最新のバージョンには追従されていないこともありますが、そこは自分で検索して調べれば十分対応できました。例えば、被破壊性メッシュの作成が見当たらないケースでは"被破壊性メッシュ ue4"で検索できます。

非破壊性メッシュの作成について - UE4 AnswerHub

リズちゃん!

クエリちゃんではなくリズちゃんを表紙に起用したあたりにセンスを感じます。
なお、書籍内で開発するサンプルアプリの主役もリズちゃんです。

実際、動いてるリズちゃんは、なかなか可愛い。

クエリちゃんモデルは、ちょっぴりセクシーな感じに仕上がっております。

というか、UnityではSDクエリちゃんを使っていたので、セクシーさの概念はなかった。

f:id:jyuko49:20180224222021j:plain

感想

Unreal Engineの第一印象として、Unityよりもビューが豊富で機能も多そうなので、慣れるまで時間がかかりそうかなと思っていました。
でも、この本を読みながらUEの機能を色々さわっていると、なんとなくできた気分になってきます。

これって実は大事なことで、最初の一歩で躓くと、そこでもう諦めてしまうケースって多いと思うんですね。
初心者は情報量が多いと逆に混乱しますし、苦手意識が払拭されるだけで入門書としての役割は果たしていると思うので、対象読者とコンセプトがはっきりした良書という感じがしました。

Unreal EngineでARをやってみる

ARの開発環境では、Unityがデファクトスタンダードだと思いますが、Unityに使いにくさを感じることもあり、Unreal Engineも試してみることにしました。

そもそもなんでUnityを使ってたんだっけ?

ARを始めた当初(といっても1年半くらい前ですが)、最新のARデバイスと言えばHoloLensとTangoで、ネイティブ環境を除くとどちらも開発環境がUnity一択でした。

元々はUnityを一度も触ったことがなくて、積極的に選択したという訳ではなかったと思います。

Unityのつらみ

あくまで経験1年ちょっとの素人目線ですが、公式のスクリプトリファレンスが超絶わかりにくいです。
メソッドやプロパティが何をするためのものなのか、説明が1,2行でざっくりとしか書かれていないので、名称と型から雰囲気で察しながら実装しています。
一体、みんなどうやって開発しているのか...

当然、意図通りに動かないことも多く、有益な情報がないかとググる訳ですが、出てくる情報が軒並み古かったりするんですよね。

今までは、TangoやARCoreのドキュメント、サンプルコードを読み漁って対応していましたが、それ以上のことをやろうとすると想像以上に苦戦します。

というわけで、UEも使えるようになりたい。

Unreal EngineのAR対応

UEでARを利用するためのプラグインは、UEにビルトインされているようです。
"設定" > "Plugins" > "Augmented Reality"プラグインが見つかるので、"Enabled"にチェックを入れれば有効になります。

f:id:jyuko49:20180707081534p:plain

ビルトインで楽ではあるんですけど、ARKit/ARCoreのバージョンが上がったら、UEのバージョンも上げないとってことですよね?

この記事を書いている時点で、UEの最新バージョンは4.19、プレビュー版の4.20が出ている状況です。
UEのAR対応についての記事を見ると、ARKit 1.5、ARCore 1.2に対応しているのは4.20以上になっているので、プレビュー版を使わないと最新の機能は使えないみたいです。

Augmented Reality Overview

バージョンの追加は、Epic Games Launcherから簡単にできます。1バージョンで15GBくらいあるけどね…

f:id:jyuko49:20180707082724p:plain

ARKit

UE 4.20であれば、ARKit 1.5だけでなくARKit 2.0にも対応しているようです。

ARKit Prerequisites

ARCore

UE 4.20がARCore 1.2に対応しています。

ARCore Prerequisites

ARCoreに関しては既にv1.3.0が出ているのですが、UEのサイトには記述が見当たりません。
ARCoreのサイトを見てみると、GoogleがforkしたバージョンのUEをGithubからインストールしろと書かれています。

Quickstart for Unreal  |  ARCore  |  Google Developers

f:id:jyuko49:20180707075756p:plain

仕方ないので、リンク先のGithubをクリックしてみると…

f:id:jyuko49:20180707080023p:plain

ちょっと意味がよくわからないですね。

UEでARプロジェクトを作る

プロジェクトテンプレートから作る

UEにはARプロジェクトのブループリントがあり、簡単に開発をスタートすることができます。

f:id:jyuko49:20180707091825p:plain

手順としては、公式のQuick Startをそのままやっていくだけです。

Augmented Reality Quick Start

基本のビューは構成されており、ARKit/ARCoreのプラグインもデフォルトで有効になっているので、プラットフォーム(iOS/Android)の設定をして、デバイスで起動するだけです。
Androidの場合、SDKのセットアップで少し迷いましたが、以下の記事に載っているCodeworksを使ったら問題なく動きました。

Installing CodeWorks for Android 1R6u1

せっかくなので、どんな感じで動いているかも見てみました。

"コンテンツ" > "HandheldARBP" > "Blueprints" > "GameFramework"にある"BP_ARPawn"をダブルクリックします。

f:id:jyuko49:20180707105658p:plain

ブループリントのイベントグラフが表示されて、処理のフローが見れます。

f:id:jyuko49:20180707104220p:plain

メインフローとして、TouchイベントからHitTestが実行され、最終的にHitResultを受け取ったBP_PlaceableがSpawn(生成)されていることがわかります。
まだ全然慣れてないですが、処理のフローが可視化されているのはよいですね。

ちなみに、プルンと出てくる処理の正体は、BP_PlaceableのIntro Animのタイムラインアニメーションでした。

"コンテンツ" > "HandheldARBP" > "Blueprints" > "Placeable" > "BP_Placeable"をダブルクリックでイベントグラフが表示され、さらにIntro Animのノードをダブルクリックでタイムラインエディタが開きます。

f:id:jyuko49:20180707155733p:plain

f:id:jyuko49:20180707161532p:plain

ARCoreのサンプルプロジェクトを使う

UEのテンプレートとは別に、GoogleGithubにARCore SDK for Unrealリポジトリがあります。

github.com

こちらをgit cloneするか、zipファイルをダウンロードして解凍すれば、ARCoreのサンプルプロジェクトをベースに開発ができます。

プロジェクトごとにフォルダが作られており、直下にある.uprojectのファイルを開きます。

f:id:jyuko49:20180707112055p:plain

開くとUEのバージョン選択が表示され、Unreal Editorが起動します。

f:id:jyuko49:20180707112827p:plain

初回起動時にPluginの追加を促すコーションが表示された場合は"Yes"で起動すればOKです。

f:id:jyuko49:20180707113218p:plain

起動した直後は、Androidプラットフォームが有効になっていないので、Quick Startと同様の手順で設定します。
Androidであれば、"設定" > "プロジェクト設定" > "Android"でプラットフォームを有効にします。

f:id:jyuko49:20180707163110p:plain

実機で起動してみると、Unity版と同じようなサンプルアプリが動きました。

これならUEでも開発できるかなと思ったのですが、Cloud Anchorのサンプルに相当する"CloudPin"だけは起動しようとするとエラーになります。

f:id:jyuko49:20180707172929p:plain

UEのバージョンを4.20にしてもダメ。Not FoundになっていたGithubからARCore 1.3に対応したUEをインストールすればできるのかもしれませんが、Not Foundですし。

そもそも、Cloud AnchorはARCore 1.2で動くはずなのですけど、どうにも上手くいきません(諦め)

その他UE関連

ヒストリアさんのブログが半端ない

UEについて色々調べていたら、ヒストリアさんのブログの情報量がすごかったです。
これ読めば、UEかなり詳しくなりそう。

historia.co.jp

ありがとうの気持ちを込めて、Airtoneを宣伝しておく。

www.youtube.com

Unity引っ越しガイド

Unityの用語との対応表がありました。Unityで行なっていた操作がUEでどうやるのかわからないときは、見比べながら進めていけばなんとかなりそうです。

api.unrealengine.com

まとめ

  • ARKit/ARCoreの最新バージョン対応がUnityに比べると遅い
  • ブループリントは慣れれば便利そう

プレビュー版じゃないと最新のAPIに対応していない上、ARCore 1.3とCloud Anchorに至っては、すんなり試せなかったのが致命的です。
ブループリントは使いこなせれば便利だと思いますけど、慣れるまでに時間もかかりそう。面倒な処理はスクリプトをゴリゴリ書いていく派なので、その点もちょっと合わないかもしれません。

地味なところだと、AndroidiOSのプラットフォーム切り替えが要らないので、クロスプラットフォーム開発が楽です。

結論

UEよさそうだけど、ARならまだUnityかな・・・。

実際に試してみて、AR(特にモバイルAR)に関しては、依然としてUnityがファーストチョイスかなぁと思いました。
もしVRをメインでやっていくなら、UEを勉強する気がします。

ARKit/ARCoreやサードパーティ製のプラグインを使ってスクリプトを書いていきたい場合は、Unityで良さそうです。Unity自体の機能は、理解することを諦めた。
逆にUE使う場合は、ARの機能やスクリプトに頼る部分はシンプルにして、アニメーションやエフェクトなどグラフィックにこだわるとか、そういった使い方になると思います。

今後はWebARの利用範囲が徐々に増えていくと思うので、いずれはUnity/UEじゃなくて、Webと同じプラットフォームで開発していくことになるかと。

jyuko49.hatenablog.com