LoginSignup
3

More than 1 year has passed since last update.

概要

  • この内容は、私が独自に調査して動作確認を取ったものをまとめたものです。
  • OpenDroneMap/ClusterODM/NodeODMの仕組み。
  • オプション関連の調査。

OpenDroneMap/ClusterODM/NodeODMの仕組み

[前提知識]OpenDroneMapのパイプライン処理

スクリーンショット 2020-09-25 11.56.36.png

OpenDroneMapのパイプライン処理の流れ

[注意]多少不正確な説明があることをお許しください。

  1. dataset 前処理。写真のリサイズやEXIFを抽出してGPSの解析をしたりする。
  2. split 前処理を元に、写真をいくつかのsubmodel(塊)にグルーピングして分割する。
  3. merge 動きを見ていると全体をsplit-mergeしているわけではないようだ。詳細は後述。
  4. opensfm Structure from Motion。sparse point cloudを生成する。デフォルトではここまで分割しないで処理する。 split-multitracks オプションでこのSfMも分割できるらしいけど、現状エラーで動作しない。
  5. mve Multi View Environment。MVSのこと。dense point cloudを生成する。ここからは分割処理する。
  6. odm_filterpoints 不明。
  7. odm_meshing 点群をいくつかまとめてポリゴンを生成する。ここは分割処理できない(ODM 0.9.10時点)。
  8. mvs_texturing ポリゴンにテクスチャを貼る。ここは分割処理できない(ODM 0.9.10時点)。
  9. odm_georeferencing ローカル座標系で処理してきた点群/ポリゴン/テクスチャにワールド座標系に変換する。具体的にはGPS座標をブレンドする。
  10. odm_dem Digital Elevation Model生成。具体的にはDTM(Digital Terrain Model)とDSM(Digital Surface Model)を生成する。
  11. odm_orthophoto オルソ画像を生成する。
  12. odm_report 最近追加された処理。レポートを生成するらしい。

ClusterODM/NodeODMの処理を見ている限り、2回split-mergeしている。
おそらく「3D textured meshes はsplit-mergeできない」制限が原因。

[単体Node] dataset
    ==> splitで複数EC2インスタンスで非同期分散処理。
[複数Node] opensfm(split-multitracksオンの場合) -> mve
    ==> mergeで一旦データを併合。
[単体Node] meshing -> texturing
    ==> splitで複数EC2インスタンスで非同期分散処理。
[複数Node] georeferencing -> dem -> orthophoto
    ==> mergeで複数データを併合。
[単体Node] report

スクリーンショット 2020-09-17 18.04.45.png
split-multitracks オプションが現在動作しないので、opensfmは分散処理されません。

ClusterODMのキューイングの仕組み

  • WebODMから2つ同時にタスクを実行してみる。
  • DummyのNodeODMのDockerコンテナで複数をdocker runしている。 /code/run.sh がOpenDroneMapの実行コマンドで、2つ同時にrunしていた。
  • アクセスが集中する場合、同時に数十個くらいのタスク要求があるので、同時に数十個キューイング状態になる。
  • しかし内部では docker run しているのでパラレルに処理している。順次処理ではなかった。
  • kubernetesクラスタの仮想ノードもしくはECS+FargateなどででCPU/メモリAutoScalingすれば解決する。 結局これらはVerticalAutoScalingできないので、別の対処方法を考えました。
# docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                                                                NAMES
59362edd3b90        opendronemap/clusterodm   "/usr/local/bin/node…"   2 minutes ago       Up 2 minutes        0.0.0.0:4000->4000/tcp, 0.0.0.0:8080->8080/tcp, 3000/tcp, 0.0.0.0:10000->10000/tcp   clusterodm
b034779a31cd        opendronemap/nodeodm      "/usr/bin/nodejs /va…"   4 minutes ago       Up 2 minutes        0.0.0.0:3000->3000/tcp                                                               nodeodm-1

