19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Universal OpenCV プロジェクト

Last updated at Posted at 2017-11-30

はじめに

  • この記事はOpenCV Advent Calendar 2017 1日目の記事です。関連記事は目次にまとめられています。また、この記事はUniversal OpenCV Projectの翻訳でもあります。
  • 皆様は、どうやってOpenCVのプロジェクトをビルドしますか? Makefile ですか、それともVisual Studioですか?
  • OpenCVを使うためには、インクルードパス、ライブラリパス、ライブラリファイル名など、幾つかの設定項目が存在します。皆様はどうやって設定されていますか?
  • 本記事ではCMakeを使った"Universal"なOpenCVプロジェクトの作り方を、Visual Studioを例に解説します。どのOpenCVのバージョンにも共通して言えることです。
  • 基本的には「それ、CMakeに任せなさい」です。

後日談というか、今回のオチ

  • それ、CMakeに任せなさい
    chart.png

リンカ設定

  • ハードコード、ダメ、ゼッタイ
  • ソースコードにOpenCVのフルパスを直接書いてはいけません。太字で書いたのには訳があります。
  • ソースコードにOpenCVのフルパスを直接書いてはいけません。大事なことなので二回言いました。
  • ここにハードコードしている例を挙げます
#if _DEBUG
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_core330.lib")
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_highgui330.lib")
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_imgproc330.lib")
#else
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_core330d.lib")
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_highgui330d.lib")
#pragma comment(lib, "C:\\foo\\bar\\lib\\opencv_imgproc330d.lib")
#endif
  • 醜い!!実に醜い!!
  • もし、下記に記す条件をすべて保証できるのであれば、ハードコードする理由がギリギリ成立するでしょう。
    • あなた本人だけがソースコードを読み、ビルドする。他人とコードを共有することはない
    • 将来絶対OpenCVのバージョンを変えない
    • 将来絶対別マシン/別ドライブでビルドしない。これはつまりあなたのコンピュータが未来永劫故障しないことを意味します
  • さて、3番目の条件をクリアできる人はいないでしょう。
  • 万が一PCが壊れ再度セットアップする際に、同じパスに同じバージョンのOpenCVをインストールする必要があるのです。
  • ライブラリの設定をハードコードするデメリットが、とても大きいと理解してもらえたでしょうか。
  • 確かに、人によってはメリットがあると感じるでしょう。
  • 利点
    • 書くのが簡単
    • さらっとビルドできる
  • 欠点
    • 他人が同じソースコードを使いたいときに、全く同じパスにOpenCVを配置する必要がある
    • OpenCVのバージョンを変えるためには、ソースコードに埋め込まれてるバージョン番号/パスをすべて書き換えないといけない
    • 2.4系列と3.0系列で互換性を持たせる場合は、いくつかのモジュールが消滅/追加されている(ex: dnn、cudaimgproc、imgcodecsなど)
    • 系列を切り替えようと思った場合には悪夢が待っています。
  • このように、パスをハードコードすることは百害あって一利なしなのです。
  • それ、CMakeに任せなさい

プロパティシートを使わない

  • Visual Studioを使う際にプロパティシートを使う場面は多々あると思います。
  • プロパティシートはコードと、ビルドのコンフィグを切り離すための素晴らしい枠組みだと思います。
  • というわけで、プロパティシートを使って、任意の場所にOpenCVをインストールし、プロパティシートでその場所を参照できます。すごい!
  • しかし、OpenCVの場所を指し示すことすら、私にとっては頭痛ものです。
  • プロパティシートを使っても先の問題を何も根本的に解決してはいないのです。
    • 例えば、OpenCVのバージョンを変えたくなった場合、プロパティシートにかかれているパス/バージョン番号すべてを書き換える必要があります
    • もしあなたのHDD/SSDがクラッシュした場合、OpenCVを再度インストールする必要がありますが、その際には全く同じパスにインストールする必要があります
    • また、わざわざ使うライブラリファイルを、全部列挙する必要が本当にあるのでしょうか?
  • それ、CMakeに任せなさい

インクルードパス

  • ライブラリファイルと同様、OpenCVのヘッダファイルをプロジェクトから参照する必要があります
  • 前述の通り、プロパティシートを使うべきではありません。
  • CMakeにパス関連の設定を任せ、あなたなはアルゴリズムの開発に専念すべきなのです。
  • OpenCVのバージョンを変えてもビルドできるように、インクルードは以下のように1ファイルだけインクルードするのがおすすめです
