Wikitude SDKで「AR(拡張現実)」スマホアプリをお手軽開発[PR]

GrapeCity Garage Wikitude SDKで「AR(拡張現実)」スマホアプリをお手軽開発[PR]

AR(拡張現実)開発を始めよう! 複数ターゲットの認識と、距離・角度の把握(Wikitude活用、iOS編)[PR]

2017年10月11日

2人の相性を写真から診断するアプリを、画像認識型ARで開発。ターゲット写真を認識してARオブジェクトを表示し、2枚のターゲット間の距離と角度が一定の条件内になれば診断を開始する仕組みだ。

デジタルアドバンテージ 一色 政彦
  • このエントリーをはてなブックマークに追加

 約1年ぶりになるが、すでに3本の記事を公開している本連載に、新たな記事を2本ほど追加する。

  • 連載 第1回: AR(拡張現実)とその開発技術、ライブラリ、Wikitudeの概説
  • 連載 第2回: ロケーション型AR開発入門(iOS編)と、アーキテクチャと、開発資料
  • 連載 第3回: ロケーション型AR開発入門(Android編)と、デバッグ手法
  • 連載 第4回(今回): ターゲット「画像」認識型のAR(iOS編)
  • 連載 第5回(次回): ターゲット「物体」認識型のAR(Android編)

 今回(10月)は画像認識型のAR(iOS編)で、次回(11月)は物体認識型のAR(Android編)の特徴と開発方法を紹介する。これにより、Wikitudeが提供するロケーション型/画像認識型/物体認識型という3つの基本機能を、本連載を通して開発体験できるようになる。

 以下では、第1回と第2回、第3回の後半(デバッグ手法)を全て読んで理解していることを前提とする。Wikitudeの基本的な開発方法について分からない、あるいは忘れている部分がある場合は、これらの記事を再読してから読み進めることをお勧めする。本稿はこれまでの連載の続編なので、用語などの基礎知識に関する説明は割愛するので、ご了承いただきたい。

 それではさっそく本題に入ろう。

1. ターゲット画像認識型AR(JavaScript API)開発のポイント

 まずは今回、どのようなサンプルアプリを作るのかを紹介する。

1.1 サンプルアプリ「顔認識で相性診断ARアプリ」の内容

 今回のサンプルアプリは、

アプリ上のカメラビュー内に2枚の顔写真を映すとARのターゲットとして認識され、その2枚の写真が「縦向きに横に並べられ(=角度が10度前後の誤差)」て「距離が1 cm以内に近づけられる」という一定の条件を満たすと、相性診断の処理が開始され、2人の診断結果が画面上部に表示される

というものだ。ユーザー視点で見ると、サンプルアプリの使用例は図1のようになる。

顔写真を準備する

顔写真を準備する

サンプルアプリのカメラビューで写真を表示する

サンプルアプリのカメラビューで写真を表示する

顔写真がターゲットとして認識され、ハート形のARオブジェクトが表示される

顔写真がターゲットとして認識され、ハート形のARオブジェクトが表示される

一定の条件を満たすと、相性診断の結果が画面上部に表示される

一定の条件を満たすと、相性診断の結果が画面上部に表示される

図1 サンプルアプリの使用例

 このサンプルを開発するためのAR技術のポイントは下記の2点となる。

  • 複数のターゲットを認識して、その上にARオブジェクトを表示する
  • ターゲット間の位置関係(距離と角度)を監視し、一定の条件を満たすと処理を開始する

 実際の開発内容を説明する前に、その開発環境を準備しておこう。

1.2 開発環境の準備

 Wikitude SDKを用いたiOSアプリをXcodeで開発する環境を構築するまでの手順は第2回の説明と同じなので、本稿では説明を割愛する。

 ただし2つほど注意点がある。

注意点 1: 無償トライアルライセンスキーの利用について

 本稿のサンプルアプリでは、Azureで提供されているCognitive ServicesのFace APIを使って顔写真から年齢や各感情の値を取得し、その近似度に基づき相性の度合(%)を算出している(算出方法自体は無意味で何の理論もない)。そのFace APIに送信するJPEG画像は、カメラビューを含むアプリ全体のスクリーンキャプチャなので、無償トライアルライセンスキーにより画面全体に多数の「Trial」文字がウォーターマークとして表示されている状態では、ウォーターマークが邪魔をしてFace API側の年齢・各感情の値取得に失敗してしまう。

 よって、このアプリを正常に使うには有償のライセンスが必要になる。が、サンプルアプリを手軽に試せるように、疑似的にAPIの実行結果を返すようコーディングした。つまり、「開発を体験したい」という目的の場合は、取りあえず無償トライアルライセンスキーがあれば十分である。

