매직타일 매크로
openCV로 뭘 만들까 고민하다가 옛날에 했던 게임 중 하나인 '매직 타일' 생각이 나서 매크로를 만들어 봤다.
매직 타일은 간단한 리듬 게임이다.
보다시피, 검은색 타일을 화면 내에 있을 때 클릭하면 되는 게임이다. 그래서 구현 자체는 간단할 거라 생각했다.
타일 인식 방법으로 처음엔 템플릿 매칭 방법을 사용했다.
템플릿 매칭
템플릿 매칭은 간단하게 원본 이미지에 템플릿 이미지와 일치하는 영역을 찾는 알고리즘이다.
import cv2 as cv
# 템플릿 과정은 8비트 단일 체널!
# grayscale !
src = cv.imread("test.png", cv.IMREAD_GRAYSCALE)
templit = cv.imread("templit.png", cv.IMREAD_GRAYSCALE)
dst = cv.imread("test.png")
result = cv.matchTemplate(src, templit, cv.TM_SQDIFF_NORMED)
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(result)
x, y = minLoc
h, w = templit.shape
dst = cv.rectangle(dst, (x, y), (x + w, y + h) , (0, 255, 255), 1)
cv.imshow("dst", dst)
cv.waitKey(0)
cv.destroyAllWindows()
여기까지 보면 내가 하고자하는 방향과 맞다. 하지만 템플릿 매칭의 방식은 원본 이미지에서 템플릿 이미지를 처음부터 끝까지 이동하며 비교하는 방법이었다! 그렇기 때문에 템플릿 매칭 방식으로 구현했을 때, 딜레이가 너무 심해서 오차가 컸다.
안그래도 파이썬인데, 배열을 처음부터 끝까지 조사하다 보니... 그것도 이미지가 아닌 영상을... 그래서 다음으로 생각한 방법은 컨투어(contour) 방법이다.
컨투어
컨투어(contour)는 단어 의미에서 유추할 수 있듯이, openCV에서 영상에서 외곽과 내곽을 검출하는 함수이다.
import cv2 as cv
img_color = cv.imread('test.png')
img_gray = cv.cvtColor(img_color, cv.COLOR_BGR2GRAY)
ret, img_binary = cv.threshold(img_gray, 50, 255, 0)
# 컨투어
contours, hierarchy = cv.findContours(img_binary, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
# 외곽선 그리기
for contour in contours:
cv.drawContours(img_color, contour, 0, (0, 255, 0), 3)
cv.imshow("result", img_color)
cv.waitKey(0)
이 방법을 쓰니 확실히 오차가 줄었다!
최종 코드
from PIL import ImageGrab
import pyautogui
import cv2 as cv
import numpy as np
import keyboard
import time
class Auto():
def __init__(self):
self.img_src = np.zeros((550, 980, 3), np.uint8) # 컬러 초기값
self.img_gray = np.zeros((550, 980, 1), np.uint8) # 흑백 초기값
self.img_binary = np.zeros((550, 980, 1), np.uint8) # 흑백 초기값
self.cap_coords = (1, 70, 550, 950)
self.thresh = 10
self.maxValue = 255
self.img_button = cv.imread('button.png', cv.IMREAD_GRAYSCALE)
def cap(self):
"""
게임 캡쳐
"""
print("게임 캡쳐 시작.")
while True:
self.img_src = cv.cvtColor(np.array(ImageGrab.grab(
bbox=self.cap_coords)), cv.COLOR_RGB2BGR)
self.make_binary()
self.find_contours()
# self.img_sreach()
cv.imshow("Game", self.img_src)
key = cv.waitKey(100)
if key == ord('q'):
print("캡쳐 중단.")
cv.destroyAllWindows()
return
def make_binary(self):
"""
이진화 작업
"""
self.img_gray = cv.cvtColor(self.img_src, cv.COLOR_BGR2GRAY)
ret, self.img_binary = cv.threshold(
self.img_gray, self.thresh, self.maxValue, cv.THRESH_BINARY_INV)
def find_contours(self):
"""
컨투어 과정
"""
contours, hierarchy = cv.findContours(
self.img_binary, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE)
for i in range(len(contours)):
epsilon = 0.01 * cv.arcLength(contours[i], True)
approx = cv.approxPolyDP(contours[i], epsilon, True)
size = len(approx)
if size == 4:
cv.drawContours(self.img_src, [contours[i]], 0, (0, 0, 255), 2)
mu = cv.moments(contours[i])
cx = int(mu['m10']/mu['m00'])
cy = int(mu['m01']/mu['m00'])
cv.circle(self.img_src, (cx, cy), 15, (0, 255, 255), -1)
self.mouse_click(cx, cy)
def mouse_click(self, x, y):
"""
노드 위치를 클릭한다.
"""
print("노드 좌표 : {},{}".format(x, y))
if y > 85:
pyautogui.click(x, y)
def img_sreach(self):
"""
템플릿 매칭
"""
w, h = self.img_button.shape[:2]
res = cv.matchTemplate(
self.img_binary, self.img_button, cv.TM_CCOEFF_NORMED)
threshold = 0.5
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
cv.rectangle(self.img_src, pt,
(pt[0] + h, pt[1] + w), (0, 0, 255), 2)
(x, y) = (int((pt[0]+h)/2), int((pt[1]+w)/2))
self.mouse_click(x, y)
print("프로그램 시작")
cap = Auto()
keyboard.add_hotkey("ctrl+alt", lambda: cap.cap())
keyboard.wait('esc')
print("프로그램 종료")
결과 영상
개선 방안
영상을 보면 알 수 있듯이, 개선이 많이 필요하다. 무엇보다 배열 데이터를 계속해서 다루다 보니 파이썬으론 딜레이가 많은 것 같다. 그래서 C++로 옮겨서 테스트를 해볼 예정이다.
'openCV' 카테고리의 다른 글
[C++ openCV 도형 인식] 게임 매직 타일 매크로 [2] (완) (0) | 2021.01.28 |
---|---|
[Python openCV] 유로 트럭2 반자율주행 (1) | 2021.01.25 |
[파이썬 openCV] - 트랙바로 임계값 조정하기 (0) | 2021.01.21 |
[파이썬 openCV] - 픽셀, 컬러와 흑백 이미지 (0) | 2021.01.20 |
[파이썬 openCV] - 이미지 데이터 공유 & 복사 (0) | 2021.01.19 |