#include <opencv2/opencv.hpp>
  • 前述の通り、OpenCVの2.4系列と3.0系列では、片方にしか存在しないヘッダファイルがあります。例えばimwrite/imreadは2.4系列ではhighgui、3.0系列ではimgcodecsに存在します。
  • 本当にその差分を吸収しようと思ったら、下記のようなコードになります。
  #include <core.hpp>
  #include <highgui.hpp>
  #if CV_VERSION_MAJOR == 3
  #include <imgcodecs.hpp>
  #endif
  • この様に見にくくなります。行数の無駄です。その代わりに<opencv2/opencv.hpp>の1ファイルだけインクルードしましょう
  • このアプローチの欠点としてはコンパイル時間が伸びる可能性があることです
  • たしかに必要最低限のヘッダファイルをインクルードする場合に比べ、インクルードするファイル数が増えるため、コンパイル時間が伸びます。しかし、それが果たしてそんなに問題になるでしょうか?
  • ちゃんと測定はしていませんが、おそらく測定するのも難しいぐらいの時間差なのでは無いかと思います。OpenCVのバージョン違いやインクルードパスを気にする手間に比べたら、微々たる時間の増大でしょう。
  • 1ファイルだけインクルードしなさい。騙されたと思ってそうしなさい。
  • それ、CMakeに任せなさい

OpenCV world モジュール

  • worldモジュールを~~使ってはいけません~~使うとトラブルに遭う確率が上がります
  • 筆者の知る限り、OpenCV 3.2 + contribモジュール + worldビルドすると、空っぽのライブラリが生成されるという問題があります
  • 最新の3.3.1でもCUDAとworldを同時に有効にするとビルドに失敗する問題が報告されています
  • 筆者の見る限り、worldモジュールを使うメリットはありません。
    • もしworldモジュールを使ってる理由がlibファイル名を手書きしたくないから、ならば、「それ、CMakeに任せなさい
    • もしworldモジュールを使ってる理由がlib/ヘッダファイルのパスをを手書きしたくないから、ならば、「それ、CMakeに任せなさい
    • もしworldモジュールをつk「それ、CMakeに任せなさい
  • 何故かビルド済みパッケージはworldモジュールにして配布されていますが、トラブルを増やしているだけのような気がします。
  • 実際問題、自動ビルドではworldモジュールをのテストは行われていません。つまり潜在的にバグに遭遇する可能性が高いのです。
  • 筆者からすると、worldモジュールを使う選択肢は百害あって一利なしなのです。
  • 何度でも言います「それ、CMakeに任せなさい

CMake

  • さぁ、CMakeを使ってより良い生活を始めましょう
  • 特に同僚/先輩/同級生のコードを再利用する、共有する状況を考えた場合、最悪ビルド環境のせいで戦争になる可能性もあります
  • 下記手順を追うだけで、無駄な戦争を省くことができます
    1. OpenCVをCMakeを使ってビルドする(こちらは2日目の記事に詳しく)
    2. 環境変数OPENCV_DIRに↑のビルド時に設定したINSTALL_DIRのパスを設定する
    3. CMakeを使った自分のプロジェクトをコンフィグする
    4. 下記3行を追加する
CMakeLists.txt
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(myApp ${OpenCV_LIBS})
  • 各ステップについて少し解説しましょう。

1. 任意の場所にOpenCVをインストールする

  • このステップだけは本質的には必須ではありません。
  • 公式サイトからダウンロード可能なビルド済みバイナリを利用しても本質的には同じですが、OpenCVをソースコードからビルドすることを強くお勧めします。このあたりは2日目の記事でも少し掘り下げます
  • CMake時に指定した、INSTALL_DIRのパスを覚えておいて下さい。(デフォルトでは指定したビルドディレクトリ直下のinstallディレクトリが指定される)

2. 環境変数OPENCV_DIRINSTALL_DIRを設定する

  • ここが重要なポイントです
  • 環境変数OPENCV_DIRを設定することで、CMakeはこの設定を参照します
  • 例えば、こんな感じにパスを設定します→C:\opencv\build\install
  • この仕掛のおかげで、前述したライブラリ名の手書き、パスのハードコードなどをすべてCMakeが賄ってくれます。
  • Windowsを例に説明していますが、Linuxでは<install_dir>/share/OpenCVOPENCV_DIRに設定します - ちょうどだんでらいおん先生が、いいことをつぶやいていたので、追記1

find_packageする前に、OpenCV_DIRに<install_dir>/share/OpenCVをセットすればおk

