ARあるある言いたい

VR・ARについて書いていきます

Klakを使いUnityのモーションをパッチプログラミングする

Klakとはなにか。引き継いだプロジェクトに入ってたんですが面白そうなので触ってみましょう。

github.com

まずここからダウンロード

Releases · keijiro/Klak · GitHub

Latest ReleaseのUnity 2018.3 updateからKlak.unitypackageをダウンロードしましょう。

Unityは2018.3.14fを使用。

何はともあれプロジェクトにインポート。中身はこんな感じです。

f:id:mojo_nobu:20191110221733p:plain
Klak

Motion

まずはMotionフォルダを見ていきます。Motion/Runtime以下にあるスクリプトを任意のオブジェクトにアタッチすると決められた動きをしてくれます。

SmoothFollow

f:id:mojo_nobu:20191111092018p:plain
smoothFollow

常にTargetに指定されたオブジェクトに向かっていきます。

f:id:mojo_nobu:20191111093409g:plain
smoothFollow

ConstantMotion

f:id:mojo_nobu:20191111093606p:plain
Constant Motion
指定した動きをし続けてくれます。画像の設定ではアタッチしたオブジェクトをスピード1でx軸に沿って移動させ続けてくれます。

f:id:mojo_nobu:20191111094849g:plain
Constant Motion

Cubeに設定した場合の動きです。プレイ中でも設定を変えると反映されます。

BrownianMotion

f:id:mojo_nobu:20191111095316p:plain
BrownianMotion

この設定にして動かすと、

f:id:mojo_nobu:20191111095614g:plain
Brownian Motion

えーっと、これはブラウン運動ってやつをしてくれてるみたいです。詳しくは

ブラウン運動 - Wikipedia

を参照で…

Patching

Klakを使えばUnity上でモーションのパッチプログラミングができます。

まず適当なオブジェクトにKlak/Wiring/Runtime/System/Patchをアタッチ。するとInspectorにOpen Patchのボタンが表示されるのでこれをクリック。

f:id:mojo_nobu:20191111102119p:plain
Patch

するとパッチプログラミングの画面が表示されます。ここで右クリックしてノード追加し、ノード同士をパッチでつなぎ、させたいモーションをプログラミングすることができます。

f:id:mojo_nobu:20191111102235p:plain
Patchプログラミング

基本的な流れとしてはInputから入ってくる値をパッチで変化させてOutputで指定したオブジェクトのパラメータを変えていくことができます。パッチは多数用意されていますが、ここではシンプルなものを流れに合わせて紹介します。

Patcherの作り方

まずPatcherウィンドウで右クリックして、Create/Input/Mouse Position Inputを選択してノードを作成します。続いてCreate/Conversion/From To Vector、Create/Output/Component/Transform Out作り、それぞれをパッチで結線します。

f:id:mojo_nobu:20191111104047p:plain
Patch1

次にFrom To Vectorを選択してInspectorで画像のように設定します。

f:id:mojo_nobu:20191111105059p:plain
From To Vector

これは入力(Mouse Positionのx軸の値)が変化すると、ここで設定したFromからToの値への各Vector値の変化が、続くノードに伝えられます。

次にTransform Outを選択してインスペクタで動かしたいオブジェクトをTarget Transformにアタッチします。

f:id:mojo_nobu:20191111105142p:plain
Transform Out

結果、こういう動きになります。(キャプチャの都合上見えないけどマウスのX軸の動きに連動)

f:id:mojo_nobu:20191111110032g:plain
Patcher1Movie


もう少し動かしてみましょう。次はConversion/Axis Rotationを使って次のように繋げます。

f:id:mojo_nobu:20191111110244p:plain
Patcher2

Inspectorの設定はこう。

f:id:mojo_nobu:20191111110406p:plain
Axis Rotation

動かすとこうなります。マウスのX軸の動きがCubeのX軸の動き、マウスのY軸の動きがCubeのY軸回転に対応するようになりました。

