RICOH THETA の動画からフォトグラメトリーする

今回やること

THETA Z1で録画した動画からmeshroomをつかってで3Dを起こします。通常は動画ではなく静止画でやる方が良いです。THETAは一回のシャッターで3~5秒ぐらい待つ必要があるので、割と面倒です。動画でやるとノイズ・ボケが強く精緻なフォトグラメトリはできませんが、最長10m程度のルームサイズならいけそうでした。

屋内での例:1分撮影20フレームに1枚利用

以下は自宅をスキャンした例です。4部屋を1分で移動してみた経路がちゃんと終えています。

カメラ軌道と特徴点@屋内

THETAはカメラが2個背中合わせなので、その位置が取れている事も見て取れます

カメラ位置の推定結果

以下のように非常に粗いCGですが、おおよその位置を把握するものが得られます。

フォトグラメトリ結果@屋内

屋外の例(3分程度の動画 30フレームに1枚利用)

グランフロント(google Map)の広場で試してみました。周りに常に歩行者がいるのですこしだけ難条件です。ガラス張りの建物が多く、建物はかなりの難条件が予想されます。カメラの軌道は約3週分しっかりと取れています。

特徴点とカメラの位置@グランフロント前

ちなみに綾印部分にカメラ位置合わせにつかわれた特徴点があつまって筋をつくっていますが、これは以下のような点字ブロックです。そういったランドマークになる構造が重要なのがわかりますね。

特徴のもとになったと思われる点字ブロック
フォトグラメトリ結果
参考:入力写真

駅前の建物も構築できているので、iPhoneのLIDARスキャンでは対応できない全体の位置関係確認には有用です。しかしながら、以下のように左の渡り廊下は再現されていません。

再現出来ない構造例(赤線部)

一方で点群で示される、特徴点(カメラ位置を決めるのに使う)は渡り廊下があるあたりプロットされているので不思議に感じるかもしれません。コレについては後述しますが、結論からいえばこの橋がテカっているので、カメラ位置によって表面の模様が違うように見えてしまうのが原因です。そういったテカったものがあると周囲を巻き込んで再構成が不可能になります。

私の使い方