注意点 2: Wikitude SDK最新版(7.x.x)

 連載第2回を執筆した時点ではWikitude SDK(iOS) 5.1.4というバージョンを使ったが、執筆時点の最新バージョンは7.1.0となっている。基本的なプログラミング方法に変更はないのだが、いくつかのAPIに変更があり、削除されたり、非推奨になったりしている。具体的には次のリンク先を参照してほしい。

 また、2017年9月20日にリリースされたiOS 11を正式にサポートしているのは7.1.0となっているので、バージョンが適切かを確認してほしい。筆者自身もサンプルアプリを、iOS 11向けにWikitude SDK(iOS) 7.1.0で開発した。

1.3 Wikitudeアプリ開発のひな型コードの作成

 環境の準備が整ったら、XcodeでiOSアプリのプロジェクトを作成して、Wikitudeアプリ開発のひな型コードを用意する。この手順も前回と同じで、

を参考に進めればよい。

 Wikitudeの開発は、iOS Architect SDK APIを用いた「iOS(Objective-C)側開発」と、Wikitude SDK JavaScript APIを用いた「ARchitect World側開発」の2つに分けられる(第2回で説明済み)。両者の関係を示したのが図2で、iOS側のビューコンポーネントARchitectViewに、ARchitect Worldがロードされる仕組みになっている。

図2 iOS(Objective-C)側とARchitect World側の関係

 iOS側開発については、ちょっとしたパラメーター調整などはあるものの、ほとんどの場合では定型のコードとなるので、今回も説明を割愛する。詳しくは、下記リンク先のソースコードを直接、参照もしくは使用してほしい。

 なお、第2回のサンプルなど古いSDKバージョンで作ったWikitudeのプロジェクトを新しいバージョンのSDKを用いてビルドすると、エラーや警告が表示されるが、その場合は、前述の「移行解説ページ」や上記リンク先のソースコードを参考に、コードを修正していく必要がある。

 それではiOS側開発は完了したとして、本題となるARchitect WorldをHTML+CSS+JavaScriptで実装していこう。

1.4 ARchitect Worldのビューの実装

 まずはビュー部分を実装する。

図2 index.htmlなど各種ファイルの作成場所
図2 index.htmlなど各種ファイルの作成場所

 図2に示す場所に、index.htmlファイルを作成し、コードエディターでリスト1のように記述する。

index.html
<!DOCTYPE HTML>
<html>

<head>
  <!--  基本的なメタ情報 -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta content="width=device-width,initial-scale=1,maximum-scale=5,user-scalable=yes" name="viewport">

  <title></title>

  <!-- (1)Wikitude標準ライブラリのインポート -->
  <script src="https://www.wikitude.com/libs/architect.js"></script>

  <!-- (2)ADEデバッグ用のファイルをインポート -->
  <script type="text/javascript" src="js/ade.js"></script>

  <!-- (3)デザイン用のCSSファイルをインポート -->
  <link rel="stylesheet" href="css/default.css">
</head>

<body>
<!--<body onLoad="javascript:AR.logger.activateDebugMode();">--><!--⇒ログ出力を有効にするには、onLoad属性付きのこちらを有効にしてください。-->

  <div id="topMessage" class="info">Loading ...</div>

  <!-- (4)メインロジック用のJavaScriptファイルをインポート -->
  <script src="js/mainlogic.js"></script>
</body>
</html>
リスト1 ビュー本体の実装内容

 インポートしているファイル群を見てみると、以下のファイルが存在することが分かる。

  • (1)https://www.wikitude.com/libs/architect.jsファイル: Wikitude標準ライブラリ。ARchitect Worldを実現するのに必須。ちなみに、連載第2回では「architect://」プロトコルで記載していたが、最新バージョンではHTTPSを使用してARchitect Worldをロードできるように書き方が変更されている(これに関しても、前述の「移行解説ページ」に記載がある)
  • (2)js/ade.jsファイル: ADE(ARchitect Desktop Environment)を使ってデバッグするのに必要なファイル
  • (3)css/default.cssファイル: CSSデザイン。本稿で実装
  • (4)js/mainlogic.jsファイル: メインロジック。本稿で実装

 ADEによるデバッグとAR.logger.activateDebugMode()関数については第3回で説明しているので、そちらを参照されたい。

 <div id="topMessage" class="info">は、ビューの上部に表示するメッセージ領域である。CSSのinfoクラスは、リスト2のようにcss/default.cssファイルに定義されている。