# docker logs b034779a31cd
info: Authentication using NoTokenRequired
info: Listening on 0.0.0.0:6367 UDP for progress updates
info: Initialized 1 tasks
info: Checking for orphaned directories to be removed...
info: Server has started on port 3000
info: Closing server
info: Exiting...
info: Authentication using NoTokenRequired
info: Listening on 0.0.0.0:6367 UDP for progress updates
info: Initialized 1 tasks
info: Checking for orphaned directories to be removed...
info: Server has started on port 3000
info: About to run: /code/run.sh --sm-cluster http://ec2-44-234-186-36.us-west-2.compute.amazonaws.com:4000 --split-overlap 0 --split 30 --dsm --max-concurrency 30 --project-path /var/www/data aa0aa0fd-84e4-4b31-bc98-cc229ddd204c
info: About to run: /code/run.sh --sm-cluster http://ec2-44-234-186-36.us-west-2.compute.amazonaws.com:4000 --split-overlap 0 --split 30 --dsm --max-concurrency 30 --project-path /var/www/data 0077b885-aaa3-4b52-9c93-93170c2a030d
# docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
59362edd3b90        clusterodm          0.01%               42.39MiB / 15.46GiB   0.27%               167MB / 167MB       9.78MB / 77.8kB     35
b034779a31cd        nodeodm-1           2.00%               493MiB / 15.46GiB     3.11%               246MB / 174MB       19.7MB / 242MB      29
# docker stats --no-stream --no-trunc
CONTAINER ID                                                       NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
59362edd3b9093090ad6e5c96c668c4467150ff307f148a8041c9a5d0f4c58ee   clusterodm          0.24%               78.18MiB / 15.46GiB   0.49%               247MB / 168MB       9.78MB / 119kB      108
b034779a31cd988ced1444655a4530e693efbf08387a59d992bc0fd2a8f20ea2   nodeodm-1           0.04%               548.2MiB / 15.46GiB   3.46%               249MB / 255MB       20.7MB / 531MB      33

ClusterODMで2つのタスクをキューングしている様子。
順次処理しているわけではなく、同時並行処理をカウントしているだけでした。
スクリーンショット 2020-09-17 19.21.32.png

NodeODMで2つ同時にパラレルに実行している様子。
スクリーンショット 2020-09-17 19.21.54.png

イメージ図。
スクリーンショット 2020-09-17 20.16.57.png

ClusterODMの実際の動作

スクリーンショット 2020-09-25 12.09.09.png

オプション関連の調査

  • 関係がある可能性が高いもののみ調査します。
  • オプションの文字列から意味を読み取りにくいのと、実際の動作が想像しにくいので、確認の意味です。

ClusterODMのオプション

-c, --cloud-provider

-c, --cloud-provider    Cloud provider to use (default: local)

デフォルトの local でいいみたい。

--downloads-from-s3 <URL>

