じゅころぐAR

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

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

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

ARCoreのビルドに失敗したらadbのserverプロセスをkillしてみるべし

ARCore v1.2.0を使っているプロジェクトにv1.3.0をインポートしたら、いきなりビルドエラーが出た。

f:id:jyuko49:20180622075937p:plain

うん、このエラーには見覚えがある。

対処法

adbのserverプロセスが悪さをしていることがあるので、ターミナルを起動してkillして差し上げる。

$ ps aux | grep adb
$ kill [プロセス番号]

adbコマンドが使えれば、以下コマンドでも同じはず。

$ adb kill-server

実際にやってみる

$ ps aux | grep adb
jyuko            10742   0.4  0.0  4339252   3820   ??  Ss    8:25AM   1:39.03 adb -L tcp:5037 fork-server server --reply-fd 4
jyuko            10559   0.0  0.0        0      0   ??  Z     8:25AM   0:00.00 (adb)
jyuko            14536   0.0  0.0  4267768    896 s000  S+    7:55AM   0:00.00 grep adb

$ kill 10742

ビルドできた!

どうして空間共有で先に座標系の逆変換(inverse)を行うのか?

先日、Cloud Anchorを使ったオブジェクト共有の記事を書きましたが、Matrix4x4を使った座標変換の説明で若干モヤっとしなかったですか?
Cloud Anchorを基点にした座標系で位置を伝えるのに、なんで送信側がCloud Anchor座標系への逆変換(inverse)を使うのかです。

正直、記事を書いている途中も半信半疑(でも実装上は正しい)という状態だったのですが、何か降りてきたようで、今ならわかりやすく説明できる気がする!

やりたいこと

Andy君がCloud Anchor、オレンジのピンが共有したい地点だとします。(説明を簡単にするため、とりあえず回転は考えない)

f:id:jyuko49:20180621125708p:plain

座標系が異なる他のデバイスに正しく位置を教えるには、Cloud Anchor(Andy君)から見てココ!というCloud Anchor座標系で位置を伝える必要があります。(下図の赤いベクトル)

f:id:jyuko49:20180621125811p:plain

座標変換には行列を使うのが簡単で、UnityではMatrix4x4を使えば良さそうです。

正変換→逆変換:NG

まず、間違えた実装の方から。

Matrix4x4にCloud Anchorのposition、rotationを与えると、デバイス座標系→Cloud Anchor座標系の変換を行う行列ができます。
Cloud Anchorの座標系に変換する行列なんだから、これで変換をかけた座標を渡せばいけるでしょ!
と、最初は思ってました。

実際に変換すると、デバイス→Cloud Anchorの座標変換と同じだけピンを移動(青い矢印)させた地点の座標が返ります。

f:id:jyuko49:20180621125610p:plain

結果としては上図の赤い矢印で、最初に求めたかったベクトルと違います。

この座標を別のデバイスに伝えて、逆変換をしてみましょう。
Cloud Anchorの座標系から見た座標が今求めたベクトルなので、Cloud Anchorを基準にデバイスの座標系に変換すれば、元に…

f:id:jyuko49:20180621125615p:plain

戻らないですね!(・∀・)

実機テストでこれだけ盛大に位置がズレたら、流石に計算が間違っていると気付きました。

逆変換→正変換:OK

それならばと送信側でMatrix4x4.inverseによる逆変換をかけてみました。押してダメなら引いてみろ理論です。

逆変換なのでオレンジのピンの座標に対して、Cloud Anchor→デバイスの変換(青い矢印)がかかります。

f:id:jyuko49:20180621125612p:plain

結果として得られる座標は、Cloud Anchorから見たピンの位置(求めたかったベクトル)をデバイス座標系で表したものです。

この座標を別のデバイスに伝えて、さらにCloud Anchor座標系に変換します。
今求めた座標をデバイス→Cloud Anchorで変換すると、

f:id:jyuko49:20180621125613p:plain

できてそう!!

まとめ

理論から入るのもいいですけど、とりあえず動く実装を見つけるのも大事ですね。
正しく動いていれば、理論は後から付いて来ます。