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/日付の中に展開する

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

2020.3.11f1でUnityプロジェクトを作成
builtin-RPでいく

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を追加する

新しいシーンを作成したら、AR SessionとAR Session Originを追加する

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

無事にキューブを召喚できたのでARの設定は完了

考えずに実装してたら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;
    }
}

b64いらないやんけ!ってなったので消しました

マップのデータを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でスイッチさせたい
このスクラップは16日前にクローズされました
ログインするとコメントできます