👁️

ARFoundation×CloudVisionAPIで現実空間のオブジェクトにタグ付けする

2021/12/23に公開

Play

ARFoundationCloud Vision APIを組み合わせて、現実空間のオブジェクトを解析してタグ付けしてみました。

4つサンプルのgifがあります。

何をしているのか

調べたらもっと詳しい情報が出てくるので、ざっくりと説明します。

ARFoundationはUnity公式が作っているARアプリを開発するためのSDKです。
UnityでARを作る際には基本的にはこれを使用することになります。AndroidiOSなどのPlatform固有のARSDKをラップしています。

Cloud Vision APIGCPの画像認識サービスです。
RESTなどで画像を送信するだけでいい感じに解釈してjsonを返してくれます。文字やランドマークなどいろいろなものを解析できます。今回はその中の複数のオブジェクトを検出するを使いました。

この2つを組み合わせて以下の手順で現実空間のオブジェクトにタグ付けしています。

  1. ARFoundationから現在のカメラ画像を取得
  2. Cloud Vision APIObject Localizeに送信して画像のオブジェクトを分析
  3. 分析結果からオブジェクトの位置を推定してRayを発射し、衝突判定が存在していたらマーカーを設置

アーキテクチャ図です。かたちある虚無ですがせっかく作ったので載せます。

実装内容

リポジトリはこちら。
動かす場合はREADMEに記載の通り、自前でAPIキーを発行して入れ替えてビルドしてください。

https://github.com/nekomimi-daimao/SpacialAnalyzer

環境

platform version
Unity 2020.3.23f1
ARFoundation 4.2.1
Cloud Vision v1

実際の細かい処理シーケンス

Assets/SpacialAnalyzer/Scripts/Analyzer/AnalyzeSpatial.csがロジックの全てです。

  1. カメラ位置からRayを撃ってARAnchorを設置
  2. 画像を取得
  3. 画像取得時のカメラのARAnchorからの相対位置を保存
  4. Cloud Vision APIに画像を送信
  5. レスポンスを取得
  6. レスポンスのアノテーションから画面のどの位置にオブジェクトがあるかを計算
  7. 画像とスクリーンの比率を計算
  8. ScreenPointToRayで計算した比率を元に現在のカメラからオブジェクトに向かってのRayを生成する
  9. 現在のカメラに対するRayの相対位置を計算
  10. 保存しておいたARAnchorの相対位置から画像取得時のカメラの位置を復元
  11. 画像取得時のカメラ位置に対してRayの相対位置を適応する
  12. ARRaycastManagerによってRayを撃ち、オブジェクトの位置を計算する

アノテーションからRayを生成するにはScreenPointToRayが必要で、ScreenPointToRayには画像を取得した瞬間のカメラ姿勢が必要で……。
長くて面倒なことになっていますが、要するに「画像を取得した瞬間のカメラ姿勢を保持しておいて、そこから画像のアノテーションに応じたRayを撃つ」というだけです。

ポイント

実装するにあたって遭遇したポイントを書いていきます。

Cloud Vision

RESTがRESTじゃない

公式にcurlでの実行方法が記載されているのですが、GOOGLE_APPLICATION_CREDENTIALSという環境変数に認証情報が保存されている、かつgcloudがインストール済みという前提なのでそのままUnityに流用することはできません。
uriにqueryとしてAPIキーを設定することで認証することができます。公式には記載を見つけられなかったのですが、下の記事を参考に突破しました。

https://syncer.jp/cloud-vision-api

(0,0)は左上

オブジェクトが画像のどの位置にあるかは0.0 ~ 1.0で表されます。
が、複数のオブジェクトを検出するの最初にあるアノテーション入りの画像とjsonをよーく見比べるとわかりますが、左上が(0,0)として扱われています。機械学習とか詳しくないのでこれが常識なのかGoogle presentsなのか知らないですがめっちゃびっくりしました。

ARFoundation

画像の向きが違う

https://edom18.hateblo.jp/entry/2020/08/23/121008

上の記事にある通り、ARCameraManagerから取得できる画像は生のデータであり端末の回転は考慮されていません。画像の回転処理なんて自前でやってられないですし、かといって向きがおかしい画像をそのまま送った場合Cloud Vision APIの結果にどんな影響があるかもわからないので、端末の向きを固定することで対処しました。ARアプリで端末の回転ってあんまりしない……しなくない?

画像の縦横比が違う

