OpenCVのDeep Learningモジュールの紹介

opencv3.0

opencv_contrib レポジトリに dnn という名前のディレクトリがひそかに出来ており、中を覗いてみると cv::dnn モジュールにDeep Learning関連の実装が含まれていたので軽く試してみました。Google Summer of Code (GSoC) 2015で発表され、GitHubにて実装が公開されたという経緯のようです。

It would be cool if OpenCV could load and run deep networks trained with popular DNN packages like Caffe, Theano or Torch. – Ideas Page for OpenCV Google Summer of Code 2015 (GSoC 2015)

* 2015/12/22 追記
12/21にOpenCV3.1がリリースされ、ここで紹介したDeepLearningモジュールが本体に取り込まれたようです。

環境

  • CentOS 7.1 / Ubuntu 14.03 (x86_64)
  • GCC 4.8.x
  • OpenCV 3.0.0 (+ opencv_contrib master branch)
  • GeForce GTX 760

有名なディープラーニングフレームワークである CaffeTorch で作成されたモデルの読み込みも出来るようになっているので、必要であれば Protocol Buffer などの関連ライブラリのインストールも必要です。ここでは両方試します。

Protocol Bufferは比較的新しいものが必要です。少なくともv2.3.0系だとコンパイルエラーが発生しました。CentOS 6.x系だとソースコードをコンパイルして入れるか、RPM Searchから対応するrpmファイルを探してくる必要があります。v2.5.0以上なら問題なさそうです。Ubuntuでも同様です。

また、CaffeやTorch本体のインストールはモデルを利用するだけであれば必要ありません。ただし、テストデータを生成するスクリプトを走らせる際はそれぞれインストールが必要になるので注意してください。

導入

opencv_contribレポジトリのソースを組み込んだビルド方法は公式サイトにも説明がありますが、僕の環境で指定したCMakeの設定を載せておきます。ぜひ参考になれば。GPU環境(+ CUDA Toolkit)が入っていればオプションを明示的に追加しなくても、自動的にGPU環境に対応したMakefileを作ってくれました。

Torchモデルの読み込みをサポートするには、opencv_dnn_BUILD_TORCH_IMPORTER オプションを有効にする必要があるので注意してください(ただしTorchモデルの読み込み機能は2015/12現在は未実装部分が多い、後述)。また、ここではサーバ機にインストールするのでGUI関連のツールキットは入れていません。パフォーマンス向上のために Eigen3(eigen3-devel) / TBB(tbb-devel) を入れておくのはオススメです。

実装

まずは付属のサンプルコードを参考にして、C++インタフェースからCaffe用の学習済みモデルの読み込みと予測(分類)処理を試してみます。
my cat これ入力

実行結果

雑種なんだよなぁ。小型犬より体が大きいからシャムよりラグドールに近いんじゃないかと思うのですが、両親を見たことないからわかりません。。

cv::dnnモジュールは高級なインタフェースが提供されているので書きやすいです。あと、OpenCV側でglogの出力をエミュレートしていてなかなか凝ってるなと思いました。チュートリアルやサンプルコードの内容だとこれ以上の詳しい情報が得られないので、cv::dnnモジュールの実装とDoxygenリファレンスを見ながらいろいろ試してみます。

cv::dnn::Blob クラスはネットワークの各レイヤーにおける内部パラメータ、入出力データを扱うための基本となるクラスで、実装上では cv::Mat または cv::UMat の薄いラッパークラスになっています。現在の実装では cv::UMat に未対応の機能も多いのでここでは cv::Mat を使ったコードのみ載せます。

複数の画像を一括で分類する

cv::dnn::Blob オブジェクトには複数の画像データ(バッチと呼ばれる)を格納することができます。最近のOpenCVのインタフェースは入力に cv::Mat ではなく cv::InputArray を渡すメソッドが多いようです。よくわからない人は std::vector<cv::Mat> を作って渡せばOKです。

Blobの形状(次元数等)については cv::dnn::BlobShape 構造体で管理していて、cv::dnn::Blob#shape メソッドで取得できます。operator<< をオーバーロードしているのでストリームにそのまま渡すこともできるので便利です。

ちなみにCaffeのC++インタフェースでも MemoryDataLayer の入力として std::vector<cv::Mat> を渡すことができます。

その際、prototxtファイルのdataレイヤーも MemoryDataLayer に変更しておきます。

caffe::Layer#AddMatVector メソッドに std::vector<cv::Mat> を入力データとして渡します。第二引数には学習時にラベルデータを渡す仕様になっていますが、予測時には適当なデータを入れておけばOK(ただし nullptr は渡せません)。CaffeのC++インタフェースはBoostをよく使っていることもあり、ついtypedefしたくなるほど読みにくくなることがあるんですよね。