css/default.css
.info {
  font-size: large;
  position: absolute;
  width: 100%;
  padding-top: 12px;
  padding-bottom: 12px;
  background-color: #BBBBBB;
  text-align: center;
  top: 0;
  left: 0;
  opacity: 0.7;
  display: table;
}
リスト2 ビューデザインの実装内容

 本稿で実装するファイルは、メインロジックを表すjs/mainlogic.jsだけである。ビューとメインロジックの関係は、図3のようになる。

図3 ARchitect Worldを実現するビューとメインロジックの関係

 それではメインロジックの実装内容を見ていこう。

1.5 ARchitect Worldのメインロジックの実装

 これまでの連載の中でも説明してきたように、Wikitudeでは、Worldオブジェクトの中に全ての処理ロジックをまとめて実装していくのが定石となっている。

 今回のサンプルでは、画像ターゲットを認識する必要があるので、最初に初期化関数initを実行する仕組みにしておこう。コードは、リスト3のようになる。

js/mainlogic.js
// ARchitect World(=AR体験)の実装
var World = {

  // TODO: メンバー変数はここに定義

  // 初期化用の関数。World.init()の形で、外部から呼び出されることで ARchitect World が生成されます。
  init: function () {
    // TODO: オーバーレイを作成する
  }

  // TODO: メンバー関数はここに定義

};

// ARchitect World を初期化します。
World.init();
リスト3 Worldオブジェクトとinit関数の実行

 Worldオブジェクトは連想配列の形で実装し、まずはinitキーに引数なしの関数を指定する。最後に1行、World.init();と記載することで、ARchitect World起動時に1回だけinit関数が実行されることになる。

 そのinit関数の中では、カメラビューの上にARオブジェクトを描画するためのオーバーレイを作成する。具体的には、リスト3の「TODO: オーバーレイを作成する」の部分にWorld.createOverlays();を記述して、「TODO: メンバー関数はここに定義」の部分にcreateOverlays関数を記述する。これはリスト4のようなコードになる。

js/mainlogic.js
……省略……
var World = {

  // TODO: メンバー変数はここに定義

  // 初期化用の関数。World.init()の形で、外部から呼び出されることで ARchitect World が生成されます。
  init: function () {

    World.createOverlays();

  }, // 「,」を足すのを忘れないように

  // init()関数から呼び出される。ARchitect Worldのオーバーレイを作成します。
  createOverlays: function () {

    // 1事前に作成しておいた「複数のターゲット画像群がまとめられた.wtcファイル」から、「画像ターゲットコレクション」リソースを作成します。
    var targetCollectionResource = new AR.TargetCollectionResource("assets/cats.wtc");
    // 2その「画像ターゲットコレクション」リソースから、「AR画像トラッカー」を作成します。
    var tracker = new AR.ImageTracker(targetCollectionResource, {
      // TODO: ImageTrackerのオプションをここで指定
    });

    // 3事前に準備しておいた「オーバーレイ用の.pngファイル」から、「画像」リソースを作成します。
    var heartMark = new AR.ImageResource("assets/overlays/heart.png");
    // 4その「画像」リソースから、オーバーレイとなる「画像描画物」を作成します。
    var heartOverlay = new AR.ImageDrawable(heartMark, 1, {
      // TODO: ImageDrawableのオプションをここで指定
    });

    // 5「AR画像トラッカブル」を作成することにより、追跡する(Tracker)側と追跡される(Trackable)側のセットを構築します。
    new AR.ImageTrackable(tracker, "*", {  // 追跡されるターゲットの名前を、ワイルドカード(*)にすることで、画像ターゲットコレクション内の全てのターゲットが反応するようになります。

      drawables: {
        cam: heartOverlay  // このプロパティに対して、追跡されるターゲットのオーバーレイ(=AR.ImageDrawableオブジェクト)を指定。
      },

      // TODO: ターゲット認識関連のイベントハンドラーをここに定義

    });
  },

  ……省略……
};
……省略……
リスト4 オーバーレイの作成

 createOverlays関数の中では、以下のことを順に実施する。

  • 1ターゲット画像コレクションをリソースAR.TargetCollectionResourceオブジェクト)として読み込む
  • 2そのリソースから、AR画像トラッカーAR.ImageTrackerオブジェクト)を作成する
  • 3画像ファイルをリソースAR.ImageResourceオブジェクト)として読み込む
  • 4その画像リソースから、オーバーレイとなるAR画像描画物AR.ImageDrawableオブジェクト)を作成する
  • 5AR画像トラッカブルAR.ImageTrackableオブジェクト)を作成する

 それぞれの関係性をまとめると図4のようになる。