f:id:mojo_nobu:20191111110626g:plain
Patcher2Movie


大体こんな感じです。PatcherのInputにはゲームコントローラーやキーボードのキー入力なども用意されているので、それらを使ってインタラクションを使うのに便利そうです。Patcherのノードを作ったりの拡張もできるんだろうか?できそうな雰囲気。

Trilibを使ってFBXファイルをHoloLensで読み込む

Unity EditorおよびHoloLens(UWP)上で、FBXファイルをスクリプトから読み込みたい。そういうとき、ありますよね。

そんなときは、こちらの「TriLib - Model loader package」を使います。

assetstore.unity.com

環境

  • Unity 2018.3.14f1
  • HoloToolkit-Unity 2017.4.3.0
  • Visual Studio 2017 Version 15.9.13

使い方

基本的な使い方は開発者サイトで見られます。

ricardoreis.net

しかし、UWP用には少し工夫が必要です。マニュアルには「windows store用アプリ(つまりUWP)で動作させるにはzipファイルとして読み込む必要がある」とあるのですが、これはfbxをzipに圧縮するという意味ではありません。
LoadFromMemoryWithTexturesメソッドにファイルのバイト配列を渡す際に、そのファイルの拡張子がzipだと伝えればOKです。

UWP用の変更はUnity Editor上でも問題なく動くので、#UNITY_UWPディレクティブなどは使わず次のように実装します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using TriLib;
using System.IO;
using System;

public class FbxLoader : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // FBXファイルはStreamingAssets以下に設置
        loadFbx(Application.streamingAssetsPath + "/crystal.fbx");
    }

    void loadFbx(string filePath)
    {
        // ファイルのバイト配列取得
        byte[] fileBytes = File.ReadAllBytes(filePath);

        using (var assetLoader = new AssetLoaderAsync())
        {
            try
            {
                // ロード時オプションを作成
                var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
                // このオプションをtrueにしないとMixedRealityのプロジェクトが正しく動作しない
                assetLoaderOptions.UseOriginalPositionRotationAndScale = true;

                // バイト配列と拡張子がzipだと伝える
                var loadingThread = assetLoader.LoadFromMemoryWithTextures(fileBytes, "zip", assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                {
                    // ロード完了後に行われる処理
                    // ロードされたfbxのモデルはloadedGameObject割り当てられる
                    Debug.Log("Load Finished");
                });
            }
            catch (Exception exception)
            {
                Debug.Log("exception");
            }

        }
    }
}

とりあえずUnityでこれを実行してみる。

f:id:mojo_nobu:20190623191303p:plain
出ました。なんかデカい

サイズが気になりますが(後述)、とりあえず先にHoloLensで出してみましょう。

プロジェクトをUWPへ書き出すとき、次のようがエラーが出ます。

Assets\TriLib\TriLibExtras\Scripts\AvatarLoader.cs(397,40): error CS0619: 'HumanDescription' is obsolete: 'HumanDescription is not supported on .NET scripting backend.'

Assets\TriLib\TriLibExtras\Scripts\AvatarLoader.cs(408,31): error CS0619: 'AvatarBuilder.BuildHumanAvatar(GameObject, HumanDescription)' is obsolete: 'BuildHumanAvatar is not supported on .NET scripting backend.'

ヒューマンまわりのエラーが出てます。今回はヒューマンモデルは対象外として、TrilibExtra以下のファイルも使わないのでフォルダごと削除してしまいましょう。

HoloLensで出してみました。

f:id:mojo_nobu:20190623211447j:plain
やはりデカイ

サイズ表示の問題、対策

基本的な使い方は以上です。ここからはサイズの問題について見ていきます。

上記の実行結果は想定していたよりも相当大きいサイズで表示されていました。これはtrilibではFBXファイル内の1単位(インチだったりメートルだったり)の長さを見ておらず、unityのスケール設定(メートル)を適用していることが原因です。

対策は2つ考えられます。

対策① モデルのスケール設定をメートルにしてから出力する

