前回の記事ではサンプル画像を集めるところまで書いた。
ポジティブサンプルをコツコツ処理するのは面倒なので、opencv_createsamplesコマンドを使って大量のサンプル画像を作ってみた。
opencv_createsamplesを使う
opencv_createsamplesコマンドの使い方は簡単だ。引数なしで実行すると以下のような引数の説明が表示される。
$ opencv_createsamples
Usage: opencv_createsamples
[-info <collection_file_name>]
[-img <image_file_name>]
[-vec <vec_file_name>]
[-bg <background_file_name>]
[-num <number_of_samples = 1000>]
[-bgcolor <background_color = 0>]
[-inv] [-randinv] [-bgthresh <background_color_threshold = 80>]
[-maxidev <max_intensity_deviation = 40>]
[-maxxangle <max_x_rotation_angle = 1.100000>]
[-maxyangle <max_y_rotation_angle = 1.100000>]
[-maxzangle <max_z_rotation_angle = 0.500000>]
[-show [<scale = 4.000000>]]
[-w <sample_width = 24>]
[-h <sample_height = 24>]
「-info」と「-img」はどちらか一方を指定するようで、ネガティブサンプルとポジティブサンプルを組み合わせる場合は「-img」を使用する。
サンプルデータは「-vec」引数で指定したファイルに出力されて、この出力されたサンプルダータをもとに学習処理をおこなう。
「-bg」で指定するネガティブサンプルは、画像ファイルのパスをリスト化したものを指定する。
findコマンドを利用することで簡単に画像ファイルのパスをリスト化することができる。
$ find negative_images/ -name '*.jpg' > negative_images.txt
以下のような引数を指定してサンプルデータを作成した。
「-num」が生成するサンプルデータ数になり、「-bgcolor」は背景とみなす色を指定するようだ。今回は255(白)を指定した。また、「-bgthresh」は背景色とみなす範囲?ということで5を指定してみた。
$ opencv_createsamples -vec positive.vec -img positive_images/1.jpg -bg negative_images.txt -num 7000 -bgcolor 255 -bgthresh 5 -w 24 -h 24
無事にポジティブサンプルの生成に成功したはいいが、これをもとに学習させてみてもいい結果を出すことは出来なかった。
たぶんこの方法でポジティブサンプルを用意する場合、文字など検出対象物として認識しやすいようなものではないとだめなのかもしれない。
地道にポジティブサンプルを集める
やっぱり地道に集めるしかないと思いつつ、なるべく楽に集められないか色々調べてみることにした。
あれこれ調べていると以下のサイトを見つけた。どうやらOpenCVでアニメの顔や目を認識させる、ということをやっているようだ。
anime.udp.jp
ここで公開されている便利な加工ツール「SC」を見つけたので使ってみることにした。ちなみにWindowsアプリのみのようだ。
加工元の画像はネガティブサンプルを集める時と同じように、Yahoo画像検索のAPIを利用して集めた。ひたすらカレーライスだけ切り出すのは正直しんどかった。
そして、色々なカレーライスの画像を見ていくうちに、これを認識させるのはかなり難しいのではないかと思い始めた…。
意外にカレーライスの画像は少なくて、どれも同じような画像が多いので結局800枚ほどしか用意することはできなかった。
これも同じようにopencv_createsamplesコマンドを利用してサンプルデータ化する。先ほどと違うのは、「-img」ではなく、「-info」引数を利用することだ。
「-info」には、「-bg」と同じくリスト化したファイルを指定する。この時に注意したいのは、単純に画像ファイルのパスを指定するだけではなく、「その画像に含まれる検出対象物の数」と「座標」、「大きさ」も書き出す必要があるようだ。
ImageMagickに含まれる「identify」コマンドを使うことで画像の大きさが分かるので、findコマンドと組み合わせて書き出してやればいいようだ。
$ find positive_images/ -name '*.png' -exec identify -format '%i 1 0 0 %w %h' \{\} \; > positive_images.txt
$ opencv_createsamples -vec positive.vec -info positive_images.txt -num 800 -bgcolor 255 -bgthresh 5 -w 24 -h 24
サンプルデータから学習させる
opencv_haartrainingコマンドか、opencv_traincascadeコマンドを利用してサンプルデータを学習させる。opencv_traincascadeコマンドの方が新しいようで、マルチコア対応やLBPにも対応しているのでこちらを利用するのがいいようだ。しかし、学習後に生成されるファイルも新しいフォーマットになるため、opencv_preformanceコマンドで利用することができないようなので注意。
opencv_traincascadeコマンドを引数なしで実行した結果は以下のようになる。
Usage: opencv_traincascade
-data <cascade_dir_name>
-vec <vec_file_name>
-bg <background_file_name>
[-numPos <number_of_positive_samples = 2000>]
[-numNeg <number_of_negative_samples = 1000>]
[-numStages <number_of_stages = 20>]
[-precalcValBufSize <precalculated_vals_buffer_size_in_Mb = 256>]
[-precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb = 256>]
[-baseFormatSave]
--cascadeParams--
[-stageType <BOOST(default)>]
[-featureType <{HAAR(default), LBP, HOG}>]
[-w <sampleWidth = 24>]
[-h <sampleHeight = 24>]
--boostParams--
[-bt <{DAB, RAB, LB, GAB(default)}>]
[-minHitRate <min_hit_rate> = 0.995>]
[-maxFalseAlarmRate <max_false_alarm_rate = 0.5>]
[-weightTrimRate <weight_trim_rate = 0.95>]
[-maxDepth <max_depth_of_weak_tree = 1>]
[-maxWeakCount <max_weak_tree_count = 100>]
--haarFeatureParams--
[-mode <BASIC(default) | CORE | ALL
--lbpFeatureParams--
--HOGFeatureParams--
「-data」で指定したパスに分類器が生成される。「-featureType」で学習方法を指定できるが、今回はLBPを利用することにした。
以下のような引数を指定して分類器を作成してみた。
「-numPos」でポジティブサンプルの数を指定するが、バグがあるようできっちり実際の数を指定すると途中で失敗してしまうようだ。実際の数より少なめに指定することで、失敗せずに実行できる。
OpenCV2.4.0でHAAR分類器を学習させるときの問題と対策 (ゆめ技:ゆめみスタッフブログ)
$ opencv_traincascade -data ./data -vec positive.vec -bg negative_images.txt -numPos 750 -numNeg 3000 -numStages 20 -mode ALL -w 24 -h 24 -precalcValBufSize 5000 -precalcIdxBufSize 1000 -featureType LBP
「-data」引数で指定したパスに「cascade.xml」が生成されるので、これが分類器となる。
作った分類器を試してみる
さっそく作った分類器を試してみた。結果からお伝えするとかなり認識率が低い…というかほぼ認識しない。
学習データが少ないのか、やり方が間違っているのか分からない。
OpenCVのサンプルプログラムを利用して作った分類器を試してみた。
引数に認識させたい画像ファイルを指定すると、認識された部分が緑の線で囲われて表示されるものだ。
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
Mat image = imread(argv[1]);
String cascadeName = "cascade.xml";
CascadeClassifier cascade;
cascade.load(cascadeName);
vector<Rect> komas;
cascade.detectMultiScale(image, komas);
for (vector<Rect>::iterator it=komas.begin(); it!=komas.end(); ++it) {
rectangle(image, Rect( it->x, it->y, it->width, it->height),
Scalar(0,255,0));
}
cout << "count: " << komas.size() << endl;
namedWindow("result", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
imshow("result", image);
waitKey(0);
return 0;
}
以下のコマンドでコンパイルして実行した。
$ g++ -I/opt/local/include -L/opt/local/lib -lm -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_objdetect -g -o sample sample.cpp
$ ./sample test.jpg // 実行
結果はというと…。
一応成功したっぽいけど、もう少し認識率を上げていかないと使いものにならなそう。というか、何に使い道があるんだろ…。
参考サイト
Haar状特徴に基づくブースト分類器のカスケードを利用する高速物体検知 – penny
Tutorial: OpenCV haartraining (Rapid Object Detection With A Cascade of Boosted Classifiers Based on Haar-like Features) – Naotoshi Seo