Python이 Brain Wars를 플레이 해 보았습니다 1

개요



이미지 처리 연습이 끝나면 Python이 Brain Wars를 플레이하게했습니다.

Brain Wars란 무엇입니까?



심플한 내용의 미니게임을 통해 전세계 유저와 실시간으로 대전하고 스코어를 겨루는 '실시간 대전형 뇌 트레이닝'. 실제로 플레이한 적이 있는 분도 많지 않을까?


이번에 플레이하는 게임



앱 내에는 21 종류의 게임이 준비되어 있지만, 이번에는 그 중에서 Matching를 선택했다. 표시된 카드 중에서 일치하는 쌍을 찾아 탭하는 간단한 게임


프로그램 동작



이런 식으로 작동합니다. 그건 그렇고, 내 최고의 점수는 23이므로 적어도 나보다 훨씬 강합니다.


흐름은 자연스럽게 이런 느낌 ↓
1. 카드가 표시된 영역을 캡처
2. 캡처한 이미지에서 각 카드의 이미지와 좌표를 가져옵니다.
3. 이미지 유사도 (SSIM)를 계산하여 일치 쌍을 식별합니다.
4. 일치 쌍을 클릭
5. 1~4를 게임이 끝날 때까지 반복한다

해설



어떻게 프로그램을 구현했는지 순서대로 해설해 나간다

환경


  • OS: Ubuntu 18.04.1 LTS
  • Python version: 3.6.5
  • 스마트 폰: Huawei P10 Lite

  • Brain Wars는 스마트폰에서만 플레이할 수 있다. 그래서 Vysor이라는 Chrome 확장 플러그인을 사용하여 Ubuntu에서 스마트 폰을 조작하여 플레이했습니다.

    사용한 주요 라이브러리 및 역할


  • mss : 스크린 캡처
  • opencv : 카드 윤곽 추출 및 좌표 계산
  • scikit-image : 이미지 유사도 (SSIM) 계산
  • pyautogui : 클릭 자동화

  • 1. 카드 표시 영역 캡처



    처음에는 PILImageGrab 를 사용하려고 생각했지만, 아무래도 Mac과 Windows만 지원합니다. 라는 것이므로, mss 라고 하는 라이브러리를 사용했다. 게임 레벨이 올라가면 카드의 행 수가 3열로 늘어나므로 캡처 영역이 약간 넓게 설정되어 있습니다.



    2. 각 카드의 이미지와 좌표 취득


  • 이미지를 Gray-Scale로 변환
  • 바이너리 화
  • 바운딩 박스 감지
  • Bounding Box의 좌표로부터 각 카드의 이미지와 중심 좌표를 취득

  • 3. 매치 페어 식별



    이미지 유사도 (SSIM)를 계산하려면 scikit-imagecompare_ssim을 사용했습니다. 각 카드의 조합(카드 매수가 6장의 경우라면, 15패턴)마다 SSIM을 산출하고, 스코어가 최대가 되는 것을 매치 페어로 했다

    4. 일치 쌍 클릭


    2에서 구한 각 카드의 중심 좌표와 3에서 구한 매치 페어의 Index로부터 클릭 좌표를 결정했다. 클릭 자동화에는 pyautogui를 사용했습니다. 매우 사용하기 쉬운 라이브러리로 마음에 드는

    코드


    import mss
    import numpy as np
    import time
    import cv2
    import pyautogui as pag
    from skimage.measure import compare_ssim as ssim
    
    
    def contour_center(contour):
        """Compute the center coordinates of a contour"""
        M = cv2.moments(contour)
        cx = int(M['m10'] / (M['m00'] + 1))
        cy = int(M['m01'] / (M['m00'] + 1))
        return cx, cy
    
    
    def process_img(img):
        """Extract card images and compute their locations"""
        cards = []
        locs = []
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        _, thresh = cv2.threshold(img_gray, 245, 255, cv2.THRESH_BINARY)
        _, cnts, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
        # filter contours by area
        area_thresh = 6000
        cnts = list(filter(lambda x: cv2.contourArea(x) > area_thresh, cnts))
    
        for i, cnt in enumerate(cnts):
            x, y, w, h = cv2.boundingRect(cnt)
            if i == 0: resize_h, resize_w = h, w
            card = thresh[y:y + h, x:x + w]
            card = cv2.resize(card, (resize_w, resize_h))
            cards.append(card)
    
            cx, cy = contour_center(cnt)
            locs.append((cx, cy))
    
        # sort cards and locs from top left to bottom right
        cards = [card for _, card in sorted(zip(locs, cards), key=lambda x: [x[0][1], x[0][0]])]
        locs = sorted(locs, key=lambda x: [x[1], x[0]])
        return cards, locs
    
    
    def find_match(cards):
        """Find a matching pair"""
        num_card = len(cards)
        max_score = 0
        for i in range(num_card - 1):
            for j in range(i + 1, num_card):
                score = ssim(cards[i], cards[j])
                if score > max_score:
                    match_pair = (i, j)
                    max_score = score
        return match_pair
    
    
    def grab_screen(bbox):
        """Capture the specified area on the screen"""
        with mss.mss() as sct:
            left, top, width, height = bbox
            grab_area = {'left': left, 'top': top, 'width': width, 'height': height}
            img = sct.grab(grab_area)
            return np.array(img)[:, :, :3]
    
    
    def main():
        bbox = (785, 300, 1215 - 785, 740 - 300)
        while True:
            img = grab_screen(bbox)
            cards, locs = process_img(img)
            if len(locs) < 6:
                print('Done')
                exit()
            match_pair = find_match(cards)
            print('Matching pair:', match_pair)
            for idx in match_pair:
                x, y = locs[idx]
                pag.click(bbox[0] + x, bbox[1] + y)
            time.sleep(0.25)
    
    
    if __name__ == '__main__':
        main()
    
    

    향후 전망


    Matching 이외의 게임도 자동화 해 볼 예정

    좋은 웹페이지 즐겨찾기