파이썬 게임 화면 인식 - paisseon geim hwamyeon insig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import numpy as np
from PIL import ImageGrab
import cv2

 # 무한루프를 돌면서 
while(True):
    # (0,40)부터 (800,600)좌표까지 창을 만들어서 데이터를 저장하고 screen 변수에 저장합니다
    screen = np.array(ImageGrab.grab(bbox=(0,40,800,600)))

     # pygta5-1이라는 이름의 창을 생성하고 이 창에 screen 이미지를 뿌려줍니다
    cv2.imshow('pygta5-1', cv2.cvtColor(screen, cv2.COLOR_BGR2RGB))

     # 'q'키를 누르면 종료합니다
    if cv2.waitKey(25& 0xFF == ord('q'):
        cv2.destroyAllWindows()
        break

  Colored by Color Scripter

cs

매직타일 매크로

 openCV로 뭘 만들까 고민하다가 옛날에 했던 게임 중 하나인 '매직 타일' 생각이 나서 매크로를 만들어 봤다.

매직 타일은 간단한 리듬 게임이다.

매직 타일 소개 영상
파이썬 게임 화면 인식 - paisseon geim hwamyeon insig

보다시피, 검은색 타일을 화면 내에 있을 때 클릭하면 되는 게임이다. 그래서 구현 자체는 간단할 거라 생각했다.

타일 인식 방법으로 처음엔 템플릿 매칭 방법을 사용했다.

 템플릿 매칭

 템플릿 매칭은 간단하게 원본 이미지템플릿 이미지와 일치하는 영역을 찾는 알고리즘이다.

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("프로그램 종료")

 결과 영상

결과 영상
파이썬 게임 화면 인식 - paisseon geim hwamyeon insig

 개선 방안

 영상을 보면 알 수 있듯이, 개선이 많이 필요하다. 무엇보다 배열 데이터를 계속해서 다루다 보니 파이썬으론 딜레이가 많은 것 같다. 그래서 C++로 옮겨서 테스트를 해볼 예정이다.