--downloads-from-s3 <URL>
Manually set the S3 URL prefix where to redirect /task/<uuid>/download requests.
(default: do not use S3, forward download requests to nodes, unless the autoscaler is setup, in which case the autoscaler's S3 configuration is used) 

AWS/S3を利用するので、将来カスタマイズするのに必要になる可能性がある。

--no-splitmerge

--no-splitmerge
By default the program will set itself as being a cluster node for all split/merge tasks. Setting this option disables it. (default: false)

cluster node = ClusterODM実行のコンテナ?有効にするとエラーで止まる。なんのために存在するか謎。

ソースコード上の実際の処理部分

        // When --no-splitmerge is set, do not allow seed.zip
        if (!config.splitmerge){
            if (fileNames.indexOf("seed.zip") !== -1) throw new Error("Cannot use this node as a split-merge cluster.");
        }

seed.zip ??? 初めて見る言葉。
seed.zipはClusterODMにはなく、本家OpenDroneMapに存在する。
https://github.com/OpenDroneMap/ODM/blob/0145f4c60ed2a7b151d5ff2fae90b5be0be1ecab/opendm/remote.py#L296

    def create_seed_payload(self, paths, touch_files=[]):
        paths = filter(os.path.exists, map(lambda p: self.path(p), paths))
        outfile = self.path("seed.zip")

        with zipfile.ZipFile(outfile, "w", compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zf:
            for p in paths:
                if os.path.isdir(p):
                    for root, _, filenames in os.walk(p):
                        for filename in filenames:
                            filename = os.path.join(root, filename)
                            filename = os.path.normpath(filename)
                            zf.write(filename, os.path.relpath(filename, self.project_path))
                else:
                    zf.write(p, os.path.relpath(p, self.project_path))

            for tf in touch_files:
                zf.writestr(tf, "")

        return outfile

さらにソースコードを追っていくと。
https://github.com/OpenDroneMap/ODM/blob/0145f4c60ed2a7b151d5ff2fae90b5be0be1ecab/opendm/remote.py#L330

    def execute_remote_task(self, done, seed_files = [], seed_touch_files = [], outputs = [], ):
        """
        Run a task by creating a seed file with all files in seed_files, optionally
        creating empty files (for flag checks) specified in seed_touch_files
        and returning the results specified in outputs. Yeah it's pretty cool!
        """
        seed_file = self.create_seed_payload(seed_files, touch_files=seed_touch_files)

seed.zip=seed_filesを作るということ = remote_taskを作るということ。
要するに、remote taskを作ることを禁止する = ローカルでのみsplit-mergeすること、らしい。
意味としては --no-splitmerge ではなくて --only-local-splitmerge にすべきだと思う。紛らわしい。混乱する。
今回は、remote=EC2でsplit-mergeしたものを処理してほしいので、このオプションを指定してはいけない。




--public-address <http(s)://host:port>

--public-address <http(s)://host:port>
Should be set to a public URL that nodes can use to reach ClusterODM. (default: match the "host" header from client's HTTP request)

通常運用環境ではURLが固定になるので指定した方がいいかもしれない。

--token <token>

--token <token>
Sets a token that needs to be passed for every request. This can be used to limit access to the node only to token holders. (default: none)

動作検証済。WebODM Lightningもtokenを使用しているので、通常運用環境でも使用する方がいいかもしれない。

--asr <file>

--asr <file>
Path to configuration for enabling the autoscaler. This is combined with the provider's default configuration (default: none)

このオプションを指定するとCluster AutoScaler構成になる。

NodeODMのオプション

-d, --deamonize

Set process to run as a deamon

コンテナ運用ではフォアフラウンドにする必要があるので、通常運用環境では必要ないかもしれない。おそらくネイティブインストール用のオプション。

-q, --parallel_queue_processing <number>

Number of simultaneous processing tasks (default: 2)

ClusterODMに登録するNodeODMはDummyノードなので、キューイングする数を指定する。

--token <token>

Sets a token that needs to be passed for every request. 
This can be used to limit access to the node only to token holders. (default: none)

使用予定。ClusterODMにもtoken設定があるので、その関係性を要調査。

--webhook <url>

Specify a POST URL endpoint to be invoked when a task completes processing (default: none)

指定すべきかどうか要調査。
EC2のASRではタスクが終了するとEC2側からこのwebhookに終了通知を送って(イベントドリブン)EC2をCleanup(terminate)している。

--max_concurrency <number>

Place a cap on the max-concurrency option to use for each task. (default: no limit)

同時実行数。検証の結果、NodeODM内でOpenDroneMapを同時に複数runしている動作。

OpenDroneMapのオプション

--sm-cluster

  • ClusterODMでASR構成している必要がある。今回はAWS/EC2。 asr = autoscaler?説明がないけど多分この略称。
  • ClusterODMに登録してあるダミーNodeODMは locked:true に設定して、全ての処理をバックエンド(EC2)に任せている必要がある。
  • 指定は http://<cluster-odm-ip>:4000 の形式。通常運用環境ではHTTPSの方がいい?
  • 検証したところ、この指定はwebhookにもなっているようで、submodel終了時にイベントをwebhookしてEC2をterminateしている。

--split --split-overlap

  • データセット=写真の分割設定。submodel_0000からはじまってsubmodel_0001と続いていく。
  • --split はいくつに分割するか?ではなく、何枚の写真をひとまとまりにするか、の設定。 --split 300 に設定すると300枚の写真をひとまとめにする。
  • --split-overlap はデフォルトで150m(単位はメートル)。submodel間の重なる距離を指定している。 デフォルトの150mだとかなり重なりが多いので0にしている。 150mオーバーラップすると、900枚を3分割しようとしても300/300/300にはならなくて、1つのsubmodelは300枚よりも多くなる。 オーバーラップを0にするとうまくデータ分割できないのではないか?と思うけど、検証が十分ではない。

-–split-multitracks

  • 現時点でマニュアルに載っていない。NodeODMのオプションには存在する。
  • OpenDroneMap Communityでは Split multi-track reconstructions. の意味らしい。
    https://community.opendronemap.org/t/split-multitracks/4727
  • これを指定すると IOError/OSErrorになる。原因はまだ分からない。
  • reconstructions=MVS処理(一番時間かかる処理)であれば是非複数VM分散処理しなければならない。

このオプションの処理部分はOpenDroneMapのソースコードに存在する

    parser.add_argument('--split-multitracks',
                       action=StoreTrue,
                       nargs=0,
                       default=False,
                       help='Split multi-track reconstructions.')

                resplit_done_file = octx.path('resplit_done.txt')
                if not io.file_exists(resplit_done_file) and bool(args.split_multitracks):
省略
                            #We need the original tracks file for the visualsfm export, since
                            #there may still be point matches between the tracks
                            shutil.copy(s+"/tracks.csv", path+"/opensfm/tracks.csv")

                            #Create our new reconstruction file with only the relevant track
                            with open(path+"/opensfm/reconstruction.json", "w") as o:
                                json.dump([v], o)

                            #Create image lists
                            with open(path+"/opensfm/image_list.txt", "w") as o:
                                o.writelines(map(lambda x: "../images/"+x+'\n', v["shots"].keys()))
                            with open(path+"/img_list.txt", "w") as o:
                                o.writelines(map(lambda x: x+'\n', v["shots"].keys()))
省略
                # Aligned reconstruction is in reconstruction.aligned.json
                # We need to rename it to reconstruction.json
                remove_paths = []
                for sp in submodel_paths:
                    sp_octx = OSFMContext(sp)

split-multitracks オプションを有効化すると、opensfm/reconstruction.json もsplit-mergeするようになるみたい。

reconstruction.json はOpenSfMによって作成される「疎な(sparse)点群データ」。
https://www.slideshare.net/takayukimizutani9/open-dronemap
現時点ではエラーで止まるので、OpenSfMの処理はsplit-mergeしなくてもいいと判断(できれば分割したい)。その次の処理MVSが一番時間かかってsplit-mergeしてくれればいいので、今はこのオプションは有効化できなくても構わないと判断。
オプションの名前が分かりにくい。意味で言うと --split-opensfm にすべき。そうすればすぐに意味が分かる。

OpenDroneMapのsplit-mergeとは

split-merge処理のソースコード

  1. setup.py 前処理。具体的には以下。 --resize-to --min-num-features --num-cores --matcher-neighbors
  2. run_matching.py pre-matching処理。写真のEXIFを抽出してGPS座標の位置関係を解析する。
  3. split.py matchingの結果からどこでsplit(分割)してsubmodel化できるか判定してデータをいくつかの塊に分ける。
  4. run_reconstructions.py ここの reconstruction はOpenSfMのStructure from Motionの意味。
  5. align.py なんらかのalign(揃える)処理を行ってから次の処理に渡すらしい。ソースコードに説明なし。
  6. run_dense.py dense point cloud を生成する処理。MVS(Multi View Stereo)のこと。

[参考]OpenDroneMap内の用語

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3