図4 トラッカー/トラッカブル/オーバーレイの関係性

 この実装の流れが、ターゲット画像認識型ARを開発するためのポイントである。この内容をもう少し細かく見ていこう。

1ターゲット画像コレクションをリソース(AR.TargetCollectionResource)として読み込む

 assets/cats.wtcファイルを読み込んでいるが、この.wtcはWikitude Target Managerを使って事前に作成したものだ。

 作成方法は難しくないので、ここではポイントだけ簡単に紹介しておく。手順としては、「Image」Typeのプロジェクトを任意のプロジェクト名で作成し、「画像ターゲット(Image Targets)」として使いたい画像ファイル(四角い形で大きめの方がよい)をアップロードする。なお、画像ファイルの名前が、そのままターゲット名となるので、適切なファイル名を付けるようにすること(ハイフンなどの記号を付けない方が、JavaScriptコードで処理する場面では扱いやすい。ちなみに本稿のサンプルではターゲット名は使用していない)。

 また、本稿のサンプルのように距離を扱う場合には、ターゲット画像の実物サイズをmm単位で計測して、各画像項目の右クリックメニューにある[Details]から[物理的な高さ]を設定(図5)しておく必要があることに注意してほしい(サンプル実行時に距離が取得できない場合は、この設定を忘れていないかを確認してほしい)。

図5 ターゲット画像の物理サイズをmm単位で指定しているところ

 最後に、上部のバーにある[WTC]ボタンから.wtcファイルを生成して、プロジェクト内にassets/cats.wtcファイルとしてコピーしてほしい。

 本稿のサンプル作成時には、100均で購入したシールをターゲットとして使った。著作権に配慮してGitHub上のサンプルプログラム内には画像そのものは同梱していないので、各自で任意のターゲット画像を用意して.wtcファイルを作成してほしい。

2そのリソースから、AR画像トラッカー(AR.ImageTracker)を作成する

 リスト4の「TODO: ImageTrackerのオプションをここで指定」部分は、リスト5のように記述する。

js/mainlogic.js
……省略……
var tracker = new AR.ImageTracker(targetCollectionResource, {
  // ImageTrackerクラスのオプションプロパティを以下のように設定します:
  maximumNumberOfConcurrentlyTrackableTargets: 2, // 同時に追跡可能なターゲットの最大数を設定。
  extendedRangeRecognition: AR.CONST.IMAGE_RECOGNITION_RANGE_EXTENSION.OFF, // 拡張された範囲認識を無効にします。理由は、処理パワーが必要になって動作が遅くなるのを回避するため。
  onDistanceChangedThreshold: 0,      // 後述のonDistanceChangedコールバック関数が呼び出される閾値として、ターゲット間の最小距離(mm)を設定。
  onRotationChangedThreshold: 10,     // 後述のonRotationChangedコールバック関数が呼び出される閾値として、ターゲット間の最小回転角(°)を設定。
  onTargetsLoaded: World.worldLoaded,  // ターゲット群のロードが完了したときに呼び出されるコールバック関数を指定。
  onError: function (errorMessage) {  // トラッカーがエンジンによってロードできなかったときに呼び出されるコールバック関数を指定。
    alert(errorMessage);
  }
});
……省略……
リスト5 ImageTrackerのオプション指定

 詳細はコード内のコメントを参考にしてほしい。

 2つのターゲット間の距離を扱うにはonDistanceChangedThresholdオプションプロパティ、ターゲット間の角度を扱うにはonRotationChangedThresholdオプションプロパティを指定する。ここに指定する値は、距離や角度がどれくらい変更されたら、後述のonDistanceChangedonRotationChangedコールバック関数が呼び出されるかを決めるためのものである。

 onTargetsLoadedオプションプロパティは、Worldオブジェクト内のworldLoaded関数を呼び出すようになっている。

 worldLoaded関数の実装内容はリスト6のとおりだ。ビュー上部にあるメッセージ領域(topMessage)に、ユーザーへの操作説明文を表示する処理が記載されている。

