MENU

C++でOpenCVを使って疎密探索によるテンプレートマッチングを実装する

OpenCVにおける基礎ともいえるテンプレートマッチングのプログラムです。初めてOpenCVというものに触れたので、完成するまでにすごい時間がかかりました。

入力画像とテンプレート画像

sample

sample_t

ちょうど左目のあたりの画像をテンプレートとしています。

ソースコード


#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

Mat pyr(Mat, int);   // 画像ピラミッド生成
void sad(Mat, Mat);  // 高速テンプレートマッチング

int main()
{
	Mat src_img = imread("sample.jpg", 0);  // 入力画像
	Mat tmp_img = imread("sample_t.jpg", 0);  // テンプレート画像

	sad(src_img, tmp_img);

	waitKey(0);

	return 0;
}

Mat pyr(Mat src, int n)  //(入力画像,出力画像の大きさ[1/(2^n)])
{
	Mat dst = src;

	for (int i = 0; i < n; i++)	pyrDown(dst, dst);  // pyrDownは元画像の半分の画像を生成する標準関数

	return dst;
}

void sad(Mat src, Mat tmp)
{
	int sad_min = INT_MAX;
	int sad_x = 0;
	int sad_y = 0;

	// 第一段階
	Mat src1_4 = pyr(src, 2);  // 4分の1の入力画像
	Mat tmp1_4 = pyr(tmp, 2);  // 4分の1のテンプレート画像
	for (int y = 0; y < src1_4.rows - tmp1_4.rows; y++){
		for (int x = 0; x < src1_4.cols - tmp1_4.cols; x++){
			int sad = 0;
			for (int j = 0; j < tmp1_4.rows; j++){
				for (int i = 0; i < tmp1_4.cols; i++){
					sad += abs((int)src1_4.at<uchar>(y + j, x + i) - (int)tmp1_4.at<uchar>(j, i));
					if (sad > sad_min) goto SSDA_loop1;
				}
			}
			sad_min = sad;
			sad_x = x;
			sad_y = y;
		SSDA_loop1:;
		}
	}

	// 第二段階
	Mat src1_2 = pyr(src, 1);  // 2分の1の入力画像
	Mat tmp1_2 = pyr(tmp, 1);  // 2分の1のテンプレート画像
	sad_min = INT_MAX;
	int yyy = sad_y * 2;  // 画像が2倍の大きさになるので2倍
	int xxx = sad_x * 2;
	// テンプレート画像サイズの周囲±1ピクセルの範囲を探索
	for (int y = yyy - 1; y <= yyy + 1 && y < src1_2.rows - tmp1_2.rows; y++){
		for (int x = xxx - 1; x <= xxx + 1 && x < src1_2.cols - tmp1_2.cols; x++){
			int sad = 0;
			for (int j = 0; j < tmp1_2.rows; j++){
				for (int i = 0; i < tmp1_2.cols; i++){
					sad += abs((int)src1_2.at<uchar>(y + j, x + i) - (int)tmp1_2.at<uchar>(j, i));
					if (sad > sad_min) goto SSDA_loop2;
				}
			}
			sad_min = sad;
			sad_x = x;
			sad_y = y;
		SSDA_loop2:;
		}
	}

	// 第三段階
	Mat src1_1 = src.clone();  // 元画像と同じ大きさの入力画像
	Mat tmp1_1 = tmp.clone();  // 元画像と同じ大きさのテンプレート画像
	sad_min = INT_MAX;
	int yy = sad_y * 2;  // 画像が2倍の大きさになるので2倍
	int xx = sad_x * 2;
	// テンプレート画像サイズの周囲±1ピクセルの範囲を探索
	for (int y = yy - 1; y <= yy + 1 && y < src1_1.rows - tmp1_1.rows; y++){
		for (int x = xx - 1; x <= xx + 1 && x < src1_1.cols - tmp1_1.cols; x++){
			int sad = 0;
			for (int j = 0; j < tmp1_1.rows; j++){
				for (int i = 0; i < tmp1_1.cols; i++){
					sad += abs((int)src1_1.at<uchar>(y + j, x + i) - (int)tmp1_1.at<uchar>(j, i));
					if (sad > sad_min) goto SSDA_loop3;
				}
			}
			sad_min = sad;
			sad_x = x;
			sad_y = y;
		SSDA_loop3:;
		}
	}

	// テンプレートマッチング位置を四角で囲む
	Mat rect = src.clone();
	rectangle(rect, Point(sad_x, sad_y), Point(sad_x + tmp.cols, sad_y + tmp.rows), Scalar(0, 0, 200), 3, 4);
	namedWindow("Rectangle", CV_WINDOW_AUTOSIZE);  // ウィンドウ表示の準備
	imshow("Rectangle", rect);  // ウィンドウで表示
	imwrite("build.jpg", rect);  // 画像として出力

	cout << "3段階SAD法" << endl;
	// マッチした座標(左上)とそのときのsad値を表示
	cout << "(" << sad_x << ", " << sad_y << ") " << sad_min << endl;
}

こんな感じに長ったらしく書きましたが、最適化すればもっと短くなるはずです。今回は、直観的にわかりやすいように順序はそのままで書きました。第1段階目のアルゴリズムが理解できれば、あとはすんなりプログラムできるでしょう。

実行結果

3段階SAD法
(138, 82) 15043

build

しっかりと検出できていますね!