はじめに
- これは、OpenCV Advent Calendar 2018 8日目の記事です。
- 関連記事は目次にまとめられています。
- なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。
TL;DR
- OpenCVのDLLの依存関係は複雑なので、マシン間でDLLをコピーする場合は一式コピーした方が無難です
導入
- 今年の筆者のツイートのうち、2番目に多くのイイねを受けたのが以下のツイートでした。1
「OpenCVジェンガ」遊び方「第三者により書かれたビルド済みのプログラム(OpenCVを利用)を実行し、内部で使われているであろうライブラリを予測。dll/soファイルを1つずつ消しながら交互に起動させ、"dll not found"になった方が負け。もしくは、coreだけ残ったら、最後に取り除いた方の勝ち」
— Tomoaki Teshima (@tomoaki_teshima) 2018年5月31日
- OpenCVをビルドしたマシンとは別のマシンにDLLをコピーする際、面倒臭がって一部のDLLだけコピったら何度も"DLL Not Found"に遭遇して辟易したので、先のツイートに辿り着くわけです。
- とはいえ、依存関係どうなってるんだろう?と思って、可視化してみました。
調べ方
- 以下のシェルスクリプトをGit 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
はめちゃくちゃ便利なツールなのでオススメです。 -
これで出てきた情報に、適当にヘッダとフッタを付けたのが以下のようなテキストです。
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
に依存することを意味します。
opencv_calib3d400_dll->opencv_features2d400_dll
可視化
- さて、これを
Graphviz
を使って可視化してみましょう。
- 図中の矢印は、依存する方から依存される方へ伸びています。
- 思ったよりカオス!
- 念の為SVG版も置いておきます。
観察
- すべてのモジュールは
core
モジュールに依存する
- 例外なく全てのモジュールから
core
モジュールへ矢印が到達します。core
モジュールの名前は伊達じゃないという訳ですね。
- 例外なく全てのモジュールから
-
core
モジュールのみに依存するのはimgproc
とml
とflann
-
core
モジュール以外に依存関係が無いモジュールを第二層とでも呼びましょうか。imgproc
が入ってるのは何となく想定していましたが、まさかのml
モジュールも依存関係がありません。 - また、
ml
モジュールに依存しているモジュールが存在しないのも特筆すべき点です。
-
- それ以降
- 以降、順番に依存しているモジュールをもとに第三層、第四層と決めていくと、
- 第三層には
features2d
、dnn
、gapi
、imgcodecs
、photo
が含まれます - 第四層には
videoio
、calib3d
が含まれます - 第五層には
video
、highgui
、stitching
、objdetect
が含まれます
- 適当に「層」って名前を使いましたが、グラフ理論的には何かちゃんとした名前がありそうな気がします。知ってる人、教えて下さい。
CUDA 付きでビルドした場合
-
よりカオス!
-
また、念の為contrib+CUDA付きのSVG版も置いておきますね。
観察
- 通常版と違い、
cudart64_100.dll
への依存が発生しています - これがCUDAのランタイムライブラリであり、CUDA付きでビルドしたOpenCVのDLL群はこのdllが必要になります。
- また、ここで4日目の記事で言及した
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サポートについて書くかもしれない」です。お楽しみに!
おまけ
-
参考までに、一番バズったのは、OpenCV 4.0-betaのリリースノートを深夜のテンションで和訳したツイートでした ↩