プチロボ事始め | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2416
プチロボで4軸構成にしてみる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2421
OpenCV を使ってエッジ抽出 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2452
OpenCV を使って顔認識 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2460
の続き
パズルゲームを解くのが目的なので、テンプレートマッチングは非常にオーバーヘッドが大きいのですが、試しに。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> int main( int argc, char *argv[]) { CvCapture *capture = cvCreateCameraCapture(0); // テンプレート画像 cv::Mat tmp_img = cv::imread(argv[2], 1); if (!tmp_img.data) return -1; cv::namedWindow( "search image" , CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO); while ( 1 ) { // 探索画像 cv::Mat search_img0 = cvQueryFrame( capture ); cv::Mat search_img; search_img0.copyTo( search_img ); cv::Mat result_img; // 50 個検出する for ( int i=0; i<50; i++ ) { // テンプレートマッチング cv::matchTemplate(search_img, tmp_img, result_img, CV_TM_CCOEFF_NORMED); // 最大のスコアの場所を探す cv::Rect roi_rect(0, 0, tmp_img.cols, tmp_img.rows); cv::Point max_pt; double maxVal; cv::minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt); // 一定スコア以下の場合は処理終了 if ( maxVal < 0.5 ) break ; roi_rect.x = max_pt.x; roi_rect.y = max_pt.y; std::cout << "(" << max_pt.x << ", " << max_pt.y << "), score=" << maxVal << std::endl; // 探索結果の場所に矩形を描画 cv::rectangle(search_img0, roi_rect, cv::Scalar(0,255,255), 3); cv::rectangle(search_img, roi_rect, cv::Scalar(0,0,255), CV_FILLED); } cv::imshow( "search image" , search_img0); char ch = cv::waitKey(33); if ( ch == 27 ) break ; } cvReleaseCapture( &capture ); } |
ちょうど、駒と同じサイズ(30×30)のテンプレートを用意して、画面上を探索します。
テンプレートマッチングをしたときは、cv::minMaxLoc で最大のスコアを取得するのですが、最大だと1つしか取れないので、50 回ぐらい繰り返します。閾値の「0.5」は適当に決めたものです。
ひとつの駒だけ検出するならば、そこそこのスピードで動くのですが、これを全ての駒(今回は7種類)に対してテンプレートマッチングをすると、ええ、大変遅いですね(苦笑)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> int main( int argc, char *argv[]) { CvCapture *capture = cvCreateCameraCapture(0); // テンプレート画像 cv::Mat tmp_imgs[7]; tmp_imgs[0] = cv::imread( "mini\\koma01.png" , 1); tmp_imgs[1] = cv::imread( "mini\\koma02.png" , 1); tmp_imgs[2] = cv::imread( "mini\\koma03.png" , 1); tmp_imgs[3] = cv::imread( "mini\\koma04.png" , 1); tmp_imgs[4] = cv::imread( "mini\\koma05.png" , 1); tmp_imgs[5] = cv::imread( "mini\\koma06.png" , 1); tmp_imgs[6] = cv::imread( "mini\\koma07.png" , 1); // 枠線の色 cv::Scalar cols[6]; cols[0] = cv::Scalar(0,0,255); cols[1] = cv::Scalar(0,255,0); cols[2] = cv::Scalar(255,0,0); cols[3] = cv::Scalar(255,0,255); cols[4] = cv::Scalar(255,255,0); cols[5] = cv::Scalar(255,255,255); cols[6] = cv::Scalar(100,100,100); cv::namedWindow( "search image" , CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO); while ( 1 ) { // 探索画像 cv::Mat search_img0 = cvQueryFrame( capture ); cv::Mat search_img; search_img0.copyTo( search_img ); for ( int j=0; j<7; j++ ) { cv::Mat &tmp_img = tmp_imgs[j]; cv::Mat result_img; // 50 個検出する for ( int i=0; i<50; i++ ) { // テンプレートマッチング // cv::matchTemplate(search_img, tmp_img, result_img, CV_TM_SQDIFF_NORMED); cv::matchTemplate(search_img, tmp_img, result_img, CV_TM_CCOEFF_NORMED); // 最大のスコアの場所を探す cv::Rect roi_rect(0, 0, tmp_img.cols, tmp_img.rows); cv::Point max_pt; double maxVal; cv::minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt); // 一定スコア以下の場合は処理終了 if ( maxVal < 0.5 ) break ; roi_rect.x = max_pt.x; roi_rect.y = max_pt.y; std::cout << "(" << max_pt.x << ", " << max_pt.y << "), score=" << maxVal << std::endl; // 探索結果の場所に矩形を描画 cv::rectangle(search_img0, roi_rect, cols[j], 3); cv::rectangle(search_img, roi_rect, cv::Scalar(0,0,255), CV_FILLED); } } cv::imshow( "search image" , search_img0); char ch = cv::waitKey(33); if ( ch == 27 ) break ; } cvReleaseCapture( &capture ); } |
そりゃ、全画面に対して毎回テンプレートを検索するのは無駄だし、人がパズルを解いているときはそういうことはしないので、もうちょっと方法を考えないと駄目ですね。
パズルの盤面は普通は格子状になっているので、テンプレートマッチングのようにちょっとずつ動かす必要はありません。盤面上にひとつの駒が検出できれば、その上下左右を見ていくという方法です。
あと、駒の形状を細かくマッチングしていく必要はなくて、他の駒との差異が分かればよいわけで、それぞれの駒の特徴を取り出して、実際の盤面の駒のスコアを比較すればよいわけです。実際、人の目はそうやっているし。
なので、
- あらかじめ、駒同士を比較して特徴量を検出する
- 盤面から、駒があるであろう場所を検出
- 盤面上の駒を、学習済みの特徴量でスコアを計算
- スコアが一番高いものを駒とみなす
というロジックになります。
テンプレートマッチングの参照先はこちら↓
画像処理 – OpenCV-CookBook
http://opencv.jp/cookbook/opencv_img.html#id32