본문 바로가기
코딩/OpenCV

[c++ opencv] 이미지서치 기본사용법, Template Matching 사용하기 #1 기본사용

by DIYver 2020. 10. 25.

 

본문 목표

 

OpenCV를 다루다보면 이미지서치를 사용할 때가 온다.

자율주행 시스템에서도 표지판인식, 번호판 인식에 사용하기도 하고,

컴퓨터 화면에서도 이용하기도 한다.

 

또한 생산 공장등에서도 사용하기도 한다.

그만큼 중요한 내용인 만큼 어떻게 사용하는지도 알아보도록 하자.

 

 

 

키워드 : 이미지서치, Template Matching, 템플릿매칭

 

 

 

 

 

알아볼 함수 원형

- 테플릿 매칭 ( matchTemplate )

	matchTemplate(src, templ, result, 1);
	normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat() );
	minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );

matchTemplate( img, templ, result, match_method, mask)

 

  ○ img : 이미지 원본

 

  ○ templ : 찾고자 하는 이미지

 

  ○ result : 연산 결과를 저장하기 위한 이미지

 

  ○ match_method : 이미지서치 방법 

- 0: TM_SQDIFF 
- 1: TM_SQDIFF NORMED 
- 2: TM CCORR 
- 3: TM CCORR NORMED 
- 4: TM COEFF 
- 5: TM COEFF NORMED

 

  ○ mask : templ 이미지에 해당하는 마스크, 필요로 하는 이미지서치에서만 사용

- 0: TM_SQDIFF  , 3: TM CCORR NORMED  두 가지 방법만 mask 이미지 사용 가능

 

 

 

 

 

 

 

 

코드 테스트 결과

- CODE

#include <opencv2/opencv.hpp>
#include <time.h>


using namespace cv;
using namespace std;

int main(int, char**)
{
	clock_t start, end;

	double minVal, maxVal;
	Point minLoc, maxLoc;
	Point matchLoc;

	Mat src = imread("test_img2.png");
	if (src.empty())
	{
		cout << "no src Image!"<< endl;
		return -1;
	}

	Mat templ = imread("templ.png");
	if (src.empty())
	{
		cout << "no templ Image!" << endl;
		return -1;
	}

	Mat result;

	for (int i = 0; i < 6; i++)
	{
		Mat img_out;
		src.copyTo(img_out);

		int Matching_method = i;
		/*
		0: TM_SQDIFF
		1: TM_SQDIFF NORMED
		2: TM CCORR
		3: TM CCORR NORMED
		4: TM COEFF
		5: TM COEFF NORMED";
		*/

		start = clock();


		matchTemplate(src, templ, result, i);
		normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
		minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
		if (Matching_method == 0 || Matching_method == 1)
		{
			matchLoc = minLoc;
		}
		else
			matchLoc = maxLoc;


		end = clock();

		cout << "Searching time : " << difftime(end, start) / CLOCKS_PER_SEC << endl;

		cout << "Min value : " << minVal << endl;

		cout << "Max value : " << maxVal << endl;

		rectangle(img_out, matchLoc, Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), Scalar(0, 0, 255), 1);

		cvtColor(result, result, COLOR_GRAY2BGR);
		circle(result, matchLoc, 3, Scalar(0, 0, 255), 1);

		imshow("src", img_out);
		imshow("templ", templ);
		imshow("result", result);

		waitKey(0);

	}
	return 0;
}

 

- RESULT

 

 

이미지서치에 사용한 원본 이미지

 

여기서 불상을 찾기위해 불상만 잘라내서 templ 이라는 이미지 이름으로 저장한다.

 

그리고 코드를 실행시키면

불상을 찾았고, 정확히 그 위치에 빨간 사각형이 쳐진것을 확인할 수 있다.

 

 

 

연산 결과를 저장하는 이미지를 확인해보자.

이미지가 커서 잘 안 보일 수 있지만,

연산 결과 중, 제일 검은 부분을 보면 빨간색 원으로 표시가 되어있다.

 

저 부분이 이미지 연산을 통해서 src 에서 templ 을 찾은 위치라는 것이다.

 

 

코드 해석을 하기전에 원리를 약간 이해해보도록 하자.

 

 

 

 

이 포스트 말고 전문적인 해석을 보고 싶다면

아래 링크에 들어가서 확인하면 된다.

docs.opencv.org/3.4/de/da9/tutorial_template_matching.html

opencv 공식 블로그다.

 

 

이해하기 쉽게 원리를 설명하자면

 

일단 src (원본 이미지) 에서 templ 이미지를 sliding 하면서 연산을 하게 된다.

이미지 서칭 방법이 총 6가지가 있는데,

계산식은 다음과 같다.

이런 계산식을 통해서 얻은 연산 결과 이미지는 아래와 같다.

 

이 연산에서는 가장 하얀 부분이 일치하는 점인 경우이다.

 

 

