2018년 6월 4일 월요일

Python Opencv matchingTemplate 사용하기

일주일정도 Python환경과 opencv환경의 공부 및 복습을 하면서, 간단하게 동일한 이미지를 찾는 기능을 만들어보자는 도전정신이 생겼었습니다.

원하는 방식은, 윈도우에서 동작하며, 모니터를 캡쳐하여, 찾고자 하는 이미지와 동일한 이미지를 발견할시, 해당 위치에 하이라이트를 하는 매우 간단한 코드입니다.

하지만 아무것도 모르는 상태로 시작하니 일주일이나 걸린건 안자랑..
결과도 공유하고, 복습도 할겸 글로 남깁니다.


Opencv에서는 이렇게 두개의 이미지중 타겟 이미지와 정확하게 일치하는 영역을 찾는 함수가 있는데,
cv2.matchingTemplate() 함수가 바로 제가 할 작업에 필요합니다.

매칭 템플릿은 (비교 이미지, 타겟 이미지, 비교 방식) 인자값들을 필요로 하며,
타겟 이미지와 정확하게 일치하는 영역이 비교 이미지에 있는지 식별하는 기능입니다.
비교 방식은 6가지가 있으며, 자세한 내용은 이곳에서 얻을 수 있습니다.

여러가지 타입이 많으며, 각각 비교방법도 다양해서, 어느것이 어느상황에 유리한지 알아보고 싶었으나, 현제까지 구글링한 바로는 한글로 명확하게 설명된 곳은 없었습니다.. (sigh)

하지만 기본적으로 쉽게 쓰이는것은 TM_CCOEFF_NORMED 라는것을 알았으니, 우선 이것으로 돌려보겠습니다.

현제 프로젝트에는 아래와 같은 디랙토리를 가지고 있습니다.

-matchingTemplateProject
-- main.py
-- target01.jpg

<target01.jpg 이미지 입니다>

그리고 촬영할 영역은
https://docs.opencv.org/2.4/index.html
위의 사이트 입니다. 

main.py

1
2
3
import cv2 #openCv 
import pyautogui #pyAutoGui로 스크린샷을 위해 임포트 했습니다.
import numpy as np #openCv가 의존하는 패키지
cs
필요한 패키지들 입니다.

1
2
3
4
5
6
7
8
9
10
11
_img = np.array(pyautogui.screenshot()) 
#스크린샷을 찍은 이미지를 np.array타입으로 캐스팅합니다.
#이렇게 함으로써, cv2.Mat 타입처럼 이용할 수 있습니다.
img = cv2.cvtColor(_img,cv2.COLOR_RGB2GRAY)
#이미지를 그레이타입으로 변환합니다. 모든 이미지 관련 작업은 그레이타입으로 사용합니다.
target = cv2.imread('../target01.jpg',cv2.IMREAD_GRAYSCALE)
#타겟 이미지는 target01.jpg 로 준비되어 있습니다. 임포트 합니다.
cv2.imshow('target',target)
cv2.waitKey(0)
cv2.destroyAllWindows()
#잘 불러왔는지 확인 해 봅시다.
cs
numpy.array() 는 말 그대로 array입니다. 하지만 python에는 기존의 c나 java의 array와는 달리, 여러 타입들을 지원하는등 범용성이 높으나, 비교적 속도가 낮다고 합니다.
numpy는 이를 해결하기 위해 array()함수를 만들었습니다.
리턴되는 데이터는 일반적인 c나 java의 array타입과 동일하게, 다른 데이터타입을 지원하지 않는, (다차원이 가능한)array입니다.

OpenCV에서 이루어지는 고차원 이미지처리에는 대부분 garyscale를 사용하지 않습니다.
color파일이라고 해서 검색결과가 더 뛰어나지도 않으며, 처리할 데이터의 량은 더 크기 떄문입니다.
그렇기 떄문에 img와 target을 grayscale로 변환하여 불러옵니다.

자, 이제 실행을 해 봅니다


<splender! 이미지가 정상적으로 임포트 되었습니다>

이제 templateMatching을 시작해 봅시다.

1
2
3
4
5
result = cv2.matchTemplate(img,target,cv2.TM_CCOEFF_NORMED)
#매칭 탬플릿함수를 불러옵니다. 이로써 result에는(결과가 있다면) 여러가지 결과들이 튜플로 저장됩니다.
 
