虚苦心観察ブログ

ブログ管理者である虚苦心が私利私欲に基づいて書いているブログです。主にガジェットのレビューだったり、画像処理のことだったりを記事にしています。

OpenCV + CMake + Visual Studio

はじめに

CMakeはUnixWindowsなど各ビルド環境に応じたビルドファイルを生成することで、各プラットフォームに依存しないビルドファイル生成プログラムです。 ライブラリで利用されていることが多いため、ライブラリを利用する側にメリットがないように思われる方も多いと思いますが、実はライブラリを使う側にとってもとてもメリットのある便利なプログラムになっています。

面倒な設定をCMakeで解決

ライブラリを自作のプログラムに組み込むときにはインクルードファイルやリンク時に必要なスタティックオブジェクト、シェアードオブジェクト、もしくはLibファイルなどそれらの設定を作成するプログラムに応じてMakefileを作成したりVisualStudioの設定を変更したりしなけらばなりません。 しかしCMakeで作成されたライブラリにはインクルードファイルやリンク時に必要なファイル群の場所が書かれたファイルが用意されており、これを読み込ませ、数行の設定を記述するだけでライブラリを使う設定が完了してしまいます。 VisualStudioを使っている方はこれらの設定が特に面倒ですが、それらがなくなりプログラム内にpragmaを書く必要もなくなるのでとても簡単にプロジェクトの作成ができるようになるのでおススメです。

簡単な利用例

というわけでCMakeを使ったライブラリとして、また画像処理ライブラリとして有名なOpenCVをプログラムに取り込む方法を紹介しようと思います。

さっそくですがCMakeの設定ファイルであるCMakeLists.txtを載せます。

CMAKE_MINIMUM_REQUIRED(VERSION 3.0)

find_package(OpenCV REQUIRED)

add_definitions(${OpenCV_DEFINITIONS})
include_directories(${OpenCV_INCLUDE_DIRS})
link_directories(${OpenCV_LIBRARY_DIRS})

add_executable(OpenCV_sample main.cpp)
target_link_libraries(OpenCV_sample ${OpenCV_LIBRARIES})

set(CompilerFlags
    CMAKE_CXX_FLAGS
    CMAKE_CXX_FLAGS_DEBUG
    CMAKE_CXX_FLAGS_RELEASE
    CMAKE_C_FLAGS
    CMAKE_C_FLAGS_DEBUG
    CMAKE_C_FLAGS_RELEASE)
foreach(CompilerFlag ${CompilerFlags})
    string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()

find_package()コマンドはCMakeで作成された外部ライブラリを取り込むためのコマンドです。 これを記述することで以下の変数が生成されます。 OpenCV_DEFINITIONS OpenCV_INCLUDE_DIRS OpenCV_LIBRARY_DIRS OpenCV_LIBRARIES

またfind_packageコマンドは探すパスが決まっています。 以下は本家のドキュメント。 find_package — CMake 3.0.2 Documentation

もう一つは日本語でまとめてくださっている方。 qiita.com

結論としては環境変数CMAKE_PREFIX_PATHに探索してほしいパスを追記していくことでfind_packageコマンドが見つけてきてくれます。 追記するパスにはOpenCVConfig.cmakeが含まれている必要があります(OpenCVConfig.cmakeを含むディレクトリがあればいい場合もあります。詳しくは上記のリンクを参照)。

以下のコマンドではfind_packageコマンドによって作成された変数をプロジェクトに設定しています。 これらのコマンドは同じ階層全体に反映されます(CMakeLists.txtをディレクトリごとに作成し、上位階層から読み込ませることでプロジェクトを階層で分けることができます)。 add_definitions include_directories link_directories

add_definitionsの動作はいまいちわかっていません。 include_directoriesはC/C++のインクルードディレクトリを設定しています。 link_directoriesはC/C++のビルド時にリンクするファイル群のディレクトリを設定してしています。

add_executableコマンドは実行ファイルを作成するためのコマンドです。 第一引数が生成される実行ファイルの名前、第二引数以降は実行ファイルを生成するのに必要なソースファイルです。 この第一引数はtarget_link_librariesの第一引数にも使われます。

