takminの書きっぱなし備忘録 このページをアンテナに追加 RSSフィード Twitter

2015-12-18

OpenCVでIntegral Channel Featuresを試す (OpenCV Advent Calendar)

| 23:00 | OpenCVでIntegral Channel Featuresを試す (OpenCV Advent Calendar)を含むブックマーク OpenCVでIntegral Channel Featuresを試す (OpenCV Advent Calendar)のブックマークコメント

この記事は、OpenCVアドベントカレンダー18日目の記事です。

http://qiita.com/advent-calendar/2015/opencv


OpenCVで物体検出器を作成するにあたり、手っ取り早いのはopecv_traincascadeという実行ファイルを使用して検出器をトレーニングすることです。

OpenCV2.xからHaar-like特徴以外にもLBPやHOGといった特徴量も選択することができました。

http://d.hatena.ne.jp/takmin/20141008/1412776956


HOGは人物検出などで有効な特徴量のため[1]重宝していたのですが、OpenCV3.0になりいざ検出しようとすると、HOGはだめだよーというAssertionエラーが。。。

トレーニングはできるのになぜ???とググってみたら、以下のような情報が

http://code.opencv.org/issues/4336

We decided to drop the current HOG cascades in OpenCV 3.x. The implemented HOG features are quite weird - different from Dalal's interpretation of HOG, different from P. Dollar integral channel features. In xobjdetect we slowly grow superior ICF/ACF+WaldBoost-based detector, which is there already and will be improved during 2015.

OpenCV 3.xから現状のHOGカスケードは削除することに決めたよー。実装がDalal(HOGの著者)やDollar(ICFの著者)のものと違ってとっても変だから。代わりにxobjdetectモジュール内のICF/ACF+WaldBoostベースの検出器をゆっくりだけど改善していくよ。2015年中に改善するよ。」




まじっすか。。。

この2015年中の改善っていうのをどこまで信じてよいのやらですが、とりあえずOpenCV3.0に入っているICFを試すことにしました。



余談ですが、現バージョンのOpenCVにはHOGDescriptorというクラスが用意されており、これを使えば元論文提案されたHOG+SVMの検出器が使えます。

train_HOG.cppというサンプルコードがあるので、比較的簡単に検出器の学習ができそうです。


閑話休題

というわけで、このxobjdetectモジュールICFとACFとは何ぞやですが、ICFは"Integral Channel Features"[2]、ACFは"Aggregate Channel Features"[3]の略で、cv::CascadeClassifierで使用されているViola&Johnsのアルゴリズムを拡張させたものです。

ICFアルゴリズムについては、この@tabe2314さんの素晴らしい解説があるのでこちらをご覧ください。

簡単に要約するとICFは、色や勾配ヒストグラム、エッジなどの様々な種類の特徴を、それをsoft cascadeという機械学習アルゴリズムによって認識に有効なものだけ選択して検出器を作成する手法です。特徴量を計算する際にIntegral Imageというテクニックを用い、またsoft cascadeでは非物体領域を早い段階で除外することで高速化を行っています。(soft cascadeでは弱識別機1つ1つが閾値を持っている)

一方ACFは、通常のマルチスケール物体検出では解像度の異なる画像を生成してから特徴量算出するところを、解像度の異なる特徴量を直接算出することで高速化した手法です。


OpenCVICFでは元論文ICFとは機械学習の仕組みが異なっており、元論文で使用されているmultiple instance pruningを用いたsoft cascade[4]ではなく、WaldBoost[5]という機械学習アルゴリズムが使われています。これは、早いステージ(弱識別機)で非物体領域だけでなく物体領域も判定するようなアルゴリズムです。



さて、いざOpenCVICFを試そうとしても、ろくなドキュメントもサンプルコードも存在せず、かろうじてAPIドキュメントが存在するのみです。

http://docs.opencv.org/3.0.0/d6/dc8/classcv_1_1xobjdetect_1_1ICFDetector.html


