Closed65

Immersalでキャプチャ画像から後でまとめてMap作成をしたい

にー兄さんにー兄さん

目的

Immersal公式のMapperアプリを使うと、キャプチャした画像を逐一鯖に送信することで
Immersalのマップを作成する

この方法だと、wifiがないところや電波が悪いところで使用するときに結構不便だったりするので
REST APIでキャプチャ作業が一通り終わった後に一括でアップロードしたい

こちらに構想メモがある
https://scrapbox.io/project-TSUin-MI/画像の一括アップロードによるMap作成の構想

にー兄さんにー兄さん

すでにRESTでマップ作成されている人が記事を書いていて、
この記事ががっつり参考になりそう

こちらではImmersalのMapperを改造しているので
今回はスクラッチで作ってみる

にー兄さんにー兄さん

どういう仕様にしようか迷う(激うまギャグ)

  • 画像キャプチャはpngとして保存していく
  • キャプチャ時のカメラの姿勢はjsonに配列でどんどん貯めて最後に出力
  • 画像の名前とカメラ姿勢の配列インデックスを対応させておく
  • カメラパラメータやマップの名前などもjsonに格納しておく
  • 20枚の写真をとったら、20枚の画像と1つのjsonが出力される感じ
  • データはpersistantDataPath/日付の中に展開する

みたいに記録していくのがいいかな

にー兄さんにー兄さん

ARFoundationとARCore XR Pluginを入れて、Androidにスイッチして
Vulkanを削除してカラースペースをLinearにする
MininmalAPILevelを8.1の27に設定
Scriptingbackendは、いったんMonoと.netcore2でやってみよう、ビルド速そうだし
あとからunsafeコードの許可が必要な予感がする

XR Plugin ManagementでARCおれにチェックを入れる

にー兄さんにー兄さん

やっぱりURPに設定しよう
URP 10.5.1をインポートして、Pipeline Assetを追加する

にー兄さんにー兄さん

そこで生成されたScriptablObjectをPipelineSettingsに突っ込む

それから、同じタイミングで生成されたxxx_Render.assetの中のAdd Render FeatureからAR Background
 Rendererを追加する

にー兄さんにー兄さん

テキトーにシーンにキューブを配置したのち、設定がちゃんとできているか確認するために
いったんAndroidビルドを行う

にー兄さんにー兄さん

