Boy Meets ML

機械工出身者が機械学習やその界隈の知識を考える

SfMについて #2:OpenSfMの中身をみる(extract_metadata)

はじめに

前回の記事mlengineer.hatenablog.comの続きで、 今回はOpenSfMの中身を少しずつ噛み砕き、解説していこうと思います。 目的は前回の記事でダンテのデスマスクを再構成して思い出をより堪能できる3次元点群の取得を行いましたが、 この3次元点群の精度をどうすれば高めることができるのか考え、 その後に処理の改善・修正やパラメータ調整を行いたいと考えているからです。
そのためには、まずOpenSfMが何をやっているのかを知る必要があります。

OpenSfM1は何をやっているのか?

サンプルコマンド bin/opensfm_run_all とはなんぞや?

OpenSfMのサンプルコードでは以下のコマンドを入力することで、対象データの3次元点群を取得することができます。

bin/opensfm_run_all data/berlin

このコマンドが何を実行してるのか中身を覗いていきます。 bin/opensfm_run_allをvimで見てみると次のようなコードとなっています。

#!/usr/bin/env bash

set -e
 
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
PYTHON=${2:-python}
echo "Running using Python command: $PYTHON"
 
$PYTHON $DIR/opensfm extract_metadata $1
$PYTHON $DIR/opensfm detect_features $1
$PYTHON $DIR/opensfm match_features $1
$PYTHON $DIR/opensfm create_tracks $1
$PYTHON $DIR/opensfm reconstruct $1
$PYTHON $DIR/opensfm mesh $1
$PYTHON $DIR/opensfm undistort $1
$PYTHON $DIR/opensfm compute_depthmaps $1

これは見てわかるとおり、bashによってpythonスクリプトを順番に実行しているだけで、 ワンクッションおいて$DIR/opensfmに対して、行いたい処理と対象データを引数として渡しています。
$DIRは/OpenSfM/binのことですので、/OpenSfM/bin/opensfmというスクリプトを使っています。
$PYTHONは人によって若干違うと思いますが、自分の環境で利用しているpythonコマンドです。
/OpenSfM/bin/opensfmは中身を見れば一行目でわかりますが、pythonスクリプトです。 そして、/OpenSfM/bin/opensfmに対して渡している引数と、おおよそ実施している処理は次の通りです。

  1. extract_metadata(●本記事の対象)
  2. detect_features
    • 対象データの各画像から特徴量を取得する処理
  3. match_features
    • 取得した特徴量を画像間でマッチングする処理
  4. create_tracks
    • マッチング結果からカメラ位置推定を行う処理
  5. reconstruct
    • カメラ位置推定の結果から3次元点群を生成する処理
  6. mesh
    • 3次元点群から可視化を行うためにメッシュを生成する処理
  7. undistort
    • カメラパラメータを利用して歪みを軽減する処理
  8. compute_depthmaps
    • カメラパラメータから対象データの各画像の深度マップを生成する処理

これらの処理をスクリプトを覗きながら順番に軽く見ていきます。 各スクリプトは/OpenSfM/opensfm/commands/にあります。 思いの外長くなってしまったので、今回はextract_metadataの話のみにしました。

extract_metadata

extract_metadata.pyは対象データの画像から、EXIFを使うことでメタデータを取得しています。 ダンテのデスマスクに関する取得されたメタデータは、/OpenSfM/data/italy_dante/にcamera_models.jsonというファイルで保存されます。