話が逸れましたが、OpenCVの使い方に戻ります。複数の画像を入力とした場合のSoftmax(prob)層のBlobは [入力データ数、クラス数] の大きさの二次元データになり、クラス毎の確率(信頼度)が格納されています。確率上位5位までの予測結果を出力する場合も上述のサンプルコードと同様に書くことができます。現在の実装だとCaffeのArgmaxレイヤーが読み込めないみたいなのでソートする処理も別途書く必要がありました。

画像特徴量の抽出 + SVM/ロジスティック回帰での学習

Softmax(prob)層の出力から確率を得るのではなく、中間層の出力を特徴量として抽出したい場合も簡単です。読み込んだモデルのレイヤー定義(レイヤーの名前)を事前に控えておいてください。わかりやすいようにここではCaffeのリファレンスモデル(CaffeNet)を使います。prototxtファイルは以下にあります。

cv::dnn::Net#getBlob メソッドに該当レイヤーの名前を指定してBlobとして取るだけです。注意すべきは、getBlobメソッドはBlobの一時オブジェクトを返すので、大量の画像をバッチ入力した場合はコピーコストが相当大きくなってしまいます。Caffeの caffe::Net#blob_by_name メソッドのようにスマートポインタで包んだBlobを返してくれると助かるのですが。

Caffe C++インタフェースで特徴抽出する場合は以下のように書きます。前述の通りまずは MemoryDataLayer に画像を入力しておきます。

floatのポインタで取れるので、後は取り回ししやすいオブジェクトにデータを詰め替えて利用することになるでしょう。CaffeのAPIは戻り値がスマートポインタだったり生ポインタだったり統一されていないので注意が必要です。

cv::dnnモジュールで抽出した特徴量を使ってSVM(cv::ml::SVM)やロジスティック回帰(cv::ml::LogisticRegression)のモデルを学習するサンプルコードをGitHubに上げてありますので参考までに。SVMの学習する部分だけここでも紹介しておきます。

OpenCVのSVM実装は昔と比べると使いやすくなっていて、特に cv::ml::SVM#trainAuto メソッドが便利で、内部でグリッドサーチと交差検証までしてくれます。なにより他のライブラリを併用せずに済むのがプログラマにとっては一番のメリットかもしれません。

Torchモデルの読み込み

Torchモデルの読み込み機能も実装に含まれていましたが、まだ対応しているレイヤーの種類が不十分で未実装な処理も多いので使うのは難しそうです。簡単な構造のネットワークなら一応読み込めました。読み込み処理のコードはCaffeのモデル読み込みとほとんど同じです。

nn.LogSoftMax もまだ対応していないので実際は使えないモデルですが。

cv::dnn::createCaffeImporter から cv::dnn::createTorchImporter に変更するだけです。nn.View は nn.Reshape で代替できますが、nn.LogSoftMax が対応していないと使うのは難しいです。

また、モデルの学習については未サポートとのことです。

Functionality of this module is designed only for forward pass computations (i. e. network testing). A network training is in principle not supported.

OpenCVはマルチプラットフォーム対応ライブラリなので、他のフレームワークで作ったモデルをAndroidやiOS等のデバイス上で読み込んで利用できるようにすると便利そうです。

おまけ: データセットモジュール cv::datasets

画像認識関連の検証で便利なデータセットを読み込むためのモジュールも opencv_contrib レポジトリで提供されています。せっかくなのでこのモジュールの紹介もしておきます。

現在の実装で画像分類用途に使えるものは ImageNet/MNIST/PASCAL VOC/SUN の4つのデータセットが用意されています。CIFAR-10あたりも欲しいですね。

mnist train0.png (← cv::Matなのでそのまま保存できる)

MNISTデータセットの読み込み処理の実装ですが、レガシーなC言語での実装になっていてファイルポインタのNULLチェックすらされていないので、もしファイルが存在しない場合は例外やアサートもなく即座にSEGVします、注意。

画像処理領域に限らないですけど、データセットは提供方法やフォーマットが統一されているわけではありませんから、データ入出力の処理を書くのは地味に面倒なんですよね。こちらの使い方のサンプルコードもGitHubに上げてあります。

おわりに

CaffeやTorch単体ではアプリケーション開発は困難ですが、DeepLearningの機能をOpenCVの文脈に持ってくることができれば、テストの書きやすい綺麗なアプリケーションコードが作成しやすくなるかと思います。また、数あるコンピュータビジョンライブラリの中でもOpenCVのコミュニティは特に大きく、サンプルやチュートリアル等の手厚いサポートも期待できるという安心感があるので、今現在の実装に不備が多かったとしてもマルチプラットフォーム対応等も含めて徐々に整備されていくのでしょう。また、opencv_contrib レポジトリ内には他にも魅力的な機能満載のモジュールがたくさんあるので興味があれば試してみることをオススメします。

今年もたくさんのAdvent Calendarが作られてますね。参加者が少なくて閑散としていたり、管理者ががんばって空枠を埋めている風景を見ながらお酒を飲むのが好きです。また来年ですね。

あわせて読む:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です