3. CMakeを使って自身のアプリをコンフィグし、find_package(OpenCV)を追加する

  • ようやくCMakeの出番です。
  • 単純なアプリprogramを例に、どんなふうにCMakeを書けば良いか、具体例を示します。
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(universalOpenCV)
add_executable(program main.cpp)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(program ${OpenCV_LIBS})
  • なお、この例の中で最初の3行はCMakeを使う際に必ず書く必要がある情報です。
  • 後半の3行がOpenCVのコンフィグです。
  • インクルード/ライブラリのパス、ライブラリ名に関して、CMakeLists.txtには何も記述されていません。すごい!
    • もしマシンを再セットアップした際には、新しくインストールしたOpenCVの場所をOPENCV_DIRに設定するだけです。簡単!
    • もしOpenCVのバージョンを変えたくなったら、希望のOpenCVをビルドし、その場所をOPENCV_DIRに設定するだけです。簡単!
    • もし同僚/友人がこのコードを使いたくなったとしても、その環境下にあるOpenCVの場所をOPENCV_DIRに設定するだけです。簡単!
    • OPENCV_DIRでパスを設定してCMakeを使ってビルドするだけです。簡単!

OpenCVのバージョン違いについて

  • あとにリリースされたOpenCVの方はたくさんの機能が追加されています。また、過去に存在したのに、削除された機能もあります。
    • ex: 3.3から追加されたdnnモジュール、halide対応、
    • ex: 2.4の途中でcontribに移動されたSURF/SIFTなど
  • その場合は、find_packageに最低バージョン/ピッタリのバージョンを記述することができます 。例:find_package(OpenCV 3.3 REQUIRED)find_package(OpenCV 2.4.0 EXACT)など
  • また、大きなプロジェクトの場合は、OpenCV不要のプログラムとOpenCV必須のプログラムの両方を含んでいる場合もあります
  • この場合はREQUIREDキーワードをfind_package外し、OPENCV_FOUND変数を調べることでOpenCVを必須としているプログラムを以下のようにビルド対象から外すことも可能です
CMakeLists.txt
find_package(OpenCV)
if(OPENCV_FOUND)
    add_executable(imgProcess main.cpp)
endif()
  • このようにOpenCVのバージョンを厳密に調べることもできます。が、あまり特殊な使い方をしてない限りは通常2.4系列/3.0系列の違いも含めてバージョン切り替えしても、ほとんどの機能がそのまま利用できます 1
  • 仮に特定のバージョンが必須だったとしても、ハードコードして良い言い訳にはなりませんやはりそれ、CMakeに任せなさいなのです。
  • 個人的にCMakeを使い始める際には、かなり抵抗があったので、それまでハードコードしたりプロパティシートにべた書きしていた時代がありました。
  • ただCMakeに任せるようになった今、「ビルドのコンフィグ」と「アルゴリズム開発」を完全に切り分けることができて、かなりスッキリしたように思います。

まとめ

  • 我々はエンジニアです。単純なコピペをするためにコードを書いているのではない!
  • ぜひとも皆様もCMakeで"Universal"なOpenCVライフを!

補足

「**してはいけません」とか、お前何様のつもり?

  • はい、ご指摘はもっともです。私がどんな記事を書こうと、記事の読者にはハードコードしたりプロパティシートを使ったり、好きにコーディングする権利があります。
  • 個人的にハードコードしていた時代とくらべて、100倍幸せになったと感じているので、強めの口調で「してはいけません」と書きましたが、不愉快な場合は「**してもいいけれど、百害あって一利なしで、具体的に説明するよ」と読み替えてもらえれば幸いです2

INSTALL_DIRの挙動の違いについて

  • ちょうどだんでらいおん先生が、いいことをつぶやいていたので、追記

find_packageする前に、OpenCV_DIRに<install_dir>/share/OpenCVをセットすればおk

Windows環境の場合、OpenCV_DIRは<install_dir>なのでちょいちょい混乱しますね・・・(OpenCVConfig.cmakeが配置されている場所を指定すると覚えておくとよい)

  • というわけで、OPENCV_DIRに設定するにはOpenCVConfig.cmakeファイルが指定されている場所で、WindowsとLinuxで場所が微妙に違う、というところだけ注意しましょう。
  • これも、OPENCV_DIRを設定する1回だけ注意する必要があるわけで、それ以降は気にしないで大丈夫です。
  1. 「ほとんど」は体感2.4と3.0の間で80%ぐらいの関数、3.0系列内ならば99%の関数

  2. もしくは「強く非推奨」ぐらい

19
13
0

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
  3. You can use dark theme
What you can do with signing up
19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?