またメタデータとはなんぞや?というと、/OpenSfM/opensfm/commands/exif.pyおよび公式ページ[^1]を見る限り、 次の情報を意味しているようです。
※公式ページとの対応がわかるように英語表記も残します

  • 画像サイズ(幅、高さ) 【width and height】
    • 前の記事で紹介したiPhone Xで撮影したダンテのデスマスクの画像サイズは(w,h)=(3024, 4032)となっています
  • GPS情報(緯度、経度、高さ、DOP) 【gps latitude, longitude, altitude and dop】
    • DOPとはDilution Of Precisionの略で画像取得時のGPS精度を示すものです
    • ダンテのデスマスク画像たちはGPS情報を保持していません。持っていたとしても美術館内ですので精度に期待はできないでしょう。
  • 撮影時刻【capture_time】
    • matching_time_neighborsのオプションを付けて実行することで、撮影時刻の情報を使うようです
    • 前回記事で復元したときは、このオプションを付けた覚えはありません。
  • カメラ方向【camera orientation】
    • スマホのように、どの方向を鉛直上向きにして撮影しているのかがわかるようにするための情報のようです
    • ダンテのデスマスクは通常用いるように、カメラ側を鉛直上側として縦長に撮影しており、値は1です
  • 投影方法【projection_type】
    • iPhone Xの場合はperspectiveでした。他にはfisheyeや360度カメラをつかった場合のequirectangularなどがあります。
  • 焦点距離【focal_ratio】
    • イメージセンサの大きさを利用して算出された値であり、iPhone Xの場合は約0.78のようです。
    • この値はカメラパラメータを推定するときの初期値として利用するようです。
  • メーカ名およびモデル【make and model】
  • カメラIDを示す文字列【camera】
    • iPhone Xでは、"v2 apple iphone x back dual camera 4mm f/1.8 3024 4032 perspective 0.7777"となっており、上述の情報によってつくれた文字列のようです

公式サイトをみると、EXIFによるメタ情報の取得とカメラパラメータの取得について記載がありますが、 スクリプトに書いてある順番と少し異なる(というかわかりにくい)ので、メモを残します。 スクリプトを見ていく限りは次の段階を踏んでいました。
※公式サイトの"Providing additional metadata"を読むと書いてあったりもします

EXIFの取得]
1. exif_overrides.jsonが最も優先される
* extract_metadata.pyからdataset.pyのload_exif_overridesメソッドを見る限り
2. その次に、対象画像と同一の名前で拡張子が.exifであるファイルが存在すればこの情報が使われる
* 画像が01.jpgであれば、EXIFファイルは01.jpg.exifである必要がある
* 例えば、01.jpg.exifには上述の画像サイズ〜カメラIDの情報が含まれている
3. 最終的に上記の2つがなければ各画像に付随するEXIF情報を利用する

[カメラパラメータの取得] ここは投影方法によって処理順番が異なるので注意が必要ですが以下はperspectiveの場合で確認をしています。

  1. hard_coded_calibration, focal_ratio_calibration, default_calibrationのいずれかを用いる。
    • hard_coded_calibration:GoProやsonyの一部機種など限られたカメラに関しては焦点距離、歪係数(k1, k2)の情報がスクリプト内に記述されています
    • focal_ratio_calibratio:焦点距離EXIFから利用して、歪係数(k1,k2,k3, p1, p2)はすべて0.0とする
    • default_calibration:焦点距離はconfig.yamlから利用して、歪係数(k1,k2,k3, p1, p2)はすべて0.0とする
  2. 次にcamera_models_overrides.jsonが使われる

結構、設定の優先順位やファイル名が分かりにくいですね。。 ※間違いを含む可能性がありますので、適宜見直して上記は修正するつもりです

まとめ

OpenSfMの中のサンプルスクリプトの解説を試み、 まずは画像情報からEXIFを取得するextract_metadataに関して見ました。 公式サイトにも記載がありこれがわかりにくいので、スクリプトを見ながら処理を軽くまとめてみましたが、 それでもまだわかりにくくなってしまいました。 適宜見直しを行い、わかりやすくなるように更新していきたいと思います。

本記事から、ダンテのデスマスクを高精度に3次元復元するためには、

  1. 画像のEXIFのみではなく、自分で撮影したiPhone Xのカメラパラメータを算出して入力してあげる
    • ただし、まだ未確認ですがOpenSfMの処理にカメラパラメータの推定処理が含まれる場合はこの限りではない可能性も出てきます
    • 現段階では注意が必要
  2. 画像取得時刻をオプション指定をしてあげる
    • この情報を利用することでどのような違いが生じるのか調査できてません
    • これも現段階では注意が必要

次回は対象データの各画像から特徴量を取得する処理である、detect_features に関して見てみます。 対象データによっては相性のいい特徴点検出を選んでみたり、 深層学習を利用したデコーダを挟むことができるかもしれません。

文献