ファイルのほうをプロジェクトに合わせます。モデルの1単位当たりの大きさをメートルに設定してからfbxに出力すればOKです。
3ds Maxでは、メニューからCustomize/Units Setup.../System Unit Setupと進んでいき、System Unit Scaleの値を「1Unit = 1.0 meters」にすればOKです。

f:id:mojo_nobu:20190624103651p:plain
metersに設定

対策② FBXをascii形式で出力してUnits設定を取得する

FBXにはascii形式とbinary形式があります。ascii形式の場合その中身は文字列なので、そこから1単位当たりの大きさの値を取ってくることができます。

f:id:mojo_nobu:20190624104019p:plain
ここの"UnitScaleFactor"の値を取得する

取ってきた値をロードされたgameObjectに適用すればOKです。コードとしては以下になります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using TriLib;
using System.IO;
using System;

public class FbxLoader : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        loadFbx(Application.streamingAssetsPath + "/crystal_ascii.fbx");
    }

    void loadFbx(string filePath)
    {
        byte[] fileBytes = File.ReadAllBytes(filePath);

        float scaleFactor = 1f;
        // binaryかどうかの判定
       // (参考)https://www.slideshare.net/L1048576/fbx-1-1
        if (fileBytes[21] == 0x1a & fileBytes[22] == 0x00)
        {
            Debug.Log("binary");
        }
        else
        {
            Debug.Log("ascii");
            string asciiText = System.Text.Encoding.ASCII.GetString(fileBytes);
            foreach (string line in asciiText.Split('\n'))
            {
                if (line.Contains("UnitScaleFactor"))
                {
                    string[] tips = line.Split(',');
                    scaleFactor = float.Parse(tips[tips.Length - 1]) / 100f;
                    break;
                }
            }

        }

        using (var assetLoader = new AssetLoaderAsync())
        {
            try
            {
                var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
                assetLoaderOptions.UseOriginalPositionRotationAndScale = true;

                var loadingThread = assetLoader.LoadFromMemoryWithTextures(fileBytes, "zip", assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                {
                    // ロード完了後に行われる処理
                    // ロードされたfbxのモデルはloadedGameObject割り当てられる
                    Debug.Log("Load Finished");
                    // モデルにサイズを適用
                    loadedGameObject.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);
                });
            }
            catch (Exception exception)
            {
                Debug.Log("exception");
            }

        }
    }
}

ascii形式のFBXはサイズが大きくなりがちなので、どちらの方法を選択するかは検討の余地があるかと思います。

HoloLensのアプリがバックグラウンドになるときにMediaCaptureを止める

HoloLensで、アプリ内の機能としてMixed Reality Captureのような録画機能を実装するためにはMediaCaptureというUWPのクラスを使用します。

参考 : HoloLensでMediaCaptureを使おう | D.YAMA Blog

ありがたいことにMediaCaptureを使って録画をしている間、自分で何もコード書かなくてもアプリの視界ではこんな感じ↓のカメラのアイコンが点滅してユーザーに録画中であることを伝えてくれます。

f:id:mojo_nobu:20180729164300p:plain

こんな便利なMediaCaptureですが、録画中にブルームしてアプリから抜けた(アプリをバックグラウンドにした)場合、カメラのアイコンが点滅したまま残ってしまいます。またアプリを抜けた後の視界の表示も、録画中と同じ30fpsのままになってしまっているようです。アプリウィンドウを閉じるなどしてアプリを終了させるとこの状態は解除されます。
ちなみに録画中だった映像はファイルストリームが閉じられておらず、再度アプリをフォアグラウンドにして録画停止処理をするまで保存されません。録画停止処理をした場合、保存されるのはブルーム前までです。つらいですね。

これを避けるために、UWPのライフサイクルを制御するCoreApplicationクラスに、MediaCaptureの録画停止処理をしてもらうようにします。

参考 : Windows 10 UWP アプリのライフサイクル - UWP app developer | Microsoft Docs

こんな感じで書けばOK。