js/mainlogic.js
……省略……
var World = {
  ……省略……

  // ImageTrackerクラスのonTargetsLoadedイベントに呼び出されます。トラッカーのターゲットコレクションが正常にロードされ、トラッカーがエンジンによってロードされたときにトリガーが発生します。 このトラッカーに関連するImageTrackableは、トラッカーが正常に読み込まれた後にのみトラッキングできます。
  worldLoaded: function () {
    World.initInstruction();
  },

  // ユーザーへの指示文の初期化処理を関数にまとめています。
  initInstruction: function () {
    var styleAttrDescription = " style='display: table-cell;vertical-align: middle; text-align: right; width: 50%; padding-right: 15px;'";
    var styleAttrFigure = " style='display: table-cell;vertical-align: middle; text-align: left; padding-right: 10px; width: 112px; height: 38px;'";
    document.getElementById('topMessage').innerHTML =
      "<div" + styleAttrDescription + ">写真を2枚並べてね:</div>" +
      "<div" + styleAttrFigure + "><img src='assets/leftman.png'></div>" +
      "<div" + styleAttrFigure + "><img src='assets/rightman.png'></div>" +
      "<div" + styleAttrFigure + "><img src='assets/leftwoman.png'></div>" +
      "<div" + styleAttrFigure + "><img src='assets/rightwoman.png'></div>";
  }
});
……省略……
リスト6 トラッカーのターゲットコレクションが正常にロードされたら、ビュー上部のメッセージ領域にユーザーへの操作説明文を表示

 リスト6では、assetsフォルダー内に存在するいくつかの.pngファイルが画像として読み込まれているが、これらの画像ファイルも著作権に配慮してGitHub上のサンプルプロジェクト内には含めていないのでご了承いただきたい。

3画像ファイルをリソース(AR.ImageResource)として読み込む

 ターゲットが認識・追跡されたら、そのターゲットの位置やサイズに合わせて、任意の画像から作成したARオブジェクトをオーバーレイ表示できる。その画像ファイルをここで読み込んでいる。本稿のサンプルではハート形の画像を使っているが、やはり著作権の問題があるので、プロジェクト内に画像ファイルは同梱していない。読者自身で任意の画像を用意してほしい。

4その画像リソースから、オーバーレイとなるAR画像描画物(AR.ImageDrawable)を作成する

 リスト4の「TODO: ImageDrawableのオプションをここで指定」部分は、リスト7のように記述する。translateオプションプロパティを使ってX軸上で少し位置をずらしているだけだ。

js/mainlogic.js
……省略……
var heartOverlay = new AR.ImageDrawable(heartMark, 1, {
  translate: {
    x:-0.15  // AR.ImageDrawableのX軸方向への平行移動を設定。
  }
});
……省略……
リスト7 ImageDrawableのオプション指定
5AR画像トラッカブル(AR.ImageTrackable)を作成する

 リスト4の「TODO: ターゲット認識関連のイベントハンドラーをここに定義」部分は、リスト8のように記述する。

js/mainlogic.js
……省略……
new AR.ImageTrackable(tracker, "*", {  // 追跡されるターゲットの名前を、ワイルドカード(*)にすることで、画像ターゲットコレクション内の全てのターゲットが反応するようになります。

  drawables: {
    cam: heartOverlay  // このプロパティに対して、追跡されるターゲットのオーバーレイ(=AR.ImageDrawableオブジェクト)を指定。
  },

  // 6AR.ImageTrackableオブジェクトが可視から「不可視」に変更されたときに呼び出されるコールバック関数。
  onImageLost: function (target) {
    // 計算処理を中断した方がよいが、サンプルなのでできるだけコードを短くするために実装していません。
    World.initTargetObjects();
  },

  // 7AR.ImageTrackableオブジェクトが不可視から「可視」に変更されたときに呼び出されるコールバック関数。
  onImageRecognized: function (target) { // 引数targetはImageTargetクラスのオブジェクトです。

    // TODO: 距離と角度のイベントハンドラー関数はここに定義

  }

});
……省略……
リスト8 ImageTrackableのオプション指定

 AR.ImageTrackableオブジェクトのコンストラクター引数にAR画像トラッカーを指定してAR画像トラッカブルを作成することにより、追跡する(Tracker)側と追跡される(Trackable)側のセットが構築される。

 また、drawables.camオプションプロパティにAR画像描画物を指定することで、オーバーレイの設定も行っている。

 さらに、ターゲット認識関連のイベントハンドラーを2つ定義している。

  • 6onImageLost関数: ターゲットが不可視になったら、追跡を終了するために呼び出される
  • 7onImageRecognized関数: ターゲットが可視になったら、追跡を開始するために呼び出される

 それぞれの内容を見ていこう。

