템플릿 매칭이란?
템플릿 매칭(Template Matching)은 실제 이미지 안에서 특정 패치(patch) 또는 템플릿(template)을 찾아내어 일치 여부를 확인하는 기법입니다. 쉽게 말해 이미지 속에서 원하는 패턴을 검색하는 패턴 매칭 방식이라고 할 수 있습니다.
Python에서는 OpenCV 모듈을 활용하면 이러한 매칭 작업을 손쉽게 구현할 수 있습니다. 이 글에서는 OpenCV의 템플릿 매칭 기법을 이용해 이미지 내에서 원하는 객체를 찾는 방법을 단계별로 살펴보겠습니다.
OpenCV 설치하기
OpenCV 기능을 사용하려면 먼저 pip를 통해 모듈을 설치해야 합니다.
sudo pip3 install opencv-python
임계값(Threshold)의 중요성
템플릿 매칭에는 정확도를 결정하는 요소가 있으며, 이를 임계값(threshold)이라고 합니다.
예를 들어, 얼굴 인식 시스템을 만든다고 가정해 봅시다. 눈이나 얼굴의 다른 부위 이미지를 템플릿으로 제공하면 해당 부위를 쉽게 찾아낼 수 있습니다. 하지만 눈의 형태는 사람마다 조금씩 다르기 때문에, 정확도 기준을 100%로 설정하는 것보다 50% 수준으로 설정했을 때 오히려 더 잘 탐지할 수 있습니다. 일반적으로는 상황에 따라 약 80% 수준의 임계값이 많이 사용됩니다.
템플릿 매칭 진행 단계
원본 이미지를 불러온 뒤 그레이스케일(grayscale) 이미지로 변환합니다.
템플릿 역시 그레이스케일 이미지로 준비합니다.
템플릿을 원본 이미지 위에서 슬라이딩하며 정확도가 일치하는 위치를 찾습니다.
매칭 결과가 설정한 임계값보다 크면 해당 위치를 탐지된 영역으로 표시합니다.
기본 예제: 단일 스케일 템플릿 매칭
먼저 입력 이미지와 템플릿은 다음과 같습니다.
원본 이미지

템플릿

예제 코드
import cv2
import numpy as np
#원본 이미지를 열고 그레이스케일로 변환
main_image = cv2.imread('main_image.png')
gray_image = cv2.cvtColor(main_image, cv2.COLOR_BGR2GRAY)
#템플릿을 그레이스케일 이미지로 열기
template = cv2.imread('template1.png', 0)
width, height = template.shape[::-1] #너비와 높이 가져오기
#cv2.matchTemplate으로 템플릿 매칭 수행
match = cv2.matchTemplate(gray_image, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
position = np.where(match >= threshold) #이미지 내 템플릿 위치 찾기
for point in zip(*position[::-1]): #매칭된 영역에 사각형 그리기
cv2.rectangle(main_image, point, (point[0] + width, point[1] + height), (0, 204, 153), 0)
cv2.imshow('Template Found', main_image)
cv2.waitKey(0)실행 결과

한계점: 멀티 스케일 미지원
위 코드는 멀티 스케일(multi-scaling)을 지원하지 않는다는 한계가 있습니다. 즉, 템플릿의 크기가 원본 이미지 내 객체와 다르면 탐지에 실패합니다.
이 문제를 해결하기 위해 다음 예제에서는 멀티 스케일 기법을 적용합니다. 이 방식에서는 원본 이미지를 다양한 크기로 조정(resize)하면서 반복적으로 패턴 매칭을 수행하고, 그중 가장 큰 상관 계수(correlation coefficient)를 갖는 지점을 최종 매칭 결과로 선택합니다.
원본 이미지는 동일하며, 사용된 템플릿은 다음과 같습니다.

예제 코드
import imutils
import cv2
import numpy as np
#템플릿을 열고 Canny 엣지 검출 적용
template = cv2.imread('template3.jpg')
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
template = cv2.Canny(template, 10, 25)
(height, width) = template.shape[:2]
#원본 이미지를 열고 그레이스케일로 변환
main_image = cv2.imread('main_image.png')
gray_image = cv2.cvtColor(main_image, cv2.COLOR_BGR2GRAY)
temp_found = None
for scale in np.linspace(0.2, 1.0, 20)[::-1]:
#이미지 크기를 조절하고 비율 저장
resized_img = imutils.resize(gray_image, width = int(gray_image.shape[1] * scale))
ratio = gray_image.shape[1] / float(resized_img.shape[1])
if resized_img.shape[0] < height or resized_img.shape[1] < width:
break
#엣지 이미지로 변환하여 검사
e = cv2.Canny(resized_img, 10, 25)
match = cv2.matchTemplate(e, template, cv2.TM_CCOEFF)
(_, val_max, _, loc_max) = cv2.minMaxLoc(match)
if temp_found is None or val_max>temp_found[0]:
temp_found = (val_max, loc_max, ratio)
#temp_found 정보로 x, y 좌표 계산
(_, loc_max, r) = temp_found
(x_start, y_start) = (int(loc_max[0]), int(loc_max[1]))
(x_end, y_end) = (int((loc_max[0] + width)), int((loc_max[1] + height)))
#템플릿 주변에 사각형 그리기
cv2.rectangle(main_image, (x_start, y_start), (x_end, y_end), (153, 22, 0), 5)
cv2.imshow('Template Found', main_image)
cv2.waitKey(0)실행 결과