using Windows.ApplicationModel.Core;
using Windows.ApplicationModel;

public class MediaCaptureManager
{
    private MediaCapture _mediaCapture;

    void Initialize()
    {
        // MediaCaptureの初期化
        _mediaCapture = new MediaCapture();
        try
        {
            await _mediaCapture.InitializeAsync();
        }
        catch (UnauthorizedAccessException)
        {
            Debug.Log("The app was denied access to the camera");
        }

        //  バックグラウンドになるときCoreApplication.EnteredBackgroundイベントが呼ばれる
        CoreApplication.EnteredBackground += app_EnteredBackground;
    }

//// 略

    // アプリがバックグラウンドになるときに行う処理
    private void app_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
    {
        Task.Run(async () =>
        {
            // 録画を終了
            await _mediaCapture.StopRecordAsync();
            // 明示的な終了。アプリはバックグラウンドになりこれより後ろにコード書いても実行されない
            e.GetDeferral().Complete();
        });
    }
}

開発環境はUnity2017.4.5f1。HoloLensのOS buildは10.0.17134.165です。
そのうちアップデートで勝手に終了してくれるようになる気もする。

MagicLeapの「ライトフィールド」とは何か

MagicLeapの外観と発売時期が発表されました。

www.moguravr.com

もう半分忘れかけていましたが、本当に出るんですね。 価格や入手方法など気になることはいろいろありますが、仕様となっている「デジタル・ライトフィールド」とはなんじゃらほい。

検索してお勉強してみました。

デジタル・ライトフィールド

「ライトフィールド」という単語は、ARでは主に映像の見え方に関係してきます。

通常画像データ、映像データとは、ひとつの焦点でされたものを指しますよね。画像データは1枚のjpg、映像データは数分間の、同じ見え方だけで見られるmpegといった具合に。

画像も映像も、光の見え方を記録したものです。とある時点のとある地点で、カメラに入ってきた光の集合を「点で」記録します。ライトフィールドとはその名の通り「光の場」を意味し、ライトフィールドカメラはカメラに入ってきた光を「線で」記録します。この光線の集合を、ここでは「光線群」と呼ぶことにします。

光線群を丸ごと記録すれば何ができるか、代表的なものは「映像の焦点の変更」です。カメラであればとりあえずライトフィールドを記録しておいて、あとで焦点を変えればいいやーっていうのができるわけですね。

MagicLeapのようなシースルーのARグラスにおいては、使用者の目の焦点に応じてオブジェクトの見え方を変えることができれば、現実感が大幅に向上します。ライトフィールドを使えばそれが可能になるわけですね。

実現方法は色々あるようで、Panoraの記事では「光線をそのまま出力」、funlifeの記事では、「視線トラッキングによって表示画像を随時切り替える」といった方法が紹介されています。

MagicLeapには視線トラッキングが搭載されているので後者なんじゃないかなーと思うのですが、実際どうなるのかは不明です。

参考

原理的な話

Light Field Imaging by Kawaguchi Tatsuya

Light field - Wikipedia

ところでWikipedia日本語版にはライトフィールドの記事がない。

かみ砕いた説明

究極のAR-HMDを目指して【ティーポットの独り言】

Light Field – ARを知ってる風に語るには理解しておいた方がいい最新技術 – FunLife株式会社

その他(ちゃんと読めてないけど、気になる)

https://www.ite.or.jp/contents/annual-rep/1701nenpo.pdf

「空間再生」実現のカギは超高精細技術や安価な光源 - 日経テクノロジーオンライン

誤解だらけの「ホログラム」 それっぽい映像表現との違いは? (2/2) - ITmedia PC USER

from:AmadeusSVX ライトフィールド - Twitter Search

あるしおうねさんのtwitter。いつも勉強させていただいてます。

その他雑感

この辺の知識を系統立てて学べるような、ちょうどいい参考書はないものか。上記リンクの参考文献とかいろいろ当たってみるしかないのかな。

