Gipuma(https://github.com/kysucix/gipuma)とFusibile(https://github.com/kysucix/fusibile)は下記論文のプログラムで、ソースコード、スクリプトが公開されています。
S. Galliani, K. Lasinger and K. Schindler, Massively Parallel Multiview Stereopsis by Surface Normal Diffusion , ICCV 2015
というわけで、実際に動かせるか試してみたところ、いくつかソースを改変したりする必要がありました。この記事は、そのときのメモです。
GipumaとFusiblileでできること
ざっくり言うと、複数の画像(異なる視点から同一被写体を写した画像)から3次元点群が手に入ります。
Gipumaは複数の画像からデプスマップ(plyファイル)にしてくれます。
Fusibileは、複数のデプスマップを統合して、1つのplyファイルを生成します。
Gipumaを複数回実行して、最後に1回Fusibileを実行してくれるようなスクリプトも公開されています。
実行環境
- windows 8.1(64ビット)
- cygwin(スクリプトを実行するため)
- Microsoft Visual Studio Express 2013 for Windows Desktop
動作確認までの流れ(サンプルデータセットの実行)
- ソースコードのビルド
- スクリプトの実行
ソースコードのビルド
公式のソースはgithubから入手できます。wiki(https://github.com/kysucix/gipuma/wiki)を見るとvisual studioでも動作テストできているようです。
しかし、私の実行環境ではビルド時にエラーが出てしまいました。原因は良く分かりません。適当に改変してとりあえず動いたコードは以下です。
gipumaもfusibleもcmakeしてからvisual studioでビルドします。
私はGUI版CMakeを使いました。
fusibileのビルド
cmake-gui起動後に、where is the source code にfusibileフォルダを記入します。
where to build the binaries: は任意でOKです。今回は、とりあえず・・・/fusibile/build_vs_2013_x64としました。以下の図のようなカンジです。
次にconfigureボタンを押します。そして出現するコンパイラ指定ダイアログで Visual Studio 12 Win64を選択して、Finishボタンを押します。
コンパイラを指定するダイアログは以下の図のようなもの。
Finishボタンを押すところは以下の図のようなもの。
これまでに、opencvをcmakeしていたり、cudaをインストールしていたりすると、自動で設定が反映されていると思います。
私の環境ではconfigureしたときに既に以下のような値が設定されていました。
そして、generateボタンを押します。するとvisual studio用のslnファイルが生成されているはずです。
次に、build_vs_2013_x64/fusibile.slnを起動します。
理由は良く分かっていないのですが、Debugモードではビルド時にエラーが出てしまいます。
ですので、Releaseモードでビルドします。これでfusibile.exeが生成されるはずです。
gipumaのビルド
手順はfusibileと同様です。
CMakeしてから、CMakeによって生成されるgipuma.slnを起動し、Releaseモードでビルドします。これでgipuma.exeが生成されるはずです。
スクリプトの実行
middleburyのサンプルデータの場合
middlebury大学のデータセットで試してみます。gipumaフォルダをカレントディレクトリとして以下を実行して、画像セットをダウンロードします。
$sh ./scripts/download-middlebury.sh
するとgipumaフォルダ内にdataフォルダが生成されて、dataフォルダ内に画像データセットがダウンロードされます。
画像データセットは何種類かあります。ここでは、試しにdinoSparseRingを入力としてみます。dinoSparseRing用のスクリプトはscripts/dinoSparseRing.shです。
スクリプト内には、gipumaとfusibileの実行ファイルのパスが書かれています。スクリプト内のパスに合わせるには、ビルドして生成されたバイナリをコピーします。
gipumaフォルダにgipuma.exeを置きます。gipumaフォルダをカレントディレクトリとして../fusibileにfusibile.exeを置きます。
ツリー状に見ると↓のようなカンジ。
├─fusibile
│ └─fusibile.exe
├─gipuma(カレントディレクトリ)
│ ├─gipuma.exe
│ ├─data
│ │ └─dinoSparseRing
│ └─scripts
なお、必要に応じて、shファイルにopencvのdllへのパスを指定します。
PATH="${PATH}:/cygdrive/略/opencv-3.1.0/build_vs_2013_x64/install/x64/vc12/bin"
export PATH
あとはgipumaフォルダでscripts/dinoSparseRing.shを実行すれば、処理が始まります。
$./scripts/dinoSparseRing.sh
処理が終われば、./results/dinoSparseRing/consistencyCheck-日時 に最終結果 final3d_model.py が保存されているはずです。
MeshLabで開くと↓こんなカンジ。
法線表示すると↓こんなカンジ。
VisualSfMの結果を入力とする場合
VisualSfMの結果を入力とする場合は、VisualSfMでpmvsを実行したときに生成されるフォルダを、gipuma実行時の引数に--pmvs_folderオプションで指定すればよさそうです。
また、fusibile実行時も同様に--pmvs_folderオプションで指定します。
以下は実行コマンドの例です。
gipuma.exe --pmvs_folder . --camera_idx=0 --max_views=5 --blocksize=11 -no_display --algorithm=pm
上記コマンドはpmvsの出力フォルダをカレントディレクトリと想定しています。
ディレクトリ構成例としては以下です。
pmvs.nvm.cmvs
├─00
│ ├─fusibile.exe
│ ├─gipuma.exe
│ ├─visualize
│ ├─txt
│ └─models
上記コマンドで、visualize/00000000.jpgを参照視点(?)としたデプスマップが生成されます。出力先はresultsフォルダです。resultsフォルダはカレントディレクトリに生成されると思います。
--camera_idxの値を変えて複数回実行することによって複数のデプスマップを生成することができます。
fusibileの実行例は以下です。カレントディレクトリはgipuma実行時と同じ想定です。
fusibile.exe --pmvs_folder . -input_folder results/ -images_folder visualize --cam_scale=1 --disp_thresh=0.5 --normal_thresh=30 --num_consistent=1
参照視点を変えながら何度もgipumaを実行するのは面倒なのでとりあえずスクリプトを書いてみました。実行時引数にgipumaのパス、fusibileのパス、pmvs出力フォルダのパスを入力して実行します。
オプション | 意味 | 省略時(=デフォルト) |
---|---|---|
"-dll" | opencvのdllのパス | "." |
"-g" | gipuma.exeのパス | "gipuma.exe" |
"-f" | fusibile.exeのパス | "fusibile.exe" |
"-pmvs" | pmvs出力フォルダのパス(visualizeやtxtフォルダがあるディレクトリ) | "." |
# -*- coding: utf-8 -*-
"""
example: $python run_gipuma.py -dll E:/programs/opencv/bin -g E:/programs/gipuma.exe -f E:/programs/fusibile.exe -pmvs E:/data/vsfm_result/pmvs.nvm.cmvs/00
"""
import sys
import argparse
import os
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--opencv_dll_path', '-dll', type=str, default='.')
parser.add_argument('--gipuma', '-g', type=str, default='gipuma.exe')
parser.add_argument('--fusibile', '-f', type=str, default='fusibile.exe')
parser.add_argument('--pmvs_folder', '-pmvs', type=str, default='.')
args = parser.parse_args()
os.environ['PATH'] = os.environ['PATH'] + ';' + args.opencv_dll_path
visualize_dir = os.path.join(args.pmvs_folder, 'visualize')
if not os.path.exists(visualize_dir):
print 'Error: visualize folder does not exist.'
sys.exit()
image_num = len(os.listdir(visualize_dir))
# parameters for gipuma
max_views = 5
block_size = 11
# call gipuma each view
for i in xrange(image_num):
cmd = '{} --pmvs_folder {} --camera_idx={} --max_views={} --blocksize={} -no_display --algorithm=pm'.format(args.gipuma, args.pmvs_folder, i, max_views, block_size)
print cmd
os.system(cmd)
# parameters for fusibile
cam_scale = 1
disp_thresh = 0.5
normal_thresh = 30
num_consistent = 1
# call fusibile
cmd = '{} --pmvs_folder . -input_folder results/ -images_folder visualize --cam_scale={} --disp_thresh={} --normal_thresh={} --num_consistent={}'.format(args.fusibile, cam_scale, disp_thresh, normal_thresh, num_consistent)
print cmd
os.system(cmd)