Immersalでキャプチャ画像から後でまとめてMap作成をしたい
目的
Immersal公式のMapperアプリを使うと、キャプチャした画像を逐一鯖に送信することで
Immersalのマップを作成する
この方法だと、wifiがないところや電波が悪いところで使用するときに結構不便だったりするので
REST APIでキャプチャ作業が一通り終わった後に一括でアップロードしたい
こちらに構想メモがある
すでに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を追加する
テキトーにシーンにキューブを配置したのち、設定がちゃんとできているか確認するために
いったんAndroidビルドを行う
無事にキューブを召喚できたのでARの設定は完了
考えずに実装してたらRESTでマップを生成する処理から書こうとしていた(おい
いったん実装の手順を整理したいかも
- ARCameraからCPUImageをCapture
- persistantDataPathに画像を保存する
- キャプチャした瞬間のカメラパラメータと姿勢をメモリに保存
- ボタンなどのアクションでjsonとしてpersistantDataPathに保存
一旦これまでを実装する。
この時は日付とか関係なしにpersistantDataPathに直でファイルを展開してしまってよさそう
そのあとに
- persistantDataPathから画像とjsonを入力
- Immersalにリクエストを送信し確認
- 何枚か送信してmap constructを実行してみる
- 最後に全部をまとめる
Immersal REST APIのMapConstructionのリクエスト、ドキュメントの中で揺れていて草
どれが正しいんじゃ
とりあえずARのカメラ画像を取得→テクスチャへ変換までが完了した
ちゃんと非同期で動いていてとても偉い!
非同期処理への理解も深まりました。とは言ったもののたまにアプリごと落ちたりするのでまだ完全じゃないかも?
実は初めて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いらないやんけ!ってなったので消しました
LocationServiceを使って位置情報を取得することもできた
マップのデータをjsonで保存することにも成功した
あとは画像を保存してリクエスト多くってみる
日を跨いでの作業
まずは画像を保存してみる。ヌルヌル動く状態で保存したい
あと、Cameraパラメータは毎回取得したほうがいいのか検証したい
スマホって横向きにしたらカメラパラメータ変わる?
画像を保存してみたら、なんか横向きになってるな?
issueにもあったんだけど、カメラのセンサ状態をそのまま取得しているのでしょうがないんだとか
そうなんか...
まぁでも位置合わせもこれと同じデータでやってたし、トリマこのままで行ってみるか
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メソッドが割り当てられているみたい
AutomaticMapperでは、マッピングしている間はAutomaticCapturemanager.CaptureImages()
というコルーチンが起動しているっぽい
この中でマップ作成のライフサイクルが回っている
コルーチンの中ではAutomaticCapture.Captureメソッドが呼ばれている
こいつがキモっぽいんだよなぁ
ちなみにAutomaticCaptureめちゃくちゃ便利そうだったので、
最大枚数を300枚にしてRGBキャプチャをtrueに設定した
これは使っていきたい
AutomaticCapture.Capture()
の中ではAsyncCaptureJob
というキャプチャの処理を意味するオブジェクトが作られ、
そのジョブの設定をしていき、ジョブのキューに放り込むということがされているみたい
AsyncCaptureJob
はAsyncJob
というクラスを継承しており、
AsyncJob.RunJobAsync()
メソッドをオーバーライドしている
おそらくcaputre以外の処理もすべてAsyncJob
に共通化されていて、キューの中に入ったジョブはRunJobAsync
を次々に実行されていく構図になってるんだと思う
AsyncCaptureJob.RunJobAsync
の中身を深掘ってみれば、
最終的なデータがどんな風に保存されているのかを見ることができる
RunJobAsync()
の中身、割と普通のことが書かれているなぁという印象
ただちょっと気になるのが、JobCaptureAsync
にあるencodedImageとimagePathという
プロパティがあって、どうやらencodedImageは使われていないっぽい
byte[] image = File.ReadAllBytes(imagePath);
という感じで画像のbyte列を取得しているんだけど
ここだけどんなデータなのかがパッと見わからなかった
ん-png画像のバイナリデータが妥当か
このimagePathって具体的にどんな値が入るのか
そのpathにはどんな画像が入っているのか調査したい
ちなみにRunJobAsyncではhttpリクエストをしているけど、UnityWebRequestは使っていなかった
AutomaticCapture.cs、1.14.0になるまではなかったんですね
history見てて思った
Pathにはやはり画像的なものが入っているっぽい
AutomaticCapture.Capture()
の中で、XRCpuImageを処理している部分があるんだけど、
その中で唯一よくわからないのが個々のコード
その直前にあるGetPlaneDataRGBではXRCpuImageからbyte列を取得しているものの、
そのあとになぜかかなり低レベルなコードが出現している
ImmersalCoreSDKふぁオープンじゃないので見せられないけど、
なにかのdllからインポートした処理を実行している、GCAllocとか言う文字も見えるし、
何をしているのかわからない
AutomaticCaptureでguidがファイル名になっているバイナリ、あはり最後に.pngってつけると画像データになった
んー正直もう新しいプロジェクトでやるの諦めようかな......
brocollyさんのQiita記事に従ってできることをまず確認したい
上の記事を参考に実行してみたら、確かにマップの作成が完了できた、すごい!!
ツツイでこのようなアドバイスをいただいた
確かに、XRCpuImageのピクセル値を取得してから何かしらの処理をしている、という部分なので
pngエンコードが妥当か
元々自分はXRCpuImage→Texture2D→pngエンコードという順番でやっていたので
方法が違ったんだな
よくわからないdllに処理を任せているのはあまり好きではないけど......
自分がやったときはTryAcquireCpuImage
の時に帰ってくるイメージが640x480だったけど
Immersalのimmersal-sdk-samplesではフルHDのイメージが返ってきた
なんでかなぁと思ってたんだけど、取得の方法が違ったみたい
ARCameraManager
からではなく、そのsubsystemから取得している
subsytemにしても結局解像度の件は解決せず、特に変化はなしにマップ生成も失敗していました
よくわからないけど、いやわかるんだけど
返還後のTextureFormatをRGB24にしたらなんかマップ生成成功した!!!!!
座右が逆になってたので案の定MirroX噛ませました
結局まだ解像度が低いままなのでなんとかしたい
configを変更すればいけるかもしれないとのこと
config自体はframeReceivedが起きてから変更することができるらしい
変更直後には寧されるわけではないので注意したい
ということでフルHD対応もできた
キャプチャ画像のファイル名を日付と時間でタイムスタンプ式にした
これに伴ってキャプチャデータ側にもファイル名の属性を持たせた
このアプデによって、キャプチャを複数のマップにまたがって連続使用することができ良になった
今後のタスクを整理。
実際もう欲しい機能はできているけど、公開するとなったらまだまだ
- mapConstructリクエストを送れるようにする
- コードの整理
- MonoBehaviorが重いので処理を分けたい
- キャプチャ向けなのかマップ作製向けなのかをインスペクタでboolでスイッチさせたい
とりあえずいい感じにまとまったのでGitHubで公開しました
これでいったんひと段落かなぁ