HoloLensでARToolKitを使ってみた

Oculus Rift Advent Calendar 2017の8日目の記事です。

qiita.com

ARToolKitとは、カメラに映るマーカーや任意画像の位置や角度を認識してくれるライブラリで、類似のVuforiaなどと異なる点はオープンソースであることです。無料で利用できます。

今回はこれをHoloLensで使ってみます。

HoloLensARToolKit

HoloLensARToolKitというライブラリを使います。

github.com

「releases」のリンクから最新のv0.2(HoloLensARToolKit-v0.2.zip)をダウンロードしましょう。

ちなみに対応しているUnityバージョンは5.6.xとのことですが、今回僕は2017.1.0f3でやりました。

とりあえず動かす

まずはサンプルプロジェクトを動かしてみましょう。

Unityを起動し、ダウンロードした「HoloLensARToolKit-v0.2.zip」内の「HoloLensARSample」プロジェクトを開きます。次にUnityメニューのAssets/Import Package/Custom Package...から、同じく「HoloLensARToolKit-v0.2.zip」内のARToolKitUWP.unitypackageを選択し、インポートします。 色々警告が出ますが、特に問題ないので無視します。

Sampleフォルダ内にサンプルシーンが複数あります。HoloLensARToolKitCoordsシーンを動かしてみましょう。 シーンを読み込むと、カメラやplayer settings、quality settingsも準備されているので、そのままビルドできます。

f:id:mojo_nobu:20171208185821p:plain

ビルドしてHoloLensで起動したら、以下の本家ARToolKitgithubリポジトリから以下の画像を取ってきて見てみましょう。

github.com

f:id:mojo_nobu:20171208190553j:plain

はい。少しずれたり、不思議な挙動したりしてますが、現時点では細かいことは考えず先に進みましょう。

動作を見ていく

HoloLensARToolKitCoordsシーンをいじりながら動作を見ていきましょう。 主な働きをするのはARUWP Controllerオブジェクトです。

f:id:mojo_nobu:20171208062727p:plain

ARUWPControllerコンポーネント

名前が同じで紛らわしいですが、ARUWPControllerオブジェクトに含まれるARUWPControllerコンポーネントが、このライブラリのコアになります。このコンポーネントはHoloLensARToolKitを使用するプロジェクトに一つ以上含んではいけないことになっています。

f:id:mojo_nobu:20171208064509p:plain

Pattern Detection Modeは、認識対象となる画像の種類を指定します。 「AR_TEMPLATE_MATCHING_COLOR(MONO)」は任意画像のマーカー、「AR_MATRIX_CODE_DETECTION」はARマーカー(後述)を認識したいときに指定します。

今回は両方を認識したいので「AR_TEMPLATE_MATCHING_MONO_AND_MATRIX」を指定します。

Matrix Code Typeは認識するARマーカーのサイズ(粒度)を指定します。今回はAR_MATRIX_CODE_3x3です。

オプションの詳しい説明は以下をご参照ください。

longqian.me

ARUWPMarkerコンポーネント

ARUWPMarkerコンポーネントは、ARToolKitが認識する画像と、認識後に表示するオブジェクトを管理します。

ARMarkerを認識する

まずARマーカーの認識するようにしてみましょう。

ARToolKitは、最初から特定のデータセット(ARマーカー)が認識できるようになっています。

(ARマーカーについてはこちらを参照)

まず、typeを「Single_barcode」にセットします。 f:id:mojo_nobu:20171208191328p:plain

「Barcode ID」を指定することで、単一のARマーカーを認識できるようになります。前述の本家ARToolKitgithubリポジトリ内のここ)から、ID別のARマーカー画像を取得することができます。

「Size in mm」で実際に認識するマーカーサイズをミリ指定し、「Visualization Target」で表示するオブジェクトを指定します。

基本的にはこれでOKですが、実はHoloLensでこのまま動かすと、IDが異なるARマーカーを区別できない、という結果になると思います。最初のサンプルシーンでも一つ画像を認識すると複数のオブジェクトが追従してきたかと思いますが、これらは「マーカーっぽいのは認識できているが詳細に区別できていない」ことが原因です。