というわけで、APIドキュメントとソースコードを読みながら使い方を探ってみました。

尚、ここではINRIA Person Datasetを使って実験しました。



まず学習はこんな感じ。

//! textファイルを1行ずつ読み込みdst_vectorへ格納
void ReadList(const std::string& filename, std::vector<cv::String>& dst_vector)
{
	std::ifstream ifs(filename.c_str());
	std::string buf;
	while (ifs && std::getline(ifs, buf)) {
		dst_vector.push_back(buf);
	}
}


void main()
{
	cv::String pos_list = "poslist.txt";	// 正例画像リストを記述したテキストファイル
	cv::String neg_list = "neglist.txt";	// 負例画像リストを記述したテキストファイル

	// 画像ファイルパスを読み込み
	std::vector<cv::String> pos_img_files, neg_img_files;
	ReadList(pos_list, pos_img_files);
	ReadList(neg_list, neg_img_files);

	// 学習パラメータ指定
	cv::xobjdetect::ICFDetectorParams icf_params;
	icf_params.feature_count = 25000;
	icf_params.weak_count = 100;
	icf_params.features_type = "icf";
	icf_params.bg_per_image = 5;
	icf_params.model_n_cols = 96;
	icf_params.model_n_rows = 160;
	icf_params.alpha = 0.02;
	icf_params.is_grayscale = false;
	icf_params.use_fast_log = false;

	// 学習
	cv::xobjdetect::ICFDetector icf_detector;
	icf_detector.train(pos_img_files, neg_img_files, icf_params);

	// 学習結果のファイル保存
	cv::FileStorage cvfs("icf_model.txt", cv::FileStorage::WRITE);
	cvfs << "icf";	// ラベルは何でも可
	icf_detector.write(cvfs);
}


使い方は意外と簡単でした。

まず学習パラメータcv::xobjdetect::ICFDetectorParamsクラスのインスタンスへ指定します。

	cv::xobjdetect::ICFDetectorParams icf_params;
	icf_params.feature_count = 25000;
	icf_params.weak_count = 100;
	icf_params.features_type = "icf";
	icf_params.bg_per_image = 5;
	icf_params.model_n_cols = 96;
	icf_params.model_n_rows = 160;
	icf_params.alpha = 0.02;
	icf_params.is_grayscale = false;
	icf_params.use_fast_log = false;

パラメータの意味は以下の通りです。

変数意味
feature_count生成する特徴の数
weak_countWaldBoostで選択する特徴の数=弱識別器の数
feature_type"icf"または"acf"を指定
bg_per_image1枚の背景画像から何枚負例を生成するか
model_n_cols学習モデルの幅(pixel)
model_n_rows学習モデルの高さ(pixel)
alphaWaldBoostで使用する目標false negative rate (多分。。)
is_grayscale入力がグレースケール画像か?
use_fast_loglogの計算を簡易的なものを使用するか?


トレーニングはcv::xobjdetect::ICFDetectorクラスのtrain()メソッドで行います。

	cv::xobjdetect::ICFDetector icf_detector;
	icf_detector.train(pos_img_files, neg_img_files, icf_params);

ここで、第一引数は正例画像のパスを格納したstd::vector<cv::String>型、第二引数は背景画像のパスを格納したstd::vector<cv::String>型です。

実際には負例画像は第二引数指定した背景画像の中からランダムにicf_params.bg_per_imageで指定した数の画像を切り取って使用します。

また、正例画像および背景画像から切り取られた負例画像はすべてmodel_n_colsおよびmodel_n_rowsで指定したサイズにリサイズされてから学習に使用されます。

第三引数は学習パラメータを格納したICFDetectionParamのインスタンスです。

2416枚の正例画像と1218枚の背景画像の学習に手元のDynabookで三時間以上かかりました。


学習結果はICFDetectorクラスにはread()とwrite()のメソッドが用意されているので、簡単に読み書きできます。

	cv::FileStorage cvfs("icf_model.txt", cv::FileStorage::WRITE);
	cvfs << "icf";	// ラベルは何でも可
	icf_detector.write(cvfs);

