実務としての COLMAP

1 minute read

Published:

本編からの寄り道、二部構成です。前半は Structure-from-Motion と COLMAP が 何であるかを、予備知識なしで説明します。後半は実務の細部 — 実際に自分で 動かす必要がある人のために。考え方だけ知りたい方は「四つの工程」まで読んで やめてください。予告した「スケールなしで測る」記事は、この次に書きます。

観光客でいっぱいの広場から

にぎやかな午後の、街の広場に立つ彫像を思い浮かべてください。二百人が それを撮ります — 階段の上から、カフェのテラスから、間近から、通りの 向かい側から。誰も打ち合わせなどしていません。そのあと、二百枚すべてが ひとつのフォルダに集まります。

さあ、腰を据えてその写真を見ていきましょう。誰がどこに立っていたかは 一切知らされていない。それでも、驚くほど多くのことが分かります。これは 左から撮ったもの。これは高い位置、たぶん階段の上から。この二枚はほとんど 同じ場所から。なぜ分かるかといえば、同じ細部 — 石の欠け、街灯、敷石の模様 — が、写真ごとに違う位置に繰り返し現れるからです。

Structure-from-Motion とは、まさにそれを、自動で、しかも人間よりはるかに 正確にやる技術です。 フォルダを渡せば、二つのことを同時に復元します。

  • どの写真がどこから撮られたか、そして
  • 全員が向けていた対象の、三次元の形

名前がそのまま方法の説明になっています — カメラがショットからショットへ 動くこと(motion)から、形(structure)を復元する。なぜそんなことが できるのかは 以前の記事 に書きました。要するに、あなたの両目が毎秒やっているのと同じ手品です。

さて、彫像をトマトの株に、二百人の観光客をスマートフォン片手にゆっくり 一周する一人に置き換えてください。それが私の一日の仕事で、問題はまったく 同じものです。

COLMAP は、それを行うプログラム

COLMAP は Structure-from-Motion を実行するオープンソースのソフトウェア パッケージで、Johannes Schönberger と Jan-Michael Frahm による2016年の論文と ともに公開されました。以来およそ十年、既定の選択肢であり続けています。 唯一の選択肢ではありませんが、他のすべてが「これを使ったはずだ」と 黙って前提にしている選択肢です。

ボタン一つではありません。順番に実行するいくつかのコマンドライン工程の 集まりで、各工程は結果を共有のデータベースファイルに書き込み、次の工程が それを拾います。

このブログにとって重要なのは、私の知るかぎり 3D Gaussian Splatting の パイプラインがどれもここから始まるからです。 スプラットの記事 が素材として扱った点群は、実務上は COLMAP の出力です。カメラ位置も同じで、 そしてこちらも同じくらい重要です — スプラットの最適化は、その写真が どこから撮られたかを知らなければ、自分の描画と比べることすらできません。

四つの工程を、平たい言葉で

順に四つの工程:各写真の中で目印になる箇所に印をつける、二枚の写真の印が同じ実体かどうかを判定する、各カメラの位置と各点の位置を解く、レンズの歪みをまっすぐに直す。
順に実行する四工程。最初の二つは写真だけを見ています。 三つ目で初めて三次元の幾何が現れ、四つ目は次に来るツールのための 後片付けです。
  1. 目印になる箇所を見つける。 各写真を単独で眺め、あとで見分けがつくほど 特徴的な場所に印をつける — 角、斑点、石の欠け。のっぺりした一様な領域には 何も付きません。
  2. 突き合わせる。 写真を二枚ずつ取り、一方の印のどれが他方の印のどれと 同じ実体なのかを判定する。
  3. 解く。 そうした対応が何千と与えられたとき、そのすべてを説明できる カメラと3次元点の配置は一通りしかない。それを求める。
  4. 整える。 レンズによる直線の曲がりを取り除いて写真を書き直し、 下流のツールが単純で理想的なカメラを仮定できるようにする。

雑事を取り除けば、それがこの四つのコマンドです。

colmap feature_extractor \
    --database_path  colmap/database.db \
    --image_path     colmap/images \
    --ImageReader.single_camera 1 \
    --FeatureExtraction.use_gpu 1

colmap exhaustive_matcher \
    --database_path  colmap/database.db \
    --FeatureMatching.use_gpu 1

colmap mapper \
    --database_path  colmap/database.db \
    --image_path     colmap/images \
    --output_path    colmap/sparse \
    --Mapper.max_num_models=1 \
    --Mapper.init_min_tri_angle=4 \
    --Mapper.filter_min_tri_angle=0.5

colmap image_undistorter \
    --image_path     colmap/images \
    --input_path     colmap/sparse/0 \
    --output_path    colmap/undistorted