この対策として、「Advanced Options」以下の「Confidence Cutoff」の値を変更します。

デフォルトは0.5ですが、大体0.8くらいにすればマーカーの区別ができるようになるかと思います。大きすぎると今度は正しいマーカーも認識されなくなるので注意してください。

任意マーカーを作って認識する(ただし制限あり)

次に、任意の画像を使って認識します。ARToolKitで任意画像を認識するには、対応したパターンファイルを生成する必要があります。パターンファイルは以下を使って生成します。

ARToolKit Marker Generator Online

このページ、chromeでは正常に動作せず、Edgeで動作確認できました。

で、実際やってみるとわかるんですけど、ARToolKitで読み込むマーカーって4辺が指定の比率の枠線で囲まれた四角形じゃないといけないっぽいんですよね~(参考)なので、そういう画像を用意します。

f:id:mojo_nobu:20171208192430p:plain

patファイルを取得したら、Unityに戻って取得したpatファイルをStreamingAssetsフォルダ以下にインポートします。

ファイルが準備できたら、ARUWPControllerオブジェクトのARUWPMarkerコンポーネントに戻って、typeを「Single」にします。

f:id:mojo_nobu:20171208192133p:plain

「File」に先ほどインポートしたpatファイルを拡張子まで書き、「Size in mm」、「Visualization Target」、「Advanced Options」以下の「Confidence Cutoff」を指定します。

これでビルドしてみましょう。

f:id:mojo_nobu:20171208193047j:plain

これで任意画像の認識ができました。

なんかずれてない?

はい、実はコレずれています。どうやらHoloLensのカメラと装着した人間の目の位置のずれが十分に補正されていないようです。

この件に関して、githubリポジトリの以下で解説がされています。

github.com

github.com

どうやらARUWPMarker.csの844行目以降にあるMagicMatrixの値を変更し、各自の目の位置に正確に表示してくれ、とのことです。

    private void InitMagicFunction() {
        magicMatrix1.SetRow(0, new Vector4(0.912590966818f, 0.00264415233376f, -0.0128817795355f, 0.000747730441087f));
        magicMatrix1.SetRow(1, new Vector4(-0.00522566480145f, 0.906864775491f, -0.0896724176439f, 0.0177872745822f));
        magicMatrix1.SetRow(2, new Vector4(-0.0232595319748f, 0.00845030987907f, 0.887221375125f, 0.0730807968318f));
        magicMatrix2.SetRow(0, new Vector4(0.999970522935f, 0.00515075228031f, 0.00569412074919f, 0.0f));
        magicMatrix2.SetRow(1, new Vector4(-0.00483183598788f, 0.998492806032f, -0.0546696411472f, 0.0f));
        magicMatrix2.SetRow(2, new Vector4(-0.00596712838354f, 0.0546405165891f, 0.998488260985f, 0.0f));
    }

    /// <summary>
    /// Perform the magic function, add distortion to the tracking result matrix. [internal use]
    /// </summary>
    private void MagicFunction() {
        if (performMagicFunction) {
            Vector4 vp = magicMatrix1 * latestTransMatrix.GetColumn(3);
            latestTransMatrix = magicMatrix2 * latestTransMatrix;
            latestTransMatrix.SetColumn(3, vp);
        }
    }

変更後は、ARUWPMarkerコンポーネントの「Apply Undistortion Function」を有効にすればよいとのことです。

f:id:mojo_nobu:20171208194431p:plain

今回はここまでです!MagicFunctionの設定方法はまたいつか。

明日は...

Oculus Rift Advent Calendar2017、明日はm2wasabiさんによる記事です!

qiita.com

これまでのARとこれからのAR

9月20日、iOS端末上でARをサポートするARKitの機能を搭載したiOS11がリリースされました。 それを追う形で、Googleは同様にAndroid端末上でARをサポートするARCoreを開発中です(現在SDKのプレビュー版を公開中)。