write()する前にFileStorageにラベルを書き込まないとエラーになるので気を付けましょう。


続いて検出はこんな感じで書けます。

	cv::xobjdetect::ICFDetector icf_detector;

	// 学習したモデルの読み込み
	cv::FileStorage cvfs("icf_model.txt", cv::FileStorage::READ);
	icf_detector.read(cvfs["icf"]);

	// 試験画像の読み込み
	std::string img_file = "test.png";
	cv::Mat img = cv::imread(img_file);

	// 検出
	std::vector<cv::Rect> objs;
	std::vector<float> values;
	icf_detector.detect(img, objs, 1.2, cv::Size(96,160), cv::Size(480, 800), 9.0, 10, values);

	// 結果を描画して保存
	for (rect_it = objs.begin(); rect_it != objs.end(); rect_it++)
		cv::rectangle(img, *rect_it, cv::Scalar(255, 0, 0));
	cv::imwrite("result.png", img);

検出はICFDetector::detect()を叩くだけ

	// 検出
	std::vector<cv::Rect> objs;
	std::vector<float> values;
	icf_detector.detect(img, objs, 1.2, cv::Size(96,160), cv::Size(480, 800), 9.0, 10, values);

detect()の第一引数が入力画像、第二引数が検出場所、第三引数はマルチスケール検出の際の画像の拡大率、第四引数は検出する最小サイズ、第五引数は最大サイズ、第六引数は検出閾値、第七引数はSliding Windowを動かすステップ(ピクセル)、第八引数は第二引数に対応する検出スコアです。


で、試した結果の例がこちら。。。。

f:id:takmin:20151218224806p:image:w360

むむむ、なんかうまくいってないですね。。。


試しにNeighbor Suppressionという方法で、検出スコアがピークを取るものの周辺を抑制してやると

f:id:takmin:20151218224807p:image:w360



うーん、難しいですね。

こういう時は検出失敗した画像や誤検出した画像なんかを加えて再学習すると各段に性能が良くなったりします。

が、再学習中にアドベントカレンダーの期限切れになりそうなので、ひとまず現状でアップして、また結果が出たら追記します。




それに、、、、、



githubのリポジトリからはすでにICFDetectorは削除されてますし!




というわけで、それを知ってやる気なくなったというのもあります。。。

次のOpenCVのバージョンではここに書いたことは使えなくなりますのであしからず。


参考文献

[1]Dalal, N., & Triggs, B. (2005). Histograms of Oriented Gradients for Human Detection. 2005 IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR), 1, 886–893.

[2]Dollár, P., Tu, Z., Perona, P., & Belongie, S. (2009). Integral channel features. In British Machine Vision Conference (BMVC).

[3]Dollar, P., Appel, R., Belongie, S., & Perona, P. (2014). Fast feature pyramids for object detection. IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), 36(8), 1532–1545.

[4]Zhang, C., & Viola, P. (2007). Multiple-instance pruning for learning efficient cascade detectors. Advances in Neural Information Processing Systems (NIPS).

[5]Šochman, J., & Matas, J. (2005). WaldBoost - Learning for time constrained sequential detection. Proceedings of the IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR), 2, 150–156.

payashimpayashim 2016/01/20 18:56 慶應大 林です
https://groups.google.com/forum/#!topic/opencv-gsoc-2014/zqkuJQr9hpQ GSocのプロポーザルを見たところ、このICF実装の担当者は提案時点では4年生だと書いてありますね。若いのにチャレンジングで素晴らしいと思いますが、その分、苦戦してクオリティーの高いものを完成するには到らなかった、というのが要因な気がします。

takmintakmin 2016/01/21 00:36 情報ありがとうございます。結局3.1から入っているWBdetectorもICFとは別物みたいですね。
個人的には、WaldBoost採用したのはPatentとられてないからだというのが、なるほどでした。(性能はやっぱりSoftCascadeより悪いみたいですが)