考えずに実装してたらRESTでマップを生成する処理から書こうとしていた(おい
いったん実装の手順を整理したいかも

  1. ARCameraからCPUImageをCapture
  2. persistantDataPathに画像を保存する
  3. キャプチャした瞬間のカメラパラメータと姿勢をメモリに保存
  4. ボタンなどのアクションでjsonとしてpersistantDataPathに保存

一旦これまでを実装する。
この時は日付とか関係なしにpersistantDataPathに直でファイルを展開してしまってよさそう
そのあとに

  1. persistantDataPathから画像とjsonを入力
  2. Immersalにリクエストを送信し確認
  3. 何枚か送信してmap constructを実行してみる
  4. 最後に全部をまとめる
にー兄さんにー兄さん

とりあえずARのカメラ画像を取得→テクスチャへ変換までが完了した
ちゃんと非同期で動いていてとても偉い!
非同期処理への理解も深まりました。とは言ったもののたまにアプリごと落ちたりするのでまだ完全じゃないかも?
https://photos.app.goo.gl/e1dGzFQFrDnpmkJz5

にー兄さんにー兄さん

実は初めてAndroidで実機デバッグをやったのだけど、Quality of Developmentが100倍上がって嬉しい

にー兄さんにー兄さん

画像以外の必要なデータを整理する

まず、キャプチャごとに必要なデータは

  • カメラの姿勢
    • 位置
    • 回転クォータニオン
  • run→trackingState(というかずっと0でもいいかなって思う)
  • anchor→そもそもImmersalでanchorという概念が何を指しているのかわからない。falseでいいか
  • 世界座標
    • 緯度経度高さ
にー兄さんにー兄さん

マップ作成中変わらないデータは

  • トークン
  • マップの名前
  • カメラパラメータ

jsonに記載する必要がないデータは

  • 画像のインデックス
にー兄さんにー兄さん

マップのjsonデータ、ひとまずこれで行ってみよう

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

namespace ImmersalRestMapConstructor.CaptureData
{
    [Serializable]
    public struct CaptureImageInfo
    {
        public string b64; // base64 encoded texture
        public Pose pose;
        public int run; // incremented when tracking failed
        public bool anchor;
        public float longitude;
        public float latitude;
        public float altitude;
    }

    [Serializable]
    public struct CaptureMapInfo
    {
        public string token;
        public string name;
        public Vector2 focalLength;
        public Vector2 principalOffset;
        public List<CaptureImageInfo> images;
    }
}
にー兄さんにー兄さん

マップのデータをjsonで保存することにも成功した
あとは画像を保存してリクエスト多くってみる

にー兄さんにー兄さん

日を跨いでの作業
まずは画像を保存してみる。ヌルヌル動く状態で保存したい
あと、Cameraパラメータは毎回取得したほうがいいのか検証したい
スマホって横向きにしたらカメラパラメータ変わる?

にー兄さんにー兄さん

画像を保存してみたら、なんか横向きになってるな?

issueにもあったんだけど、カメラのセンサ状態をそのまま取得しているのでしょうがないんだとか
そうなんか...
https://github.com/Unity-Technologies/arfoundation-samples/issues/559

にー兄さんにー兄さん

まぁでも位置合わせもこれと同じデータでやってたし、トリマこのままで行ってみるか
MirroXだけ適用する

にー兄さんにー兄さん

カメラIntrinsicsも一旦このままでやって見よう
ホントは横向きでの撮影にも対応させたいが
それか横向き強制にしようかな?

にー兄さんにー兄さん

画像ファイル保存、非同期で書いてるはずなんだけどカクツクな......

にー兄さんにー兄さん

一応何枚かキャプチャとjsonを取得できたので
これを使ってmapを生成できるか試してみたい

キャプチャはプロジェクトのPersistantDataPathに保存してあるので
エディタからテストすることが可能である

にー兄さんにー兄さん

途中マップ作製をWebブラウザとかで実装しようかちょっと迷ったけど
Quaternion→Mtrixの変換とか何使おうか迷ったので後回し

のちのち、アップロードしたファイルからWebGLでカメラの撮影位置をレンダリングしてみたら面白そう
そのままマップ作製できるWebアプリ、いいな

にー兄さんにー兄さん

persistantDataPathからデータの読み込みを確認できた

Immersal APIに画像をリクエストしてマップ作成のリクエストまでできた
しかしマップがfailedしてしまった......

にー兄さんにー兄さん

画像を回転させてみたり、MirroXをNoneにして見たり、最初の画像をanchorにしてみたり、パターン替えて色々やってみたが全部failedしてしまった
普通にMapperアプリでやったときは成功しているので、実装に間違いがあるらしい

あと、failedするのが速い
何か根本的な原因があるのかもしれないな

にー兄さんにー兄さん

いっかいQiitaの記事を通りに実装してみるandコードリーディングが必要な気がしてきた

にー兄さんにー兄さん

日を改めてのログ
immersal-ar-sdkのリポジトリを見てみると、どうやら2020.3.18f1にアプデされてた
CoreSDKも1.14.0になったし、どんな感じなのか見ていこう

にー兄さんにー兄さん

2018アンスト祭とかやってたら時間が結構かかってしまった、2時間くらい環境整えるのに使ってしまった

にー兄さんにー兄さん

Immersal-ar-sdkのMapperAppを見てみている
このサンプルはこのシーンだけビルドすればログインした後にAutomaticMapperやMapVisualizerを使うことができる便利なシーンである。
シーン自体は Assets/ImmersalSDK/Samples/Scenes/MappingApp.unityにある

にー兄さんにー兄さん

シーン自体を開くとログイン画面しかないけど、
LoginManager.cs(Assets/ImmersalSDK/Samples/Scripts/Mapping/AutomaticCapture/AutomaticCaptureManager.cs)を見ると、ボタンを押した後に m_MappingUIなるgemaObjectをInstantiateしている

にー兄さんにー兄さん
public void EnableMappingMode()
        {
            if (m_MappingUI == null)
            {
                m_MappingUI = Instantiate(mappingUIPrefab);
            }
            else
            {
                m_MappingUI.SetActive(true);
            }
        }
にー兄さんにー兄さん

MappingUIプレハブはLoginManagerを同じゲームオブジェクトにアタッチされている、ToggleMappingModeというスクリプトにアタッチされているプレハブ

開いてみるとこんな感じ
ログインした後のUIが全部いりのcanvasらしい
プレハブの場所はAssets/ImmersalSDK/Samples/Prefabs/Mapping/Mapping UI.prefab

にー兄さんにー兄さん

MappingUIの中で、▷ボタンを押すとAutomaticMapperが起動する流れになっている

ボタンのコールバックにはAutomaticCaptureManagerの
ToggleMappingメソッドが割り当てられているみたい

にー兄さんにー兄さん

ちなみにAutomaticCaptureめちゃくちゃ便利そうだったので、
最大枚数を300枚にしてRGBキャプチャをtrueに設定した
これは使っていきたい

にー兄さんにー兄さん

AutomaticCapture.Capture()の中ではAsyncCaptureJobというキャプチャの処理を意味するオブジェクトが作られ、
そのジョブの設定をしていき、ジョブのキューに放り込むということがされているみたい

AsyncCaptureJobAsyncJobというクラスを継承しており、
AsyncJob.RunJobAsync()メソッドをオーバーライドしている
おそらくcaputre以外の処理もすべてAsyncJobに共通化されていて、キューの中に入ったジョブはRunJobAsyncを次々に実行されていく構図になってるんだと思う

にー兄さんにー兄さん

AsyncCaptureJob.RunJobAsyncの中身を深掘ってみれば、
最終的なデータがどんな風に保存されているのかを見ることができる

にー兄さんにー兄さん

RunJobAsync()の中身、割と普通のことが書かれているなぁという印象
ただちょっと気になるのが、JobCaptureAsyncにあるencodedImageとimagePathという
プロパティがあって、どうやらencodedImageは使われていないっぽい

にー兄さんにー兄さん
byte[] image = File.ReadAllBytes(imagePath);

という感じで画像のbyte列を取得しているんだけど
ここだけどんなデータなのかがパッと見わからなかった

にー兄さんにー兄さん

ん-png画像のバイナリデータが妥当か
このimagePathって具体的にどんな値が入るのか
そのpathにはどんな画像が入っているのか調査したい

にー兄さんにー兄さん

ちなみにRunJobAsyncではhttpリクエストをしているけど、UnityWebRequestは使っていなかった

にー兄さんにー兄さん

その直前にあるGetPlaneDataRGBではXRCpuImageからbyte列を取得しているものの、
そのあとになぜかかなり低レベルなコードが出現している
ImmersalCoreSDKふぁオープンじゃないので見せられないけど、
なにかのdllからインポートした処理を実行している、GCAllocとか言う文字も見えるし、
何をしているのかわからない

にー兄さんにー兄さん

AutomaticCaptureでguidがファイル名になっているバイナリ、あはり最後に.pngってつけると画像データになった

にー兄さんにー兄さん

上の記事を参考に実行してみたら、確かにマップの作成が完了できた、すごい!!

にー兄さんにー兄さん

ツツイでこのようなアドバイスをいただいた
確かに、XRCpuImageのピクセル値を取得してから何かしらの処理をしている、という部分なので
pngエンコードが妥当か

元々自分はXRCpuImage→Texture2D→pngエンコードという順番でやっていたので
方法が違ったんだな
よくわからないdllに処理を任せているのはあまり好きではないけど......

にー兄さんにー兄さん

自分がやったときはTryAcquireCpuImageの時に帰ってくるイメージが640x480だったけど
Immersalのimmersal-sdk-samplesではフルHDのイメージが返ってきた

なんでかなぁと思ってたんだけど、取得の方法が違ったみたい
https://github.com/immersal/immersal-sdk-samples/blob/6e4c1b1055fc9f141cc10ff4166c4ba32a162981/Assets/ImmersalSDK/Samples/Scripts/Mapping/Mapper.cs#L84

ARCameraManagerからではなく、そのsubsystemから取得している

にー兄さんにー兄さん

subsytemにしても結局解像度の件は解決せず、特に変化はなしにマップ生成も失敗していました

にー兄さんにー兄さん

よくわからないけど、いやわかるんだけど
返還後のTextureFormatをRGB24にしたらなんかマップ生成成功した!!!!!
座右が逆になってたので案の定MirroX噛ませました

にー兄さんにー兄さん

config自体はframeReceivedが起きてから変更することができるらしい
変更直後には寧されるわけではないので注意したい
ということでフルHD対応もできた

にー兄さんにー兄さん

キャプチャ画像のファイル名を日付と時間でタイムスタンプ式にした
これに伴ってキャプチャデータ側にもファイル名の属性を持たせた

このアプデによって、キャプチャを複数のマップにまたがって連続使用することができ良になった

にー兄さんにー兄さん

今後のタスクを整理。
実際もう欲しい機能はできているけど、公開するとなったらまだまだ

  • mapConstructリクエストを送れるようにする
  • コードの整理
    • MonoBehaviorが重いので処理を分けたい
    • キャプチャ向けなのかマップ作製向けなのかをインスペクタでboolでスイッチさせたい
このスクラップは2021/10/04にクローズされました