「これまでもスマホでARができたのに何が違うの?」と思う方もいるかと思います。この記事ではスマホを中心にこれまでのARとこれからのARを、その技術の変化とともに時系列に紹介することで、今後の展望について考えます。

用語解説

マーカーAR

ARにはマーカーARマーカーレスARがあります。

これまでに広く使われてきたのはシンプルなマーカーARで、これは単純に前もって登録された画像を認識することで、その画像の場所にオブジェクトを表示させることができます。認識の対象は如何にもといった感じのマーカー(QRコード、ARタグなど)にとどまらず、自分で任意の画像を認識対象にすることもできます。メリットは「処理負荷が低く精度が高い」、デメリットは「マーカー等の事前準備が必要」「利用シーンが限られる」、です。

ARタグを使ったマーカーAR

www.youtube.com

任意の画像を使ったマーカーAR

www.youtube.com

マーカーレスAR

一方で、このマーカーを必要としないものがマーカーレスARです。これは周囲の環境を認識することで、表示させるオブジェクトと環境とのインタラクションを可能にします。これによってCGのキャラクターに床を走り回らせたり壁の前で方向転換させたり、そういうことができるようになるわけです。この手法のメリットは「マーカーが不要」「非常に多様な表現が可能」、デメリットは「処理負荷が高く、精度が求められるものは不向き」「アプリケーションのデザインが難しい」となります。

マーカーレスAR

www.youtube.com

「マーカーレスAR」の定義は諸説あるようで、QRコードやARコードに限らない任意の画像を使用可能なものをマーカーレスARと呼んでいる文章も見受けられました。 正確な定義はわかりませんが、本稿では「マーカーAR」「マーカーレスAR」を上記の意味として扱います。

SLAM

マーカレスARに必要な技術がSLAM(Simultaneous Localization and Mapping)です。wikipediaの言葉を借りると「自己位置推定と環境地図作成を同時に行うこと」になります。「自己位置」とはカメラの位置(と向き)、「環境地図」は床があり壁があり、ある地点からある地点までの距離が何mであるかといった周囲の環境を意味します。つまりSLAMによってディスプレイに表示される映像が決定されることになります。

スマホARの変遷

2017年8月までのスマホAR

ここではマーカーARがメインの機能となります。 これまでiOSAndroidも、OSレベルでARをサポートしていませんでした。しかしOSレベルでサポートされていなくてもARは実現可能です。単にプログラミングで実現すれば良いのです。

さらにARの技術を持っていない開発者でも、有償無償の各種ライブラリを使うことで、基本的なマーカー認識などのAR機能を持ったアプリケーションを作ることができます。ARライブラリにはVuforia, SmartAR, Kudanなど、機能や価格の異なる様々な種類があるので用途に合わせて選びましょう。SmartARとKudanは、制限があったり精度が甘かったりしますが、マーカーレスARができます。

「プログラムだけでARができるなら、もうそれで良くない?」と思う方もいるかと思いますが、そうではないのです。

SLAMの一手法に、現在広く使われているvSLAMというものがあります。これはカメラから入ってくる画像の特徴点(机の角とかカーペットの模様とか。尖ったものがよく選ばれる)の動きを利用して空間を把握する手法ですが、これは非常に計算負荷が高いんですね。

vSLAMを使った空間認識の様子

www.youtube.com

これまでのスマホARではカメラ画像に加えてスマホのジャイロや加速度センサーを補足的に使うことでこれをカバーしていました。カバーしていたとは言ってもこの時点のOSから見ればカメラと各種センサーはそれぞれ独立した機能で、それらを連携させるにはOSよりも高レイヤーの部分でプログラムする必要があったのです。これでは効率が悪い。

OSレベルでARをサポートするということは、端末の持つ各センサーのARのための連携をOSが提供することで、これまでよりも高負荷な処理(具体的には精度の高いマーカーレスARなど)を可能にする、ということです。

