takminの書きっぱなし備忘録 @はてなブログ

主にコンピュータビジョンなど技術について、たまに自分自身のことや思いついたことなど

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& 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 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_count WaldBoostで選択する特徴の数=弱識別器の数
feature_type "icf"または"acf"を指定
bg_per_image 1枚の背景画像から何枚負例を生成するか
model_n_cols 学習モデルの幅(pixel)
model_n_rows 学習モデルの高さ(pixel)
alpha WaldBoostで使用する目標false negative rate (多分。。)
is_grayscale 入力がグレースケール画像か?
use_fast_log logの計算を簡易的なものを使用するか?

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

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

ここで、第一引数は正例画像のパスを格納したstd::vector型、第二引数は背景画像のパスを格納したstd::vector型です。
実際には負例画像は第二引数指定した背景画像の中からランダムに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 objs;
	std::vector 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 objs;
	std::vector values;
	icf_detector.detect(img, objs, 1.2, cv::Size(96,160), cv::Size(480, 800), 9.0, 10, values);

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


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

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


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



うーん、難しいですね。
こういう時は検出失敗した画像や誤検出した画像なんかを加えて再学習すると各段に性能が良くなったりします。
が、再学習中にアドベントカレンダーの期限切れになりそうなので、ひとまず現状でアップして、また結果が出たら追記します。




それに、、、、、



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.