この記事の残りはオプションの話です。面白い判断は、そちら側にあるからです。

single_camera — 分かっていることは伝える

少しだけ広場に戻ります。二百人の観光客がいるということは、二百台の カメラやスマートフォンがあり、それぞれ別のレンズがあるということです。 そして「どのレンズが写真をどう歪めるか」を割り出すのも、この謎解きの一部 なのです。ところが二百枚すべてをあなた自身が一台のスマートフォンで 撮ったのなら、割り出すべきレンズはひとつしかない — そしてそう明言して おくだけで、膨大な推測が省けます。

このフラグはそれです。--ImageReader.single_camera 1 は、すべての画像が 同一のカメラから同一の設定で得られたと宣言します。つまり内部パラメータ (焦点距離、主点、レンズ歪み)を共有する、ということです。

フレームを一本の動画から切り出したのなら、これは端的に事実であり、宣言する だけでほぼ無償の精度が手に入ります。画像ごとに内部パラメータを解く代わりに、 すべての画像から拘束を受けた一組を推定することになる。未知数は減り、 条件は良くなり、ドリフトも減ります。

裏を返せば、途中でレンズを替えたりズームしたりしていれば、このフラグは嘘に なり、復元を静かに歪めます。確かめておく価値はあります。

マッチング — 時間を食う工程

二百枚の写真を手渡され、「広場の同じ隅が写っている組をすべて挙げよ」と 言われたと想像してください。ラベルなど誰も貼っていません。確実にやるには、 一枚を残り全部と突き合わせるしかない — そしてそれは、途方もない回数の 突き合わせです。

だから時間を食うのは特徴抽出ではなくマッチングなのです。一枚の写真の中から 目印を見つけるのは速く、全部まとめて処理できます。しかしどの写真とどの写真が 重なっているかという問いには、安上がりな答えがありません。

16枚の写真を輪に並べた図。左は総当たりマッチングで、すべての組に線が引かれ密な網になっている。右は逐次マッチングで、撮影順に近いものどうしだけが結ばれ、輪に沿った細い帯になっている。
総当たりマッチングはすべての組を比べます。徹底的ですが、 計算量は二乗で増える。逐次マッチングは動画のフレームが順番に並んでいる という事実を使い、時間的に近いものだけを比べます。

exhaustive_matcher はすべての組を試します。画像が N 枚なら N(N−1)/2 回の比較 — 200枚なら問題なく、1,000枚なら不快で、5,000枚では お手上げです。同時に、重なりを見落とすことがないという意味では、 もっとも安全な選択でもあります。

sequential_matcher は画像が撮影順に並んでいると仮定し(動画から切り出した なら実際そうです)、各フレームを近傍の窓の中とだけ照合します。

colmap sequential_matcher \
    --database_path colmap/database.db \
    --SequentialMatching.overlap 30 \
    --FeatureMatching.use_gpu 1

私はフレーム数で使い分けています — 数百枚までは総当たり、それ以上は逐次。 逐次マッチングについて知っておくべきなのは、時間的に隣り合う組しか見ない、 ということです。植物のまわりを一周すると、最後のフレームと最初のフレームは 空間的には重なっているのに、並びの上では遠く離れています。そのループは 閉じられず、復元は一周ぶんずれたまま、それを引き戻すものが何もない。 COLMAP にはまさにこのための vocabulary tree によるループ検出があります。 一周する撮影で逐次を使うなら、必ず有効にしてください。

mapper と、三角測量角

mapper が実際の Structure-from-Motion のソルバです。まず良い初期ペアを選び、 その二枚の間で点を三角測量し、残りの画像を一枚ずつ登録しながら、 折を見てバンドル調整をかけ直して全体の整合を保ちます。

私のオプションのうち二つは三角測量角に関するものです。日常の言葉で言えば こういうことです。あなたの両目は約6センチメートル離れていて、その隔たりが 距離感を生んでいます。では、もし両目が2ミリしか離れていなかったら。部屋は どちらの目にもちゃんと見えます — けれども二つの像があまりに似通っていて、 何が近くて何が遠いかの感覚はほとんど失われるでしょう。

二つの視点が、共に見ている対象の位置でなす角。それが三角測量角であり、 意味はまさにこれです — 角が小さいとは、奥行きについての意見が弱いということ。

だから --Mapper.init_min_tri_angle=4 は、初期ペア — COLMAP が他のすべてを その上に積み上げる二枚 — のカメラ間に最低4度を要求します。ほとんど同じ視点の 二枚は、見事に対応がついても、奥行きについてはほとんど何も語りません。 光線がほぼ平行なので、画像上のわずかな誤差が交点を遠くまで滑らせてしまう。 そこから始めるというのは、もっともらしく見えて実は間違っている復元を得る ための方法です。

