singularityでOpenDroneMapを使う

# はじめに

UAVを使って写真測量をしたときに，最終的に欲しいものはオルソ画像やDSMだったりします．これらを得るためには，普通はPix4Dなどの高級なソフトウェアを使うことになりますが，[OpenDroneMap（ODM）](https://www.opendronemap.org)なるものを使うとこれをケチれるようなので，試してみました．

# 事前準備

当たり前ですが，まずは，UAVを使って写真データを撮ってきます．
この時，RINEXファイルを同時に保存するように設定することで，GNSSの位置情報を後処理（PPK）でcmレベルまで補正できます．
[https://qiita.com/ykatsu111/items/d3f4b9eba3a0ed5257ca](https://qiita.com/ykatsu111/items/d3f4b9eba3a0ed5257ca)

GCPが使える状況であればそこまで拘る必要はありませんが，これにより，得られるDSMの精度が圧倒的に高くなります．今回は，RTKLIBを使ってPPKによって*.posファイルが得られているところまではできている前提で事前準備を進めます．

次に，例えば，Phantom 4 RTK を使って写真を撮っている場合は，PPKで補正した位置情報から写真がとられた自転における位置情報を推定して，各写真の正確な位置をリストでまとめる必要があります．これについては，自分でプログラムを書く，または，下記HPにこのことを実装したExelマクロが配布されてますので，そちらを使うなりします．
[https://www.aerotas.com/phantom-4-rtk-ppk-processing-workflow](https://www.aerotas.com/phantom-4-rtk-ppk-processing-workflow)

最終的には，ODMに食わせるために，下記フォーマットに合わせて写真のファイル名とその位置座標を一覧にまとめたファイルを作っておきます．

```
epsg:XXXX
写真のファイル名 X Y Z
```

ここでは，普通はGPSを使っていると思うので，次のようなファイルになると思います．

```geo.txt
epsg:4326
DJI_0028.JPG    136.5555   46.84252125    198.609
DJI_0032.JPG    136.5553   46.84244444    198.609
```

なお，高さZは楕円体高で与えます．

参考：[https://docs.opendronemap.org/geo/](https://docs.opendronemap.org/geo/)

ここまでで，ワーキングディレクトリに以下のようなディレクトリツリーができている状態としておきます．

```
project
    |--- geo.txt
    |--- images
             |--- DJI_0001.JPG
             |--- DJI_0002.JPG
             |--- ...
```

# ODMの実行

ODMの実行には，公式ページではDockerを使うように案内されています．しかし，Dockerは，様々な事情によりライセンスを買えない場合やスパコンのような大型計算機では対応できないなど色々と問題があるので，singularityを使う方法を取ります．

まず，初めにsingularityで使うsifファイルをダウンロードします．

```
$ singularity pull --disable-cache  docker://opendronemap/odm:latest
```

もし，CUDAを使いたい場合は，

```
$ singularity pull --disable-cache  docker://opendronemap/odm:gpu
```
とします．

あとは，ODMを実行します．全てデフォル設定で実行する場合は，下記コマンドになります．

```
$ singularity run --bind /full-path/to/project:/project/code --writable-tmpfs odm_latest.sif --dsm --force-gps --geo /project/code/geo.txt --project-path /project
```

CUDAを使う場合は，

```
$ singularity run --nv --bind /full-path/to/project:/project/code --writable-tmpfs odm_gpu.sif --dsm --force-gps --geo /project/code/geo.txt --project-path /project
```

ここで、`--bind`は、ホストのディレクトリをコンテナのディレクトリにマウントするためのもので、`--bind <host>:<container>`の書式になります。デフォルトでコンテナは書き込み不可のため、これをやらないとホストにデータを保存できないそうです。また、この書式では、フルパスでディレクトリを指定する必要がある点に注意してください。

ハマったポイントとして、[公式ページのドキュメント](https://docs.opendronemap.org/tutorials/#using-singularity)の通りだとエラーも吐かずに途中で停止してしまった点です。どうやら、`images`ディレクトリは、`/project/code`直下にバインドして、`--project-path`を正しく`/project`と指定する必要があるようで。

参考：[https://docs.opendronemap.org/tutorials/#using-singularity](https://docs.opendronemap.org/tutorials/#using-singularity)

さて，全てデフォルトで綺麗なDSMが得られれば良いのですが，そこは無料のソフトということで，Pix4Dなどのような商用ソフトに比べて，デフォルトの設定は必ずしも調子良くは動きません（私は動きませんでした）．

以下にいくつ経験上チューニングした方がよいオプションをまとめておきます．

* --gps-accuracy：geo.txtに書いたGPS位置情報の精度（cm）を明示的に指定します．デフォルトでは，GPS単独測量の精度ということで10mになるようですが，PPKをやっていれば２〜３mにして良いと思います．無駄にこの値が大きいと，SFMで画像同士のマッチングがうまくいかなくなる確率が高かったです．
* --feature-quality：SFMに使う画像の解像度を指定します．ultra, high, medium, low, lowest から選べます．それぞれ，元の解像度に対して，1.0, 0.5, 0.25, 0.125, 0.0675のサイズに縮小されます．当然ですが，ultraにすると必要なメモリサイズが大きくなります．もしも，ODMを実行してみて，opensfmの段階でメモリ不足になった場合は，このオプションの修正を考えてみてください．デフォルトはhighです．
* --feature-type：SFMで画像同士のマッチングに使う特徴量の種類を指定します．akaze | hahog | orb | sift　から選べます．デフォルトはsiftですが，これは非常に多くのメモリが必要になります．もしも，opensfmでメモリ不足が起きる場合は，orbなどに変更してみると良いかもしれません．経験上は，orbにしてもマッチングの精度はそれほど悪化せずにメモリは半分以下で済みます．また，CUDAを使っている場合でsiftを使うと，入力の画像サイズの大きさに制限がかかります．GPU側で効率的に演算を行える画像サイズ以外は受け付けてもらえないので，どうしてもCUDAでsiftを使いたい場合は，予めODMに受け付けてもらえる画像サイズにリサイズしておく必要があります（トライ＆エラーで！）．ソースコードを追ってみるとpopsiftというライブラリーで適切なサイズかどうかの判定を行なっているようでした．参考：[https://github.com/OpenDroneMap/ODM/issues/1403](https://github.com/OpenDroneMap/ODM/issues/1403)
* --min-num-features：１画像あたりで検出する特徴量の最小値を指定します．このオプションの挙動が最もよくわかりませんでした．でも，わりと結果が変わるので，いい感じの値を探してみてください（トライ＆エラーで！）．多分１万〜２万個くらいがよいのでしょうか．．．
* --pc-quality：点群データの解像度を指定します．デフォルトはmidiumです．得られるDSMの実効の解像度に直接影響するので，高解像度なDSMが欲しければhighやultraにします．その代わりメモリが大量に必要になります．もしも，depth map作成時にODMがメモリ不足になって落ちるようでしたら，このオプションを変えてみてください．

その他のオプションについては，公式ドキュメントを参照してうまいこと良いパラメータの組み合わせを見つけてみてください．
公式ドキュメント：[https://docs.opendronemap.org/arguments/](https://docs.opendronemap.org/arguments/)