minValue,maxValue,minLoc,maxLoc = cv2.minMaxLoc(result)
#minMaxLoc함수는 입력src를 받으며, 결과값으로, min값, max값, min의Point,max의Point 가 리턴됩니다.
cs

cv2.matchingTemplate() 의 결과를 result 변수에 담았습니다.

이제 이 결과를 열어보면,또 다른 여러차원으 array들이 있음을 알수 있습니다.

이 result변수에서는 target이라고 컴퓨터가 판단한 부분들이 위치와 크기등을 배열로 저장되어있습니다.

matchingTemplate 는 단 하나의 결과를 출력하지 않습니다(보통)
공식문서에서 보시는 바와 같이, 매칭되는 이미지를 원본끼리 단순하게 비교를 하지 않습니다.(왜 안그러는거지?)
대신 원본 이미지와 타겟 이미지를 몇가지 메소드에 따라 변형을 통해, 비슷한 패턴을 찾는 방식이기 때문에, 간혹 다른 여러 부분의 이미지도 일치한다고 추측을 합니다.

그럼 이렇게 많은 패턴들이 있는데, 어떤게 내가 찾는 진짜 위치인가? 에 대한 답을 얻기 위해서는, result 배열 값들중 가장 정확도가 높은 값을 찾으면 됩니다.

다행히도, cv2.minMaxLoc() 함수를 이용하여,
최소 정확성 경우의 위치, 최고 정확성 경우의 위치를 손쉽게 얻을 수 있습니다.

리턴되는 값중 우리는 maxLoc가 필요합니다.
이 Point 변수는 최대매칭률을 가진 위치의 시작지점 x,y를 가지고 있습니다.

이제 컴퓨터가 추측을 올바로 했는지 결과를 찍어볼 시간입니다.
우리의 다음 목표는 이러합니다.
<maxLoc의 위치에 사각형을 그려넣어, 위치를 보기 쉽게 그릴것 입니다>

 이를 위해서는 우리는 _win 변수를 출력하는데, maxLoc의 위치에서 target 사이즈 크기만한 사각형을 그리면 됩니다.
사각형을 그리는 함수는 cv2.rectangle() 입니다.
인자값으로는 (이미지,시작지점,끝지점,색깔,굵기) 를 받습니다.

필요한 인자값을 찾았으니 구해야겠습니다.

우선은 우리가 가지고 있는 변수들을 다시한번 리뷰 해봅시다.
_win 은 Mat타입으로 원본 스크린샷을 가지고 있고,
target 은 타겟 이미지를 가지고 있습니다.
그리고 maxLoc는 앞서 설명했듯이, 가장 정확하다고 추측한 위치의 좌표입니다.



<target입니다. 우리가 필요한건 targetWidth와 targetHeight입니다 >


<원본 이미지입니다. minLoc는 알고 있으나 ? 의 위치를 모르는군요.>

정리를 좀 하니 진짜 필요한게 뭔지 보이는군요. 바로 위의 사진의 ? 좌표로군요.
?의 좌표를 구하기 위해서는, maxloc의 x값과 y값에, target이미지 width와 height를 더하면 됩니다! 간단하네요!

그럼 이제 코드로 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
leftTop = maxLoc
#TM_CCOEFF_NORMED는 MAX값이 올바른 값 이므로,
rightBottom = maxLoc[0]+targetHeight , maxLoc[1+ targetWidth
# 결과값의 좌측 상단 Point와 우측 하단 Point값들을 결과들로 부터 얻어냅니다.
 
cv2.rectangle(_img,leftTop,rightBottom,(255,255,0),3)
#leftTop 부터 rightBottom까지 사각형을 _img 에 그리는데,
# 색은 (255,255,0)으로, 굵기는 3으로 입니다.
 
cv2.imshow('result',_img)
#결과를 출력하고
cv2.waitKey(0)
cv2.destroyAllWindows()
#올바르게 출력되고 종료하기 위해 반드시 두줄도 작성합니다.
cs

 끝났네요!
12 13 라인은 cv의 이미지를 출력할때 반드시 필요합니다.
waitKey가 없으면, 이미지 프레임은 로드되나, 내용물이 보이지 않고,
destroyAllWindows는 프로세스 강제 종료등의 상황에서 가비지 메모리가 남을 수 있습니다.

이제 돌려보면

<제 기나긴 일주일의 삽질 결과가 마침내 나왔습니다 >

 이렇게 이쁜 결과가 나오게 됩니다!