6ターゲットが不可視になったら、追跡を終了するために、onImageLost関数が呼び出される

 onImageLost関数の中では、initTargetObjects関数のみを呼び出している。この関数はリスト9のように実装されている。リスト4の「TODO: メンバー変数はここに定義」の箇所に、これを記載してほしい。

js/mainlogic.js
……省略……
// 2つの画像ターゲット間における、現在の距離と回転角を保持する変数。
curDistance: 1000,
curRotation: 180,

// 2つの画像ターゲットを追跡中の場合、そのターゲットオブジェクトをこの変数に登録して保持しておきます。
targetsRegistry: [],

// ターゲットオブジェクトの初期化処理を関数にまとめています。
initTargetObjects: function () {
  World.targetsRegistry = [];
  World.curDistance = 1000;
  World.curRotation = 180;
  World.initInstruction();
},
……省略……
リスト9 ターゲットに関する初期化処理

 いくつかの変数を初期化したりしているが、これらの変数は、現在の距離や角度、追跡中のターゲットの状態を管理するためのもので、後述の7の処理で使用する。

7ターゲットが可視になったら、追跡を開始するために、onImageRecognized関数が呼び出される

 前置きが長かったが、いよいよ本題の「ターゲット間の距離と角度を取得する方法」について説明する。リスト8の「TODO: 距離と角度のイベントハンドラー関数はここに定義」部分は、リスト10のように記述する。

js/mainlogic.js
……省略……
onImageRecognized: function (target) { // 引数targetはImageTargetクラスのオブジェクトです。

  // ターゲット間の距離が変化したときに呼び出されるコールバック関数。前述のonDistanceChangedThresholdプロパティ値も設定してください。
  target.onDistanceChanged = function (distance, destinationTarget) {

    World.curDistance = distance; // 現在の「距離」を保存
    World.showTargetObjects(target, destinationTarget);

  };

  // ターゲット間の回転角が変化したときに呼び出されるコールバック関数。前述のonRotationChangedThresholdプロパティ値も設定してください。
  target.onRotationChanged = function (rotation, destinationTarget) {

    // 回転角を 0° ~ 360° の範囲に調整
    if (rotation.z < 0) {
      rotation.z += 360;
    }

    World.curRotation = rotation; // 現在の「角度」を保存
    World.showTargetObjects(target, destinationTarget);

  };

}
……省略……
リスト10 画像ターゲットが認識されたら呼び出される関数

 現在の距離や角度を保存したら、2つのターゲット(targetdestinationTarget)を引数に指定してshowTargetObjects関数を呼び出している。

 showTargetObjects関数は、リスト11のようになる。これをリスト3の「TODO: メンバー関数はここに定義」の部分に記述する。