Project Tango(2016年後半~2017年8月)

Googleが推進していたProject Tangoというものがあります。これは一般的なスマートフォンにARのための広角カメラ(特徴点検出用)、赤外線センサーを追加し、さらに各種センサーとの連携をOSレベルで提供する端末を提供するものでした。

Tango対応端末は通常のスマートフォンよりもはるかに高精度、高機能(壁検出、オクルージョン、Area Learningなど)なARが実現可能でしたが、ARCoreの発表とともにTangoは終了となりました。市販された対応端末はLenovoのPhab2ProとAsusのZenFoneARの2機種のみ。Area LearningなどはARKitやARCoreでも実現できていないので、Tangoの終了はとttttっても残念です。

しかし今広く普及しているスマホも、今後は世代が進むにつれCPU性能の向上やセンサー類の追加で結局はTango端末のようなものに向かっていくのではないでしょうか。Project Tangoは早すぎたのだと思います。

Tangoによる高精度、オクルージョンを含むAR表現

www.youtube.com

2017年9月以降のスマホAR

と、ここまで書いてみるともうこの項で書くことはあまりないですね。

iOSのARKit、AndroidのARCoreともに、OSレベルでARをサポートすることでこれまでより高性能なARがスマホで実現できる、ということになります。OSレベルのサポートがあるといってもCPUの性能は必要で、ARKit, ARCoreともに対応する端末が限定されているのが現状です。この点では端末のラインナップがコントロールされているiPhoneのARKitに大きなアドバンテージがあると言えるでしょう。Tangoが一足飛びに進んでしまい誰もついていけなかったARの道を、これからは処理能力の向上とともに多くのスマートフォンが少しずつ進んでいくことになります。

ADF(Area Description File)の取得方法

AreaLearningとは

TangoにはArea Learningという機能があり、デバイスを持って周囲を撮影しながらウロウロしていると、デバイスがその場所を覚えてくれます。これはどういうことかというと、現実空間に置いた3Dオブジェクトの場所をセーブ/ロードできる、ということです。 例えばAreaLearningしながらナビゲーション用の3Dオブジェクトとかを配置しておくと、その後はその場所でいつでもナビゲーションを高精度で復元できるようになります。

(まだArea Learningを触ったことのない方は、Tango SDK for Unityに含まれているAreaLearningシーンをビルドして実際に試してみるのが良いでしょう。操作方法についてはここでは割愛します。)

やりたいこと

こうなってくると次はセーブデータを他のユーザーと共有したくなりますよね?Tangoでは、この覚えた場所の情報をADF(Area Description File)と呼んでいます。素直に考えるとADFはArea Learningを行った端末のどこかのディレクトリ以下に置いてあって、それをコピペとかすればいいような気がします。しかしファイルエクスプローラーを使っても公式ドキュメントを探しても、ADFの具体的なファイルパスは見つかりません。

解決方法

どうやらADFは、ファイルパス指定の方法ではアクセスできない形で、同一端末においてアプリケーションの区別なくまとめて保管されているようです。

ではどうやってADFを取得すれば良いのか?SDKで用意されているExportToFile(string filePath)メソッドを使用します。

使い方は以下の通りです。

AreaDescription[] ads = AreaDescription.GetList(); // 端末内の全ADFの情報を配列に呼び出し
AreaDescription m_areaDescription = ads[0]; // 簡単化のため先頭のADFを取得対象にする
m_areaDescription.ExportToFile(Application.persistentDataPath); // 引数は任意のパスを指定可能

これで指定したパスにADFが出力されます。ちなみにADFはバイナリファイルで、Tango端末内でしか扱うことができません。Tango端末でスキャンした部屋とか物体をメッシュにしてobjファイルとかで扱いたい、という場合はTango SDK for Unity内にあるExperimentalMeshBuilderWithColorシーンなどが参考になるかもしれません。

参考 Tango.AreaDescription Class Reference  |  Tango Unity SDK  |  Google Developers