어쨋든 원리는 단순하다.

쉽게 생각하면 필터를 적용해서 가장 밝거나 어두운 부분을 찾고

그 영역이 일치한다고 판단하는 원리이다.

 

 

당연하게도 원본이미지에서 잘라내어서 templ 이미지로 사용했다면

결과는 정말 오차도 없이 좋게 나온다.

 

하지만 그런 경우가 아닐경우 minVal 와 maxVal 값을 통해서 일치 정확도를 알 수 있다.

이 정확도를 필터링 할 수도 있다는 뜻이다.

 

 

자세한건 다음에 더 다루도록하고,

이제 코드 해석을 해보자.

 

 

이미지 연산 결과를 저장시킬 변수들을 우선적으로 선언해주었다.

 

 

 

원본이미지와 template 이미지를 불러오는 코드이다.

이미지가 제대로 안 읽히면 오류코드를 띄우게 해주었다.

 

연산 결과를 저장할 result 이미지를 만들어주고,

연산 방법을 선택하는 부분이다.

반복문을 이용하여 6가지 경우를 모두 테스트한다.

 

 

 

 

오늘의 주인공 템플릿매칭 함수를 사용하는 부분이다.

템플릿매칭을 사용해서 result에 결과이미지를 저장시키고

 

normalize 함수를 이용하여 이미지를 정규화 시켜준다. 

필터의 한 종류라고 이해하면 쉽다. 이미지를 0에서 1까지 고르게 분포시켰다고 생각하면 편하겠다.

 

minMaxLoc 함수를 이용해서 result 이미지를 분석하여 결과들을 각각 저장하였다.

그냥 이렇게 쓰는거라서 복붙해서 사용하면 된다.

 

원본 이미지에 사각형을 그리는데,

연산결과에서 얻은 minLoc 좌표에 templ 이미지 만큼 크기를 더해서 사각형을 그린다.

 

그리고 결과 이미지를 grayscale 에서 BGR 컬러로 바꿔준다.

왜냐하면 빨간색 원을 표현하려면 BGR 색채널이어야 하기 때문이다.

그리고 빨간색 원을 minLoc 좌표에 표시한다.

 

 

 

 

 

.

동일한 날 살짝 다르게 찍힌 이미지를 또 준비해봤다.

좌 : 위에서 썼던 원본이미지

우 : 새롭게 템플릿 매칭을 적용시켜볼 동일한 날 찍은 이미지

 

이미지 구도가 살짝 다르고, 밝기 정보가 다르다.

 

 

 

0: TM_SQDIFF

 

 


1: TM_SQDIFF NORMED

 

 


2: TM CCORR

 

 


3: TM CCORR NORMED

 


4: TM COEFF

 


5: TM COEFF NORMED

 

이미지를 바꿨을 뿐인데 2번의 방법으로 한 경우

잘못된 위치를 동일하다고 판단했다.

 

 

 

이건 템플릿 매칭의 큰 단점 중 하나로

이미지가 살짝이라도 다르면 잘 잡아내지를 못한다는 것이다.

 

연산을 이용하므로, 밝기 정보도 중요하고, 구도와 기울기도 중요하다.

 

그러니깐 회전되어있는 이미지에서는 검출을 하지 못한다는 것이다...

또한 크기도 중요하다. src 이미지나 templ 이미지가 확대된 이미지라면 검출이 안 된다.

 

실제로 표지판 인식 시스템을 템플릿매칭으로 해본 사람들은 알 것이다.

햇빛 아래에서 할 때, 그림자가 조금이라도 있거나 하면 잘 인식 되던것도 인식 안 되고 한다.

이런 경우 템플릿 매칭을 하기 전에 이미지를 한번 더 처리해주어야하는데 여간 귀찮은게 아니다.

 

그리고 한가지 더 확인할 수 있는것은

0번과 1번은 어두운 점을 일치점으로 인식하는데

나머지는 밝은 점을 일치점으로 인식한다는 것이다.

 

이 차이를 잘 이해하는 것이 중요하다.

 

 

 

각 방법 중 어느 방법을 선택해야 할지 고민도 많이 될 것이다.

한가지 확실한 것은 모든 방법을 다 써보고 가장 좋은 결과를 내는 방법을 선택하는 것이 옳다.

 

보통은 3번 방법( TM CCORR NORMED) 을 많이 쓴다.

 

 

 

 

 

다음부턴는 마스크를 이용해서 template matching 하는것을 다뤄보고

원본 이미지에서 templ 이미지를 여러개 검출해내는 것을 다뤄보도록 하겠다.

더 나아가서는 실제로 카메라와 게임에서 사용하는 것을 다뤄보는 것을 목표로 삼아본다.

 

 

 

 

 

 

 

 

 

 

도움이 되었거나, 문제가 있는 경우 댓글로 알려주세요~!

감사의 댓글은 작성자에게 큰 힘이 됩니다 ^^

댓글