js/mainlogic.js
……省略……
showTargetObjects: function (target, destinationTarget) {

  if (target == null || destinationTarget == null) return; // 不要だと思うが念のため。

  // 回転角が10°前後以内、かつ距離が30mm以内になったら、計算処理を実行します。
  if ((World.curRotation.z > 350 || World.curRotation.z < 10) && World.curDistance < 30) {

    // 計算処理をすでに実行済みの場合は、再計算しないようにはじきます。
    if (World.targetsRegistry.filter(function (obj) {
        return (obj.first == target && obj.second == destinationTarget) ||
          (obj.first == destinationTarget && obj.second == target);
      }).length == 0) {

        // 計算済みとして2つのターゲットを保存
        World.targetsRegistry.push({first: target, second: destinationTarget});

        // 「2人の相性」の計算処理の元データとして、Cognitive ServicesのFace APIを呼び出します。
        // スクリーンキャプチャなどデバイス側の機能が必要になるので、 iOS(Objective-C)側に処理を任せます。
        AR.platform.sendJSONObject({
          action: "calculate_relation"
        });
    }

  } else {

    if (World.targetsRegistry.length != 0) {
      // 計算処理を中断した方がよいが、サンプルなのでできるだけコードを短くするために実装していません。
      World.initTargetObjects();
    }

  }

},
……省略……
リスト11 距離と角度に関する一定の条件に基づいて処理を実行する関数

 このコードでは、「角度(=2つのターゲット間の回転角)が10°前後以内、かつ、距離が30mm以内になったら」という基準を、処理実行の条件として定義している。各ターゲットの横幅が20mmあるので、その中心点から端までの距離が10mmとなり、それが各画像の両端に存在するので、「30mm以内の距離」という指定で実質的には「ターゲット間の端と端が10mm(=1cm)以内の距離」という意味になるので注意してほしい。

 AR.platform.sendJSONObject({ action: "calculate_relation" });というJavaScriptコードは、iOS側の処理を呼び出すためのもので、calculate_relationというアクション名をしている。このようにiOS側に処理を委ねている理由は、顔写真を含めたアプリのカメラビュー全体のスクリーンキャプチャを取得する機能が、ネイティブ側にのみ存在しているからだ。

 iOS側では、ViewController.mファイル(Objective-Cコード)はリスト12のように記載している。JavaScript側のsendJSONObject関数を呼び出すと、iOS側のreceivedJSONObjectメソッドが最終的に呼び出されることになる。

ViewController.m
……省略……
- (void)architectView:(WTArchitectView *)architectView receivedJSONObject:(NSDictionary *)jsonObject {
  id actionObject = [jsonObject objectForKey:@"action"];
  if ( actionObject && [actionObject isKindOfClass:[NSString class]] ) {
    NSString *action = (NSString *)actionObject;
    if ( [action isEqualToString:@"calculate_relation"] ) {
      if ( [self.architectView isRunning] ) {
        NSDictionary* info = @{};
        [self.architectView captureScreenWithMode:WTScreenshotCaptureMode_Cam usingSaveMode:WTScreenshotSaveMode_Delegate saveOptions:WTScreenshotSaveOption_CallDelegateOnSuccess context: info];
      }
    }
  }
}

- (void)architectView:(WTArchitectView *)architectView didCaptureScreenWithContext:(NSDictionary *)context
{
  NSString* receivedJsonData = @"[ { ""faceRectangle"": { ""top"": 342, ""left"": 331, ""width"": 139, ""height"": 139 }, ""faceAttributes"": { ""age"": 18.4, ""emotion"": { ""anger"": 0.0, ""contempt"": 0.003, ""disgust"": 0.001, ""fear"": 0.0, ""happiness"": 0.902, ""neutral"": 0.092, ""sadness"": 0.0, ""surprise"": 0.002 } } }, { ""faceRectangle"": { ""top"": 305, ""left"": 1095, ""width"": 111, ""height"": 111 }, ""faceAttributes"": { ""age"": 25.9, ""emotion"": { ""anger"": 0.001, ""contempt"": 0.0, ""disgust"": 0.0, ""fear"": 0.0, ""happiness"": 0.999, ""neutral"": 0.0, ""sadness"": 0.0, ""surprise"": 0.0 } } }]";
  [self.architectView callJavaScript:[NSString stringWithFormat:@"World.showRelation(%@)", receivedJsonData]];
  return;

  // 本当にFace APIから取得する場合は、GitHub上のサンプルコードを参考にしてください。
}
……省略……
リスト12 iOS側でスクリーンキャプチャとFace APIの結果取得(この例では仮データを作成しているる)

 receivedJSONObjectメソッド内では、アクション名がcalculate_relationの場合、かつARchitectViewが実行中の場合に、captureScreenWithModeメソッドを呼び出して、アプリのビュー全体のスクリーンキャプチャを行っている。そのスクリーンキャプチャが完了すると、didCaptureScreenWithContextメソッドが呼ばれる。

 didCaptureScreenWithContextメソッド内では、Azureが提供するCognitive ServicesのFace APIを呼び出して、年齢や感情値を取得するのだが、前述したとおり、無償のトライアルライセンスではウォーターマークに顔が隠されてしまい、Face APIから正常な結果が得られないという問題があるので、ここではハードコーディングで固定的なJSONデータを作成している。

 JSONデータの内容解析や計算処理は、callJavaScriptメソッドによりJavaScript側に渡している。具体的には、JavaScript側のWorld.showRelation関数を呼び出している。

 showRelation関数は、リスト13のようになる。これをリスト3の「TODO: メンバー関数はここに定義」の部分に記述する。

