はじめに
- これは、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のリリースノートを深夜のテンションで和訳したツイートでした ↩

