OpenCV ジェンガ

# はじめに
 - これは、OpenCV Advent Calendar 2018 8日目の記事です。
 - 関連記事は[目次](http://qiita.com/advent-calendar/2018/opencv)にまとめられています。
 - なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。

# TL;DR
 - OpenCVのDLLの依存関係は複雑なので、マシン間でDLLをコピーする場合は一式コピーした方が無難です

# 導入
 - 今年の筆者のツイートのうち、2番目に多くのイイねを受けたのが以下のツイートでした。[^1]

[^1]: 参考までに、一番バズったのは、OpenCV 4.0-betaのリリースノートを[深夜のテンションで和訳したツイートでした](https://twitter.com/tomoaki_teshima/status/1051858850821812226)

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">「OpenCVジェンガ」遊び方「第三者により書かれたビルド済みのプログラム（OpenCVを利用）を実行し、内部で使われているであろうライブラリを予測。dll/soファイルを1つずつ消しながら交互に起動させ、&quot;dll not found&quot;になった方が負け。もしくは、coreだけ残ったら、最後に取り除いた方の勝ち」</p>&mdash; Tomoaki Teshima (@tomoaki_teshima) <a href="https://twitter.com/tomoaki_teshima/status/1002060331403788288?ref_src=twsrc%5Etfw">2018年5月31日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

 - OpenCVをビルドしたマシンとは別のマシンにDLLをコピーする際、面倒臭がって一部のDLLだけコピったら何度も"DLL Not Found"に遭遇して辟易したので、先のツイートに辿り着くわけです。
 - とはいえ、依存関係どうなってるんだろう？と思って、可視化してみました。

# 調べ方
 - 以下のシェルスクリプトをGit Bashで動かして調べました。

```bash
#!/bin/bash
DUMPBIN=/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio\ 14.0/VC/bin/dumpbin.exe
TARGET_DIR="/c/work/sdks/opencv/x64/vc14/bin"

DLL_LISTS=`ls ${TARGET_DIR}/*[^d].dll`

for TARGET_FULLPATH in ${DLL_LISTS}
do
    TARGET_DLL=`basename ${TARGET_FULLPATH}`
    DEPEND_LIST=`"${DUMPBIN}" -dependents ${TARGET_FULLPATH} | grep -e opencv -e MSVC -e VCRUNTIME -e cudart | egrep -v '\/' | tr -d [:blank:]`
    
    for var in ${DEPEND_LIST}
    do
        echo "${TARGET_DLL}->$var" | sed -e "s/.dll/_dll/g"
    done
done
```


 - ```TARGET_DIR```にある```dll```全ての依存ライブラリを調べ、OpenCVとCUDAに関係ありそうなものだけ抽出しています。 ```dumpbin```はめちゃくちゃ便利なツールなのでオススメです。


 - これで出てきた情報に、適当にヘッダとフッタを付けたのが以下のようなテキストです。


```dot
digraph
{
rankdir=LR
opencv_calib3d400_dll->opencv_features2d400_dll
opencv_calib3d400_dll->opencv_flann400_dll
opencv_calib3d400_dll->opencv_imgproc400_dll
opencv_calib3d400_dll->opencv_core400_dll
opencv_dnn400_dll->opencv_imgproc400_dll
opencv_dnn400_dll->opencv_core400_dll
opencv_features2d400_dll->opencv_flann400_dll
opencv_features2d400_dll->opencv_imgproc400_dll
opencv_features2d400_dll->opencv_core400_dll
opencv_flann400_dll->opencv_core400_dll
opencv_gapi400_dll->opencv_imgproc400_dll
opencv_gapi400_dll->opencv_core400_dll
opencv_highgui400_dll->opencv_videoio400_dll
opencv_highgui400_dll->opencv_imgcodecs400_dll
opencv_highgui400_dll->opencv_imgproc400_dll
opencv_highgui400_dll->opencv_core400_dll
opencv_imgcodecs400_dll->opencv_imgproc400_dll
opencv_imgcodecs400_dll->opencv_core400_dll
opencv_imgproc400_dll->opencv_core400_dll
opencv_ml400_dll->opencv_core400_dll
opencv_objdetect400_dll->opencv_calib3d400_dll
opencv_objdetect400_dll->opencv_features2d400_dll
opencv_objdetect400_dll->opencv_flann400_dll
opencv_objdetect400_dll->opencv_imgproc400_dll
opencv_objdetect400_dll->opencv_core400_dll
opencv_photo400_dll->opencv_imgproc400_dll
opencv_photo400_dll->opencv_core400_dll
opencv_stitching400_dll->opencv_calib3d400_dll
opencv_stitching400_dll->opencv_features2d400_dll
opencv_stitching400_dll->opencv_flann400_dll
opencv_stitching400_dll->opencv_imgproc400_dll
opencv_stitching400_dll->opencv_core400_dll
opencv_video400_dll->opencv_calib3d400_dll
opencv_video400_dll->opencv_features2d400_dll
opencv_video400_dll->opencv_flann400_dll
opencv_video400_dll->opencv_imgproc400_dll
opencv_video400_dll->opencv_core400_dll
opencv_videoio400_dll->opencv_imgcodecs400_dll
opencv_videoio400_dll->opencv_imgproc400_dll
opencv_videoio400_dll->opencv_core400_dll
}
```

# 解説
- 各行依存関係を表し、```->```記号の左側のDLLが右側のDLLに依存することを表しています。
- 以下の例だと```opencv_calib3d400.dll```が```opencv_features2d400.dll```に依存することを意味します。

```dot
opencv_calib3d400_dll->opencv_features2d400_dll
```

# 可視化
 - さて、これを```Graphviz```を使って可視化してみましょう。

![jenga_opencv_400.png](https://qiita-image-store.s3.amazonaws.com/0/12162/78ec0f7f-c71b-9ac3-e900-e2bd6f41c7eb.png)


 - 図中の矢印は、依存する方から依存される方へ伸びています。
 - 思ったよりカオス！
 - 念の為[SVG版](https://gist.github.com/tomoaki0705/d135ebe5bddb5adc31f2a99d554707b1#file-jenga_opencv_400-svg)も置いておきます。


## 観察
- すべてのモジュールは```core```モジュールに依存する<br>
  - 例外なく全てのモジュールから```core```モジュールへ矢印が到達します。```core```モジュールの名前は伊達じゃないという訳ですね。
- ```core```モジュールのみに依存するのは```imgproc```と```ml```と```flann```<br>
  - ```core```モジュール以外に依存関係が無いモジュールを第二層とでも呼びましょうか。```imgproc```が入ってるのは何となく想定していましたが、まさかの```ml```モジュールも依存関係がありません。
  - また、```ml```モジュールに依存しているモジュールが存在しないのも特筆すべき点です。
- それ以降
  - 以降、順番に依存しているモジュールをもとに第三層、第四層と決めていくと、
  - 第三層には```features2d```、```dnn```、```gapi```、```imgcodecs```、```photo```が含まれます
  - 第四層には```videoio```、```calib3d```が含まれます
  - 第五層には```video```、```highgui```、```stitching```、```objdetect```が含まれます
- 適当に「層」って名前を使いましたが、グラフ理論的には何かちゃんとした名前がありそうな気がします。知ってる人、教えて下さい。

# CUDA 付きでビルドした場合
 - 念の為、CUDA付きでもビルドしてみました。
![jenga_opencv_400_cuda.png](https://qiita-image-store.s3.amazonaws.com/0/12162/2b2e6e4f-c111-cb39-0e26-1e6620b5dad1.png)

 - よりカオス！
 - また、念の為[contrib+CUDA付きのSVG版](https://gist.github.com/tomoaki0705/d135ebe5bddb5adc31f2a99d554707b1#file-jenga_opencv_400_cuda-svg)も置いておきますね。

## 観察
 - 通常版と違い、```cudart64_100.dll```への依存が発生しています
 - これがCUDAのランタイムライブラリであり、CUDA付きでビルドしたOpenCVのDLL群はこのdllが必要になります。
 - また、ここで[4日目の記事](https://qiita.com/tomoaki_teshima/items/6616670e53979155c58e)で言及した```cudev```モジュールについて解説します
 - ```cudev```モジュールは、先程のグラフに出てきません。何故ならば
    - どのモジュールも```cudev```モジュールに依存しないし
    - どのモジュールも```cudev```モジュールに依存されないためです。
 - 念の為```opencv_cudev400.dll```だけ```dump```してみましょう。

```
> dumpbin /dependents opencv_cudev400.dll
COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file opencv_cudev400.dll

File Type: DLL

  Image has the following dependencies:

    VCRUNTIME140.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    KERNEL32.dll

  Summary

        1000 .data
        1000 .gfids
        1000 .pdata
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        1000 .text
```
- この通り、何故か```OpenCV```はおろか```CUDA```関連のDLLにも依存しません。

```
  Image has the following dependencies:
    VCRUNTIME140.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    KERNEL32.dll
```
- 実際このモジュールに入ってるヘッダファイル達が他のモジュールに取り込まれたりします。
- なので、ビルド時に```cudev```モジュールが有効だと```core```モジュールがリンクしようとします。
- なので、依存関係としては```core```→```cudev```の向きに依存が発生します。
- 4日目の記事では```cv::__termination```によって、```cudev```→```core```の依存関係が無いのに、```extern```変数の参照で依存関係が発生し、ビルドに失敗していた訳です。
- しかも、そもそも循環参照になるから依存関係があってはいけないデザインになってますね。
- というか、ではヘッダファイルだけが必要なのであって別モジュールに分ける必要は無かったのでは？と疑問が絶えません。

# まとめ
 - OpenCVのDLLは依存関係があるので、マシン間でDLLをコピーする場合は注意しよう！
 - 明日は@UnaNancyOwen先生の記事で、執筆時のタイトルは「cv::VideoCaptureのRealSense SDKサポートについて書くかもしれない」です。お楽しみに！

# おまけ
 - [3.4.3版でも可視化したグラフを置いておきます](https://qiita-image-store.s3.amazonaws.com/0/12162/a11b124c-294a-3d01-3be8-c17155f613a0.png)
 - [同CUDA有効版(contribなし)](https://qiita-image-store.s3.amazonaws.com/0/12162/948d2903-a3f3-37f9-d5a1-94503e41cad7.png)