js/mainlogic.js
……省略……
showRelation: function (jsonData) {
var styleAttrDescription = " style='display: table-cell;vertical-align: middle; text-align: center; background-color: pink; font-weight: bold; font-size: 200%;'";
if ((jsonData instanceof Array) && (jsonData.length == 2)) {
    // 「2人の相性」の計算処理(何の理由も根拠もないデタラメな計算です……)。
    var percentage = 130
    - Math.abs(jsonData[0].faceAttributes.age - jsonData[1].faceAttributes.age) * 3
    - Math.abs(jsonData[0].faceAttributes.emotion.anger - jsonData[1].faceAttributes.emotion.anger) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.contempt - jsonData[1].faceAttributes.emotion.contempt) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.disgust - jsonData[1].faceAttributes.emotion.disgust) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.fear - jsonData[1].faceAttributes.emotion.fear) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.happiness - jsonData[1].faceAttributes.emotion.happiness) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.neutral - jsonData[1].faceAttributes.emotion.neutral) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.sadness - jsonData[1].faceAttributes.emotion.sadness) * 100
    - Math.abs(jsonData[0].faceAttributes.emotion.surprise - jsonData[1].faceAttributes.emotion.surprise) * 100;
    document.getElementById('topMessage').innerHTML =
    "<div" + styleAttrDescription + ">この2人の相性は? 「" + percentage + "%」</div>";
  } else {
    document.getElementById('topMessage').innerHTML =
    "<div" + styleAttrDescription + ">「" + jsonData + "」</div>";
  }
},
……省略……
リスト11 距離と角度に関する一定の条件に基づいて処理実行

 渡されたJSONデータから年齢や各感情値を取得して「2人の相性」を計算し、その計算結果に基づくメッセージをビュー上部にあるメッセージ領域(topMessage)に表示している。

 以上で、サンプルアプリは完成である。

2. まとめ

 今回は、ターゲット「画像」認識型ARの開発方法を、サンプルアプリを通して説明した。また、2つのターゲット画像間の距離や角度を活用する手法を示した。

 ターゲット間の距離・角度をそのまま活用することはレアケースだと思うが、今回示したサンプルアプリのように、「一定の範囲内になったらスイッチオン、範囲外になったらスイッチオフ」といった活用方法であれば、広く適用できると思う。ぜひそういった用途でも距離や角度が活用できることを頭の隅に入れて、さまざまなAR開発に励んでいただきたい。

 さて次回は、3Dの物体認識を活用したARの開発を、サンプルアプリ開発を通して説明する。約1カ月後に公開の予定だ。

1. 拡張現実(AR)とは? モバイルARを実現するテクノロジーと開発ライブラリ[PR]

ARの概要と特徴、利用モデルを図と動画で初歩から解説。主な開発ライブラリと、その一つであるWikitudeを紹介し、AR開発で使える「位置情報データ&API」も紹介する。

2. iPhone向け拡張現実アプリの開発に挑戦してみた(Wikitude活用)[PR]

モバイルARアプリ開発に初挑戦! 位置情報を含むオープンデータの「バス停位置情報」と、ARライブラリの「Wikitude」を活用したら、拡張現実に対応した有用なiOSアプリが簡単に開発できた。

3. WikitudeでカンタンAndroid向けAR開発。ブラウザーでデバッグ可能[PR]

位置情報を含むレストラン検索の「ホットペッパーAPI」を活用したAndroid向けモバイルARアプリを作成。Wikitudeを使えば、使い慣れたテキストエディターで開発し、ブラウザーで手軽にデバッグできる。

4. 【現在、表示中】≫ AR(拡張現実)開発を始めよう! 複数ターゲットの認識と、距離・角度の把握(Wikitude活用、iOS編)[PR]

2人の相性を写真から診断するアプリを、画像認識型ARで開発。ターゲット写真を認識してARオブジェクトを表示し、2枚のターゲット間の距離と角度が一定の条件内になれば診断を開始する仕組みだ。

5. 身の回りの3D物体を認識して、そこにAR(拡張現実)オブジェクトを表示する方法(Wikitude活用、Android編)[PR]

市販のドレッシングボトルを映せばレシピが提案されるアプリを、物体認識型ARで開発。物体ターゲットを認識すると、その物体空間に合わせてARオブジェクトを表示し、それをタップするとレシピページにジャンプする。

サイトからのお知らせ

Twitterでつぶやこう!