target_link_librariesではlink_directoriesで設定したディレクトリ内のリンクするファイルを設定しています。 このコマンドは先に説明したadd_definitionsなどとは異なり、一つのプログラムごとに設定するコマンドになります。 なので、どのプログラムに設定するかを指定する必要があります。 ここでは第一引数にadd_executableの第一引数で指定した名前を記述しています。

set(CompilerFlags...以下のコマンドはOpenCV3.0を使っている場合に必要な特別な記述です。 僕が作成した記事ですが、詳しくはこちらをご覧ください。 kyokushin.hatenablog.com

あとはCMakeで実行→ビルド

これらの設定を記述し、CMakeコマンドもしくはCMakeのGUIプログラムで実行するとMakefileやソリューションファイルが生成されます。 設定に間違いがなければビルドが正常に完了し、実行ファイルが出来上がるはずです。

CMakeを使うことの本質的なメリット

CMakeはプラットフォームの異なる環境でのビルドを容易にするために作成されたツールであるため、一つのCMakeLists.txtを作成するだけでUnixWindowsでビルド可能なファイル群を生成してくれます(複雑なプログラムだと環境ごとに分岐が必要になる場合もあります)。 もちろんMacもできるはず(Macユーザーではないのでしらない) ローカルではWindows、サーバーはLinuxというような開発をしている人でも同じファイルをローカルとサーバー間で共有できるため、わざわざサーバーにログインせずともローカルで開発し、開発が終わったらそれをサーバーにアップする、というような行程でのプログラミングも可能になります。 CMakeを使うことでプラットフォーム毎に設定を書くという二度手間が必要なくなり、さらに柔軟な開発スタイルを選択できるという大きなメリットを得ることができるのです。

最後に

駆け足でCMakeを使ったビルド方法について説明してみました。 いかがだったでしょう?CMakeの魅力に気付いていただけたでしょうか?

今回の記事は僕自身のための覚書として書きました(毎回そうなのですが) なので分かりにくい点が多々あると思いますが、質問があればコメントに書いていただければと思います。

今回はここまで。

CMake + OpenCV "3.0" + Visual Studio

OpenCV3.0をVisualStudioで使おうとするとこんなエラーが出ます。

error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value 'MDd_DynamicDebug' in main.obj

これはビルド済みのOpenCVが使っているライブラリとビルドしようとしているプログラムのライブラリが同じ必要があるのに違うために起こっているようです。

これはVisualStudioのプロジェクトの設定から「C/C++」→「コード生成」と移動し、「ランタイムライブラリ」の項目をMT→MDに変更すればコンパイルでき、実行可能です。

ただCMakeを使っている場合はCMakeLists.txtを変更するたびにこの変更が毎回元に戻ってしまうので面倒です。
なのでCMakeにMD→MTに変更するための設定を追記します。

stackoverflow.com

重要なのはこちら
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()

これを書くことで設定中に存在しているMDをすべてMTに変更してくれます。
めでたしめでたし

Fatal Exception: java.lang.NoClassDefFoundError android.support.v7.appcompat.R$layout

前回の続き。
前回の問題はエディター上では解決されるが、端末上では解決されていなかった。

どういうわけかインポートした外部ライブラリがロードできないでいた。
その一つがSupport Library。今までロードできていたものが使えなくなっていたのだ。

FATAL EXCEPTION: java.lang.NoClassDefFoundError: android.support.v7.appcompat.R$layout

こんなエラーが出ていて、同じ問題に直面している人がいた。stackoverflow.com

どういうわけかMultidexに対応すると解決できるらしい。
ただ、これらの問題が発生するのはAndroid4系から前のバージョン。5系では発生していない。

んー、つらい。

Error:Execution failed for task ':app:transformClassesWithDexForDebug'.

公開中のアプリのメンテナンスでSDKをアップデートしたところ、端末へのインストール時にタイトルのようなエラーが発生した。

エラーの詳細は以下の通り

Error:Execution failed for task ':app:transformClassesWithDexForDebug'.
> com.android.build.transform.api.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'C:\Program Files\Java\jdk1.8.0_51\bin\java.exe'' finished with non-zero exit value 2

上記はStackoverflowで引っかかったエラーの内容をそのまま掲載しているが、私のアプリで発生していたエラーも全く同じ。stackoverflow.com

どうもAndroidStudio、つまりはAndroidのビルドシステムのアップデートにより仕様が変更された様子(詳細はしらない)。
リンク先に解決方法が書かれているが、ちょっと工程が多すぎ。
実際にはbuild.configにmultiDexEnableの項目を加えればよいみたい

stackoverflow.com

とりあえずアプリは動くようになったので解決した。
ここまで書いてなんとなく理解がすすんだのだけど、build.configのdefaultConfigに書かなければいけない項目が変更されたのだろう。おそらく。


Androidは昔に比べれば安定しているとはいえアップデートが早く、仕様が固定されるとも限らないのは辛いですね。
それに比べWindowsLinuxのような開発環境だと仕様が変わることはまずないし、開発手段もたくさんあるので気に入らなければ乗り換えれば良いから精神的に楽なんですよね。Windowsのデスクトップアプリを作ってて最近そういうことを実感してきました。

今回はここまで。

Android Studio + Microsoft Android Emulator

ちょっと手間取ったのでメモ

Microsoft製のAndroidEmulatorをインストール&起動してみたのだがadbに認識されずデバッグに使えないという現象が発生。
公式のサポートに解決方法が載っていた。

Troubleshooting the Visual Studio Emulator for Android

が、そもそもレジストリキーが存在しなかった。
ならキーを新しく作ればいいのか?と思い、

  1. HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node の下に「Android SDK Tools」という名前でキーを作成するとフォルダができる。
  2. そのフォルダを選択し、「Path」という名前で文字列値を追加
  3. 追加する文字列はAndroidSDKのルートフォルダのフルパス。add-onsとかbuild-toolsなどSDKが全部詰まっているフォルダ

以上をしたらエミュレータを起動。起動済みなら再起動。これで動いた。

AndroidStudio単体なら以上で問題なかったのだが、AndroidStudioからAndroidMonitor(Eclipseで動くやつ)を起動するとADBが再起動してしまうためかエミュレータを認識しなかった。

AndroidStudioを起動せずにAndroidMonitorを起動した場合は問題なくエミュレータが認識されることは確認できたが、AndroidStudio↔AndroidMonitorの切り替えは未解決。結構問題なんだよなー・・・

Windows に ipython notebook インストール

基本的にはこちらを参考にした
Python for Windows インストールメモ

ただし、ipythonそのもののインストールはGithubからWindows用のバイナリをダウンロード・インストールする必要があるgithub.com

pipでのインストール時にバージョンを指定する必要がある様子
pip install ipython==3.2.1

FragmentのレイアウトでViewPagerを使い、PagerAdapterにFragment(State)PagerAdapterを使う場合の注意点

題名のままですが、実際にはFragmentの中でFragmentを管理する場合の注意点です。

どういった状況で発生するかというと(私の場合)、
1. Parent1Fragmentの中でViewPagerを使ってChildAFragmentとChildBFragmentを表示します。
2. このParent1Fragmentから別のParent2Fragmentへ遷移します。
3. 戻るボタンでParent1Fragmentへ戻ります。

このような流れをとるとFragment(State)PagerAdapterへ与えたFragmentManagerがgetFragmentManager()メソッドから得たものだと、1.の状態が正常に復元されません。
Parent1Fragmentの状態だけが復元され、ChildAFragmentとChildBFragmentは復元されないという気持ち悪い状態に陥ります。

これはFragmentの中でFragmentを管理している場合に起こります。
またFragmentの状態の復元はFragmentの遷移が発生し、戻るボタンで元のFragmentへ戻った場合に発生します(他の場合もあるかも)。
FragmentA→FragmentB→(戻るボタン)→FragmentA というような移動の時に発生し、Homeボタンでアプリを終了したときには発生しないようです。

解決方法はgetChildFragmentManager()メソッドで別のFragmentManagerを用意すること。
これによりFragmentの管理が階層化され正常に復元がされるようになります。


getFragmentManagerを使おうがgetChildFragmentManagerを使おうがプログラム上は正常に動いてしまい何が原因だかわからず半日つぶしてしまいました。。。。
誰かの助けになれば幸いです。


参考
Fragmentをネストする際の注意点 - Just for Fun
Y.A.M の 雑記帳: Fragment in Fragment 時の注意点