--Mapper.filter_min_tri_angle=0.5 は同じ考えを事後に適用し、観測が平行に 近すぎて信用できない点を捨てます。

--Mapper.max_num_models=1 は種類の違う判断です。ジグソーパズルを思って ください。二つの塊がそれぞれ綺麗に組み上がったのに、その二つをつなぐ ピースがない — 島が二つできて、互いの位置関係は分からないまま。ひとつの 整合した場面につなげられない写真群を渡されると、COLMAP は平気でまさに それを、つまり二つも三つも別々のモデルを出してきます。それは COLMAP の誠実さです — つながりが実際になかったのだから。 しかし私の目的にとって、断片化した撮影は失敗した撮影であり、 半分しか写っていないスプラットになってから気づくより、 その場で分かったほうがいい。

植物で失敗するところ

ここまでの助言はどんな場面にも当てはまります。植物は固有の問題を加えます。 そしてそのすべてが、ひとつの前提に行き着く — Structure-from-Motion は、場面が剛体で静止していることを前提とする。

広場の彫像が理想的な被写体なのは、まさにそれが動かないからです。では観光客が もっと非協力的なもの — 忙しい市場の露店 — を撮っていたら。一枚と次の一枚の あいだに木箱は積み直され、商品の半分は売れてしまっている。二枚の写真の中で 「同じもの」を見分けるという営みが、それまでとは違う意味になってしまい、 手法そのものが自分と喧嘩を始めます。

植物とは、その露店の穏やかな版です。

葉は動く。 温室には換気扇があり、気流があり、そのどちらにも反応するほど 軽い葉があります。数秒離れた二枚のフレームの間で、葉はもう形を変えている。 その対応づけは場面の静止点すべてと幾何的に矛盾し、外れ値として解に入ります。 RANSAC がある程度は吸収します。度を越せば、登録は単純に失敗する。 現実的な答えは色気のないものです — 速く撮る。そして扇風機が止まっているときに撮る。

葉は特徴として不利。 なめらかな階調で目印の少ない緑の面からは、 キーポイントがあまり取れません。COLMAP が代わりに掴むのは、土の質感、 鉢の縁、植物ラベル、ベンチの端、温室の雑多なものです。カメラ位置を解くには たいていこれで十分で — 実際に必要なのはそちらです — ただし疎な点群は 植物の上では、初めての人が予想するよりずっと薄いことが多い。 作物の上の点が少ないことは、必ずしも失敗ではありません。

温室は自分自身を反復する。 同じ鉢、同じトレイ、等間隔のベンチ、 繰り返す構造材。反復構造は、自信たっぷりの誤対応を生む古典的な原因であり、 自信たっぷりの誤対応は、局所的には綺麗で大域的には折れ畳まれた復元を作ります。

ガラスと空。 温室の被覆越しの逆光は葉のディテールを白飛びさせ、 自動露出をフレーム間で揺らします。鏡面ハイライトはカメラとともに動く — つまり、その定義からして静止場面の前提を破る特徴です。

結果を読む

スプラッティングにGPU時間を注ぎ込む前に、三つ確認します。

第一に、何枚が登録されたか。mapper が報告します。600フレーム渡して340枚 しか登録されていないなら、思っているような撮影データは手元にありません。 そして欠けた260枚はたいてい連続しています — そこが、現場で何かが起きた場所です。

第二に、モデルがいくつ出たか。二つ以上なら、場面はつながらなかった。

第三に、疎な点群を実際に見る。形式的にではなく、開いて、その形が植物に 見えるかを確かめる。折れたり二重になったりした復元は、三秒眺めれば明らかで、 ログの上では見えません。

運用上の二点

ビルドが効きます。ディストリビューションのパッケージに入っている COLMAP は CUDA なしでコンパイルされていることがあり、その場合上のGPUオプションは 何もしないか、そのまま落ちます。私がシステムのものとは別に CUDA 有効の ビルドを置いているのはこのためです。

そしてヘッドレスの機械では、COLMAP は OpenGL コンテキストを作ろうとして クラッシュします。export QT_QPA_PLATFORM=offscreen で直ります。 これで午後を一度潰しましたし、エラーメッセージは何の役にも立ちません。

最後に手元に残るもの

登録されたすべての画像のカメラ位置、疎な点群、そして歪み補正済みの画像 — スプラットの最適化を始めるのに必要なものは、すべて揃います。

揃わないのは、その植物がどれだけ大きいかという情報です。COLMAP は幾何に ついては几帳面で、スケールについては沈黙します。理由は 以前の記事 で述べたとおり — この過程のどこにも、写真を物差しと比べる場面がないからです。


次は本編に戻ります:スケールを一度も確定させずに、植物の形質を測る方法。