使っているデバイスの画面は2270×1080ですが、ARFoundationから取得した画像は640×480です。つまりスマートフォンの画面には映ってないけどカメラが認識している部分が含まれています。具体的に言うと画像の上下は画面に映っている分よりちょっと広めです。幸いにも横幅は画面=画像だったので、そこから比率を計算して縦を合わせました。
今回は自分の端末で動けばいいので力押しできましたが、対象を増やす場合は端末の画面サイズとカメラの環境が多様で吸収がしんどそうな部分です。
この事象は画面外の樹木を認識しているこれとかわかりやすいと思います。

RayがTrackableにHitしない

ARAnchorを用いることで、カメラが動いてもAR空間に配置したコンテンツの位置を安定させることができます。
ARAnchorは任意の位置に設置することができますが、Trackableと関連付けることでより安定します。たぶん。Trackableというのは、PointCloudPlaneなど、ARFoundationが認識するもののベースクラスです。

  1. Rayを撃つ
  2. Trackableにhit
  3. Trackableに関連付けたARAnchorを配置

というのがARAnchorTrackableに関連付ける基本の流れになります。
……が、使っている端末のカメラの性能がよくないのか、なかなかTrackableにhitしません。しょうがないので当たらなかったら2m先にTrackableなしのARAnchorを配置するようにしました。

画像取得のカメラの姿勢を復元

ScreenPointToRayを使う場合、画像を取得した瞬間の姿勢のカメラから算出する必要があります。ARだとそんなに端末を動かさないはずですが、ちょっと角度がずれただけで大幅にRayがずれるので、どうしても必要でした。絶対→相対→絶対と繰り返し座標を変換する際、相対座標はひとまず変数名にLocalと必ず入れることで混乱をしのぎました。
このあたりの座標変換は1ステップ1ステップ分解していけばわかることなのですが、その分解していく作業が大変です。頭の中で考えても厳しいので、素直に絵を描くと楽でした。

改良点

もしこれを発展させるならこんな点かなーと思っています。[1]

Anchorの永続化

レスポンスなどはデータクラスにまとめてシリアライズできるようにしてあります。
CloudAnchorに始まるAnchorの永続化を行うことで、シリアライズしておいた過去のスキャン情報を復元したり、スキャン情報を共有することができます。
他にも、Immersalとかでスキャン済みの点群データとオブジェクトのタグ付けを組み合わせてどこになにがあるかを表示できるようにするとか。

画質改善による影響

送信している画像はbase64に変換していますが、サイズの関係でjpegにしています。他の高画質のフォーマットにしたらCloud Vision APIの認識結果が向上するのかどうかは気になるところ。

タグの重複

同じものに対して二重にアノテーションされた場合、タグがかぶって見づらいです。
画像のアノテーションの時点で弾くか、近くにある場合はよりscoreが高いほうが勝つようにするか。

まとめ

前々からやってみたかったので、実現できて満足です。

かれこれ1月くらいはやっていましたが、期間の半分くらいは不適切なコンテンツを検出する(セーフサーチ)で遊んでたような気がします。

あとARCoreRecord & Playbackの調査。これも結構調べたので、また記事にして「まさか歩き回ってAR開発してるんですか!!?!?!?!?!?!??!?」ってARKitを煽ろうと思っています。

Cloud Vision APIは思ったより認識してくれて嬉しいですが、家の中でやるとFurnitureで埋め尽くされて前が見えなくなります。なにをやるかにもよりますが、やっぱり認識したいものを絞ってトレーニングした専用モデルを使う必要が出てくるのかなーと思いました。あと、どうもCloud Vision APIって静的にホストされている高画質の画像に対して実行されるのを想定しているような気がします。なんとなく。

VContainerを導入したのはDIのためじゃなくてARXXManagerたちを配り歩くのが面倒になったからです。いっぱいあってほんとめんどくさい。

なんか感想とかあったらくれるとうれしいです。

おしまい。

参考

Cloud Vision API

https://cloud.google.com/vision/docs/object-localizer
https://syncer.jp/cloud-vision-api
https://qiita.com/KMim/items/884b237f962066fb78eb

ARfoundation

https://qiita.com/cvusk/items/5f753be55531b2dee608
https://docs.unity3d.com/ja/2019.4/ScriptReference/Camera.ScreenPointToRay.html
https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.0/manual/anchor-manager.html
https://docs.unity3d.com/2020.3/Documentation/ScriptReference/Texture2D.html
https://nn-hokuson.hatenablog.com/entry/2020/01/29/220717
https://www.jyuko49.com/entry/2021/05/27/022934

else

https://mizutanikirin.net/unitywebrequest-deletedownloadhandler
https://light11.hatenadiary.com/entry/2021/02/01/203252
https://yowabi.blogspot.com/2018/06/unity.html

脚注
  1. 別にやったりはしない。 ↩︎

Discussion