ワイドアパーチャ写真から 3D モデルを生成

VR180 という規格がある。端的に言うと、「180度の視野がある 3D (両眼立体視)」と解釈できるためのメタデータ作法。
QooCam と Insta360 EVO という VR180 カメラを手に入れて、身近な出来事いろいろを VR180 写真や動画で撮りまくっている。

f:id:n_shuyo:20190808185306j:plain
QooCam と Insta360 EVO (とオマケの THETA V)

で、使えば使うほど VR180 は過渡期の技術だなあと実感している。
今は VR/AR 向けの手軽な映像記録手段に VR180 と 360度カメラの他にないから、しばらく使い続けるけどね。

最大の問題は、視点が固定されてしまうので 6DoF VR や AR(MR含む) で価値を発揮できないこと。
もう一つの問題は、(少なくとも今ある全ての VR180 カメラでは)瞳孔間距離に相当する2眼のレンズの距離が固定で、近い距離(50cm以内)の被写体をうまく立体視できるように撮影しづらいこと。

ただでさえ立体視が意味のある距離は狭いのに(遠いと視差が発生しない)、近い方もうまく撮れないのはかなり痛い。手の届く範囲こそ立体視したいのに…。
ほとんどの VR180 カメラはおのおののギミックで 360度カメラに変形できるんだけど、撮り比べたらどうせ THETA の圧勝(スティッチ精度とかジャギー解消とか、拡大してみたらほぼ一瞬でわかる)なんで、そっちは360度専門のカメラに任せて、近距離撮影用にレンズ間距離を調整できる機能とかの方がよっぽど欲しい。後処理の都合で固定せざるをえないんだろうけど……。

これらの問題を根本的に解消するには、やはり 3D モデルとして撮影できるようになる必要がある。簡易でもいいので。
具体的には、深度センサを含むマルチカメラで撮影したらその場で glb などの可搬性が高くてテクスチャも包含する 3D モデルデータが生成される手法に VR180 は置き換えられると予想している(ちなみに 360度カメラは、空気遠近法が表現できるほど高解像度になる方向で生き残ると睨んでいる)。
その点 Huawei P30 Pro(3光学+1TOFカメラ)には大いに期待しまくってたんだけど、技術の場外戦で先行き不透明……。

と、ここまでが前置き。

iPhone 8 以降のインカメラや Huawei のここ数年の機種など、いくつかのスマホは深度情報を含んだ写真を撮影できる。
そしたらその深度情報から 3D モデルを生成できるよね! やってみた! という話がこの記事の本題。

f:id:n_shuyo:20190816201209j:plain:w400

対象は Huawei のワイドアパーチャ(撮影後に焦点距離を変更できる)で撮影した写真。
ワイドアパーチャ写真は、一見は最近流行りのポートレート写真(人物や対象を強調するため、それ以外をぼかした写真)に見えるが、先頭以外のフレームにパンフォーカス(手前から奥まで焦点があった)写真と深度情報が格納されていて、編集時には該当する深度以外をぼかすことで焦点距離の変更をエミュレートしている。
既存の Huawei スマホは TOF センサなど積んでおらず、深度の情報はマルチカメラによる僅かな視差(1cmくらい……)と機械学習で推定していると思われる。単色無地の壁とか奥行き均一(z方向に垂直な面)になっちゃうし。

深度情報の取り出しは次のブログやコードを参考に。

Huawei Mate 10 liteで撮った画像から深度情報(Depth of Field・Depth Map)を取得する方法 - Qiita
https://qiita.com/kotauchisunsun/items/d5c2b73ec3d76d159449
jpbarraca/dual-camera-edof
Extract the EDOF from photos obtained from Dual Camera smartphones:https://github.com/jpbarraca/dual-camera-edof

実際のコードがこちら。

撮影したワイドアパーチャ写真データ(Google Photos とか通すと必要なフレームやメタデータが落ちるんで、元のファイルをそのまま持ってこないとダメ)を edof.py に食わせると、model ディレクトリに obj と mtl とテクスチャ画像を出力する。
動作確認したのは手元にある nova lite 2 だけだが、深度情報取り出しロジックは上のブログの記事と同じなので、たぶん Mate シリーズや P20 とかで撮ったワイドアパーチャでも動く、はず。
深度情報は、nova lite 2 の場合、612x816 の unsigned char で格納されており、各値が元の写真と縮尺を合わせた場合の各領域の深度(0~255)を表している。
写真画像は重力の方向が下になるように勝手に回転してくれるが、深度情報はなぜか回転してくれないので、向きにあわせて自前で回転する必要がある。


出力された obj には法線ベクトルを計算するのをサボって付けていないため、Blender などで一度読み込んで法線ベクトルを付加しないと Unity や WebGL などで正常に表示されない。
Blender なら obj をインポートし、オブジェクトを選択(右クリック)、編集モードに移行(TABキー)、タブの「シェーディング/UV」を開いて、法線「再計算」ボタンをクリック、オブジェクトモードに戻って(TABキー)、obj なり好きな形式でエクスポートすれば OK。
裏表が逆になることもちょいちょいあるので、そのときは編集モードで「メッシュ>面>面を反転」して、エクスポートし直し。

奥行きは 0~255 の離散値なので、適当にスケールした程度で z 軸の値に使ったらガタガタのレゴブロックにようになってしまう。
そこで畳込みでぼかしを与えている*1。与え具合は --nconv と --range というオプションで指定できる。--range はもとの値からどれだけの移動を許可するか、--nconv はぼかし処理を何回行うかの指定で、思いっきりなめらかにしたければ --nconv 20 --range 10 くらいを指定しておけばいいだろう。
レゴブロックを見たかったら --nconv 0 で。


f:id:n_shuyo:20190816171617j:plain:w400

nova lite 2 で撮影したワイドアパーチャに対する作例をいくつか示してみる。
近距離の対象+背景(いわゆるポートレート写真)あたりだとそれなりなモデルを生成する。わずかな視差が多少有効かつ、奥行き推定モデルもそういうデータを一番たくさん食べてるんだろう。
f:id:n_shuyo:20190816201128j:plain:w300f:id:n_shuyo:20190816201149j:plain:w300

そこから離れるほど、まあ言葉飾らず言えば、悲惨になっていく。
近距離背景+オブジェクト(静物画みたいなやつ)はまだまし。
f:id:n_shuyo:20190816201110j:plain:w400f:id:n_shuyo:20190816201116j:plain:w300

中距離になるとかなりつらい。
このあたりを結果が書き割りのようになっているのを見ると、基本的には写真を領域に分割し、それぞれの奥行きを個別に推定しているんだろうなあという処理が見える。
壁がおかしいことになっているのは、単色無地で奥行き推定に失敗しているからだろう。
f:id:n_shuyo:20190816201047j:plain:w300f:id:n_shuyo:20190816201058j:plain:w300

奥行きのある廊下とかもダメ。
f:id:n_shuyo:20190816201014j:plain:w300f:id:n_shuyo:20190816201026j:plain:w300

遠距離もダメダメ。
f:id:n_shuyo:20190816200741j:plain:w300f:id:n_shuyo:20190816200759j:plain:w300


最後のはともかく、廊下くらいまでは TOF カメラを積めばもうちょいマシなものがでてくるんじゃないかと期待している。
P30 Pro (ないし Mate 30 Pro) は日本でもちゃんと出るんだろうか……。出初めは高すぎて手出なさそうだけど(苦笑

*1:ガウシアンフィルタによるぼかしも一応実装してみたが、あんまり良い感じにならなかった。試してみたければ --gfilter オプションで。