OpenCV各モジュールの依存関係
OpenCV Advent Calendar 2020 の17日目です。
概要
OpenCVには常軌を逸したほどたくさんのモジュールがあり、それぞれに依存関係があります。
例えば、imgproc
は core
に依存していたり、features2d
は imgproc
に依存している、などです。これをすべて明らかにしてみましょう。
依存関係がわかると、例えば以下のようなうれしい場面があります。
- OpenCVをビルドする際の
CMake
で、必要なモジュールだけを選択することができる。- 使わないのを適当に
-D BUILD_opencv_hoge=OFF
とすると、欲しかったモジュールが実はそれに依存していて連鎖的にOFFになってしまったりする。
- 使わないのを適当に
- OpenCVを使用したアプリケーションをビルドする際・動かす際、どのライブラリが必要なのかわかる。
- リンカのエラーが出て、C++のlibを1個足してみてはリビルド... ってやりますよね?(自分だけ?)
モジュールの一覧
OpenCV本体と、contribを相手にします。
- https://github.com/opencv/opencv/tree/master/modules
- https://github.com/opencv/opencv_contrib/tree/master/modules
2020/12/06 時点でのGitHub master (およそv4.5)を参照しています。
環境
- Rust 1.50.0-nightly (1c389ffef 2020-11-24)
- Graphviz
成果物
実装
後述しますが、実装が面倒だったため出力は若干不正確な点を含みます。ご了承ください。
OpenCV本体のモジュール依存グラフ
破線はOPTIONALな依存を示しています。
coreから依存している cudev
や、stitchingから依存している cudaarithm
などの cuda系モジュールは、contribにあります。本体からcontribへの逆参照(?)があるのです。(どうなってんだ)
contribのモジュール依存グラフ
contribの中だけにとどめ、本体は含めていませんが、それでも小さくて見えないヤバさです。
Zennだと見えないので、こちらに置きました。
Graphviz (DOT)
OpenCV本体
digraph "OpenCV Dependencies" {
graph [
rankdir=RL
]
"calib3d" -> { "imgproc" "features2d" "flann" }
"core" -> { "cudev" } [style="dashed"]
"dnn" -> { "core" "imgproc" }
"features2d" -> { "imgproc" }
"features2d" -> { "flann" } [style="dashed"]
"flann" -> { "core" }
"gapi" -> { "imgproc" }
"gapi" -> { "video" } [style="dashed"]
"highgui" -> { "imgproc" "imgcodecs" }
"highgui" -> { "videoio" } [style="dashed"]
"imgcodecs" -> { "imgproc" }
"imgproc" -> { "core" }
"imgproc" -> { "core" }
"ml" -> { "core" }
"objdetect" -> { "core" "imgproc" "calib3d" }
"photo" -> { "imgproc" }
"photo" -> { "cudaarithm" "cudaimgproc" } [style="dashed"]
"stitching" -> { "imgproc" "features2d" "calib3d" "flann" }
"stitching" -> { "cudaarithm" "cudawarping" "cudafeatures2d" "cudalegacy" "cudaimgproc" } [style="dashed"]
"ts" -> { "core" "imgproc" "imgcodecs" "videoio" "highgui" }
"video" -> { "imgproc" }
"video" -> { "calib3d" "dnn" } [style="dashed"]
"videoio" -> { "imgproc" "imgcodecs" }
}
contrib
digraph "OpenCV Dependencies" {
graph [
rankdir=RL
]
"alphamat" -> { "core" "imgproc" }
"aruco" -> { "core" "imgproc" "calib3d" }
"bgsegm" -> { "core" "imgproc" "video" "calib3d" }
"bioinspired" -> { "core" }
"bioinspired" -> { "highgui" } [style="dashed"]
"ccalib" -> { "core" "imgproc" "calib3d" "features2d" "highgui" }
"cnn_3dobj" -> { "core" "imgproc" }
"cnn_3dobj" -> { "features2d" "viz" "calib3d" } [style="dashed"]
"cudaarithm" -> { "core" }
"cudaarithm" -> { "cudev" } [style="dashed"]
"cudabgsegm" -> { "video" }
"cudacodec" -> { "core" "videoio" }
"cudacodec" -> { "cudev" } [style="dashed"]
"cudafeatures2d" -> { "features2d" "cudafilters" "cudawarping" }
"cudafilters" -> { "imgproc" "cudaarithm" }
"cudaimgproc" -> { "imgproc" }
"cudaimgproc" -> { "cudev" "cudaarithm" "cudafilters" } [style="dashed"]
"cudalegacy" -> { "core" "video" }
"cudalegacy" -> { "objdetect" "imgproc" "calib3d" "cudaarithm" "cudafilters" "cudaimgproc" } [style="dashed"]
"cudaobjdetect" -> { "objdetect" "cudaarithm" "cudawarping" }
"cudaobjdetect" -> { "cudalegacy" } [style="dashed"]
"cudaoptflow" -> { "video" "optflow" "cudaarithm" "cudawarping" "cudaimgproc" }
"cudaoptflow" -> { "cudalegacy" } [style="dashed"]
"cudastereo" -> { "calib3d" }
"cudawarping" -> { "core" "imgproc" }
"cudawarping" -> { "cudev" } [style="dashed"]
"cvv" -> { "core" "imgproc" "features2d" }
"datasets" -> { "core" "imgcodecs" "ml" "flann" }
"datasets" -> { "text" } [style="dashed"]
"dnn_objdetect" -> { "core" "imgproc" "dnn" }
"dnn_objdetect" -> { "highgui" "imgcodecs" } [style="dashed"]
"dnn_superres" -> { "core" "imgproc" "dnn" }
"dnn_superres" -> { "quality" } [style="dashed"]
"dpm" -> { "core" "imgproc" "objdetect" }
"dpm" -> { "highgui" } [style="dashed"]
"face" -> { "core" "imgproc" "objdetect" "calib3d" }
"freetype" -> { "core" "imgproc" }
"fuzzy" -> { "imgproc" "core" }
"hdf" -> { "core" }
"hfs" -> { "core" "imgproc" }
"img_hash" -> { "imgproc" "core" }
"intensity_transform" -> { "core" "imgproc" }
"line_descriptor" -> { "imgproc" }
"line_descriptor" -> { "features2d" } [style="dashed"]
"mcc" -> { "core" "imgproc" "calib3d" "dnn" }
"optflow" -> { "core" "imgproc" "calib3d" "video" "ximgproc" "imgcodecs" "flann" }
"ovis" -> { "core" "imgproc" "calib3d" }
"phase_unwrapping" -> { "core" "imgproc" }
"plot" -> { "core" "imgproc" }
"quality" -> { "core" "imgproc" "ml" }
"rapid" -> { "core" "imgproc" "calib3d" }
"reg" -> { "imgproc" "core" }
"rgbd" -> { "core" "calib3d" "imgproc" }
"rgbd" -> { "viz" } [style="dashed"]
"saliency" -> { "imgproc" "features2d" }
"sfm" -> { "core" "calib3d" "features2d" "xfeatures2d" "imgcodecs" }
"shape" -> { "core" "imgproc" "calib3d" }
"stereo" -> { "imgproc" "features2d" "core" "tracking" }
"structured_light" -> { "core" "imgproc" "calib3d" "phase_unwrapping" }
"structured_light" -> { "viz" } [style="dashed"]
"superres" -> { "imgproc" "video" "optflow" }
"superres" -> { "videoio" "cudaarithm" "cudafilters" "cudawarping" "cudaimgproc" "cudaoptflow" "cudacodec" } [style="dashed"]
"surface_matching" -> { "core" "flann" }
"text" -> { "ml" "imgproc" "core" "features2d" "dnn" }
"tracking" -> { "imgproc" "core" "video" "plot" }
"tracking" -> { "dnn" "datasets" "highgui" } [style="dashed"]
"videostab" -> { "imgproc" "features2d" "video" "photo" "calib3d" }
"videostab" -> { "cudawarping" "cudaoptflow" "videoio" } [style="dashed"]
"viz" -> { "core" }
"xfeatures2d" -> { "core" "imgproc" "features2d" "calib3d" }
"xfeatures2d" -> { "shape" "ml" "cudaarithm" } [style="dashed"]
"ximgproc" -> { "core" "imgproc" "calib3d" "imgcodecs" "video" }
"xobjdetect" -> { "core" "imgproc" "objdetect" "imgcodecs" }
"xphoto" -> { "core" "imgproc" "photo" }
}
手法
依存関係の調べ方
OpenCVのリポジトリにある CMakeLists.txt
に書いてあります。ocv_define_module
または ocv_add_module
というCMakeの命令を読めばわかります。
例えばhighgui
での例を見てみます。
ocv_add_module(highgui opencv_imgproc opencv_imgcodecs OPTIONAL opencv_videoio WRAP python java)
ここから、以下のように判断できます。
- このモジュールは
highgui
- 必須の依存モジュールは
opencv_imgproc
opencv_imgcodecs
- 無くても可のオプション(OPTIONAL)として
opencv_videoio
-
python
とjava
ラッパーを構築する
このことから、ocv_define(add)_module
の箇所をGitHubから取得してきて解釈すれば行けそうだとわかります。CMakeの機能だけでこのへんのデータを抽出できたりするのかもしれませんが、調べる気が起きなかったので自分で頑張ることにします。
なお、これは見た目で判断しただけで、OpenCVのCMakeLists.txtを全部読み込んだわけではないので細部は間違っているかもしれません。一応以下に参考になる記述は示しておきます。
(参考) ocv_add_module, ocv_define_moduleの定義
コメントによると、ocv_define_module
はocv_add_module
の簡易版のような位置づけらしいです。正直、大して違わないように見えますが・・・。
# declare new OpenCV module in current folder
# Usage:
# ocv_add_module(<name> [INTERNAL|BINDINGS] [REQUIRED] [<list of dependencies>] [OPTIONAL <list of optional dependencies>] [WRAP <list of wrappers>])
# Example:
# ocv_add_module(yaom INTERNAL opencv_core opencv_highgui opencv_flann OPTIONAL opencv_cudev)
# short command for adding simple OpenCV module
# see ocv_add_module for argument details
# Usage:
# ocv_define_module(module_name [INTERNAL] [EXCLUDE_CUDA] [REQUIRED] [<list of dependencies>] [OPTIONAL <list of optional dependencies>] [WRAP <list of wrappers>])
グラフの描画: Graphviz
グラフの描画は Graphviz
を使うことにします。Graphvizの詳細は省略しますが、簡単な定義(DOT言語)によってグラフを描画してくれるものです。今回の目標はこのDOT形式の文字列を吐くこととします。
以下は簡単な例です。説明不要と思います。
digraph "OpenCV Dependencies" {
graph [
rankdir=RL
]
"imgproc" -> "core"
"ml" -> "core"
"features2d" -> "imgproc"
}
Graphvizは様々な方法で簡単に触ることができます。環境構築も難しくありませんし、さらに手っ取り早いのは例えば WebGraphviz だと思います。
Graphvizは様々なオプションでグラフをデコれるのですが、本題では無いのでほぼ素の状態で済ませました。ちなみにWebGraphvizはあまりデコるオプションには対応していません。
実装について
再掲ですが、ここに書きました。そちらをご参照ください。勉強がてらRustで書いており、だいぶよろしくない実装とは思います。
処理は単純で、CMakeの記述を少し書き換えてDOT形式にするだけです。
CMakeの例:
ocv_define_module(highgui opencv_imgproc opencv_imgcodecs OPTIONAL opencv_videoio)
DOT出力:
"highgui" -> { "imgproc" "imgcodecs" }
"highgui" -> { "videoio" } [style="dashed"]
制限事項
CMakeLists.txt はかなり表現の自由度が高く、完璧に解釈はできていません。例えばこれはstitchingのCMakeLists.txtの例です。
set(STITCHING_CONTRIB_DEPS "opencv_xfeatures2d")
if(BUILD_SHARED_LIBS AND BUILD_opencv_world AND OPENCV_WORLD_EXCLUDE_EXTRA_MODULES)
set(STITCHING_CONTRIB_DEPS "")
endif()
ocv_define_module(stitching opencv_imgproc opencv_features2d opencv_flann
OPTIONAL opencv_cudaarithm opencv_cudawarping opencv_cudafeatures2d opencv_cudalegacy opencv_cudaimgproc ${STITCHING_CONTRIB_DEPS}
WRAP python)
STITCHING_CONTRIB_DEPS
という変数を利用していますが、この解釈は諦めてスキップしています。従って成果物として示した依存グラフではopencv_xfeatures2d
への依存が欠けていると思います。
その他、CMakeのコメント#
が途中に有ったり改行が自在に入ったり、INTERNAL
って何だろう...などいろいろ完璧にするには壁が多いです。
また、python, java, objcなどラッパーモジュールは除外しました。自分的に今回求めたいものではなかったというのもありますが、ocv_define_module
の解釈がまた面倒だったのが主な要因です。
おわりに
- 何度でも言いますがcontribはヤバい。どこまでモジュールを増やすんでしょう。contribの中でも依存の階層構造が結構あるのが驚きでした。
- 調べるモジュールのリストはハードコーディングしており、本当はGit(Hub)をスクレイピングして動的に調べられるとより良いですね。
- この依存グラフをインタラクティブに触れる感じなSPAでも作って、必要なモジュールをGUIでポチポチ押したらOpenCVビルド用のCMakeパラメータを吐いてくれる、みたいなのが欲しい機運です。まじめな話、contribがいくら肥大しようともCMakeで欲しいのだけ好きに選べばいいよねという整理なのでしょうが、最初に述べたように各モジュールのON/OFFは依存関係の正確な理解を要します。これだけ依存が複雑ですとそれもままならないレベルです。
-
stitching
はcudaに依存しすぎと判明したのでcontribに追いやってほしいですね。 - cvとcxcoreだけだった時代に戻ってほしい老害です。
Discussion