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

の続き

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

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


 

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

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

なので、

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

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

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

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

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