OpenCV のテンプレートマッチを使って駒を検出

プチロボ事始め | 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

の続き

パズルゲームを解くのが目的なので、テンプレートマッチングは非常にオーバーヘッドが大きいのですが、試しに。

#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種類)に対してテンプレートマッチングをすると、ええ、大変遅いですね(苦笑)。

#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 );
}


 

そりゃ、全画面に対して毎回テンプレートを検索するのは無駄だし、人がパズルを解いているときはそういうことはしないので、もうちょっと方法を考えないと駄目ですね。

パズルの盤面は普通は格子状になっているので、テンプレートマッチングのようにちょっとずつ動かす必要はありません。盤面上にひとつの駒が検出できれば、その上下左右を見ていくという方法です。
あと、駒の形状を細かくマッチングしていく必要はなくて、他の駒との差異が分かればよいわけで、それぞれの駒の特徴を取り出して、実際の盤面の駒のスコアを比較すればよいわけです。実際、人の目はそうやっているし。

なので、

  1. あらかじめ、駒同士を比較して特徴量を検出する
  2. 盤面から、駒があるであろう場所を検出
  3. 盤面上の駒を、学習済みの特徴量でスコアを計算
  4. スコアが一番高いものを駒とみなす

というロジックになります。

テンプレートマッチングの参照先はこちら↓

画像処理 – OpenCV-CookBook
http://opencv.jp/cookbook/opencv_img.html#id32

カテゴリー: C++, プチロボ パーマリンク