このような粗いCGでもインスタレーション作品等を置く場合の配置検討ぐらいには使えます。作品CGの合成の場合は、THETAのカメラ位置がわかっているので、CGの中でおおよ位置をあわせてカメラと作品をおいて、背景(Skybox)を撮影したTHETAの画像にすると以下のようなCGを上塗りした360度合成画像が作れます。(フォトグラメトリで作ったCGは位置合わせ用なので、描画には使わない。 合成CGはBlenderで作ってるのですが、その方法はまたの機会に

THETAの360度画像にCGを合成した例

必要なもの

  • PC WinでもMacでもLinuxでもOKなはず。(Winでしか試してない)
  • 16GB以上のメモリをつんだマシン、cuda対応のGPU(CPU律速になるのでへちょくてOK)をつんでればbetter。
  • python環境(OpenCV/pyexifパッケージが必要)
  • MeshRoom(オープンソースのフォトグラメトリソフト)

処理の流れ

THETAでDouble Fisheye(魚眼)で動画撮影します。(THETA Z1は標準でこの形式しか動画出力できない。)2つのカメラ画像が1枚にくっついてるので、基本はソレを切り離してmeshroomに突っ込むだけです。ただし、後述のような事情から追加の加工をします。

なおググるとequirectangularを経由してピンホール画像をを複数枚作る方法がでてきますし、私も試してみましたがMeshroomでの合成結果は僅差で劣る上に、実質のカメラ数が多くなるためにdepthmap生成が非常に遅くなるのでボツにしました。

撮影時の注意

THETAに限らずフォトグラメトリでは以下の点に注意が必要です。

  • カメラを高速に回転させない(画像がぶれる)
  • カニ歩きで視差をちゃんと作る。(THETAの場合、レンズを左右方向に向けて真っ直ぐ歩くでOK)
  • テーブル下にかくれた地面なども必要なら、高さ方向の移動も必要(見えないモノはフォトグラメトリされない)

THETA特有の注意点として、動画撮影中に最低1回ゆっくりとした、その場回転を入れる。(フロント・リアカメラの相対位置の結び付け)その場回転はできるだけ画像特徴量が多いところで手ぶれを少なくするため、体ごと回すと良いです。

動画からの画像切り出し

以下のスクリプトを使って動画を切りだします。以下の様に引数にファイル名を指定します。スペース区切りで複数指定すると一括処理します。出力フォルダ名は「元ファイル名+_rig」ですがmeshroomに食わせる時にはフォルダ名を「rig」に変更して下さい。フォルダ名が「rig」でないと前後のカメラが同一の機体に取り付けられた「rig camera」として認識してくれないので、計算精度的にちょっと不利になります。

python split_theta_mov.py R0010205.MP4

https://gist.github.com/akirayou/99d8b55e7c588c672b32dc33d4c17118

ちなみにrigカメラとして認識されると入力画面に鎖のマークが出ます。出ない時はフォルダ名や出力ファイルのexifが正しいか要確認です。

一括処理されたファイルは番号が被らないように出力してくれるので以下のように、フォルダごとコピーすることで一つのデータとして纏められます。(動画を分けて撮った後に纏めるのにつかう)

Meshroomの実行

Meshroomに読み込むのはrigフォルダの中の「0」「1」とかかれたフォルダです。ドラッグアンドドロップで2つのフォルダをまとめて突っ込んで下さい。十分な特徴量のある画像であれば、あとはstart押すだけで走りますが、以下のようにカメラ情報等を追加したほうがより安定して実行されます。

カメラ情報の追加(一回のみ)

初期値として、レンズ歪みなどの情報が入っている事が望ましいです。まずセンサーデーターベースに登録します。以下のEdit Sensor Databaseをおすとファイル名を教えてくれるので、そのファイルに以下のように「RICOH;CUTED;8.8;digicamdb」とかかれた行を追加します。この設定はmeshroomインストール後1回だけです。

レンズ情報(カメラ射影パラメータ)の追加(毎回)

レンズの焦点距離、歪みは自動で推定されますが指定していたほうが計算が安定します。設定するのは「(Initial) Forcal Length」「Principal Point」「Distortion Params」。厳密にキャリブレーションした値を使いたければLockedにチェックをいれますが、通常は適当に誰かのカメラのパラメータをコピペしてLockedを外しておけば良い感じに微調整してくれます。

私のカメラは以下の値だったので、適当にソレぐらいの値をいれればOKです。

カメラ0
Forcal:      
546.8359100582505
PrincipalX:  
958.2558295024851
PrincipalY:  
958.4779035860312
Distortion:
0.33418964563703096
-0.4844049108504701
0.290705957027171
-0.0660216755711601
カメラ1
Forcal:      
534.2810405201699
PrincipalX:  
949.6200820463862
PrincipalY:  
953.2650985618549
Distortion:
0.3221384178864152
-0.41119056380191743
0.21843650843357354
-0.04385714287984768

なお、これら設定はGUIでポチポチいれるよりも、一旦mgファイルを保存して編集したほうがてっとり早いかもしれません。

Featureの追加(FeatueExtraction設定)

フォトグラメトリ演算の中でカメラ位置を推定しますがソレをより安定させるにはakaze特徴量を追加してみてください。

屋外(長距離)ではdepthmapの設定見直し

上の屋外の例ではdepthMapの設定をさらに変更しています。まずDownscaleを1に設定しています(デフォルトは2)。遠い所は小さく映るので計算時間はかかりますが高解像度のdepthmapが必要になります。また、ここで「SGM:Nb Neighbour Cameras」を10から40に変更しています。これは沢山の写真をつかって比較的信頼性の高いdepth mapを作ります。「信頼性の高いdepth map」移行のmeshing工程で重要になります。

遠すぎる(or少ししか見えないもの)を無視するMeshingの設定

今回の例ではさらにMeshingでもMinObservation(参考)を10から40に変更しています。これはdepthmapから3D点群を起こす際に、広い角度から見ている(≒撮影範囲から近い)部分の点群のみを用います。これは一見単純に撮影範囲を絞るだけのオプションに見えますが、meshingアルゴリズムにとっては重要です。Meshingアルゴリズムノイズが霧のようにかかった点群の中から構造由来の点群を抽出する処理を行います。この霧はテカった素材がある部分で特に大量に発生します。この霧から構造を抽出する処理は霧が濃いほど急激にメモリを消費するので、わりと大胆に計算を打ち切ります(MaxInputPoints/MaxPointsの設定次第)。なのでノイズを減らすために上のdepthmapで質の悪いdepthを切り落とし、Meshingのminobservationの設定で処理対象となる点群を減らします。

Meshing:MinObservation

時には使う枚数をそもそも減らす

ちなみに、上の例では30フレームに1枚の画像で再構成しましたが10フレームに1枚など使う枚数を増やすとカメラ位置推定の安定性は上がるモノの、Meshingに入ってくる点群が増えるために有限のメモリでは逆に再構成が難しくなるという状況なります。

以上の設定で以下のように、渡り廊下が復元出来るようになりました。逆に撮影枚数が少ないものは無視されるので、オクルージョン(前の物体で隠される事)が多いグランフロント・駅ビルは消えてしまっています。

Deptmap downscale 1,Depthmap cameras 40 Mesing MinObservation 40の設定での例

おまけ:画像切り出しの流れ

THETA動画から切りだした画像は以下のように2つのレンズ由来の画像があるので、左右に切りだします。スクリプト冒頭に設定がありますが20フレームごとに切りだしています。0.6秒に1枚撮影している換算です。撮影動画にあわせて調整します。

左右に分割した上で、目視確認がしやすいように上向きに向きをあわせて、切りだした後に灰色のマスクを追加しています。コレには細かい理由があります。meshroomdで対応している魚眼レンズのモデルはOpenCVのfisheyeです。これは画角180度未満しか対応していません。180度以上の所にマッチする特徴量があるとカメラパラメータを推定するのにこけるのか、うまくカメラ位置を推定出来ない事がああります。なので、できるだけ特徴量を捉えないように内側画像の平均輝度の灰色で周囲を塗りつぶしてあります。そもそも180度付近は撮影者が映り込んだりレンズ歪みも大きい事が多いので、安全をみて少し内側を塗りつぶしています。