LoginSignup
1
5

More than 5 years have passed since last update.

PythonにBrain Warsをプレイさせてみた1

Posted at

概要

画像処理の練習がてら、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からスマホを操作してプレイした

使用した主なライブラリと役割

  1. mss: スクリーンキャプチャ
  2. opencv: カードの輪郭抽出や座標計算
  3. scikit-image: 画像の類似度(SSIM)の計算
  4. pyautogui: クリック自動化

1. カード表示領域のキャプチャ

最初はPILImageGrabを使おうと思ったのだが、どうやらMacとWindowsのみしかサポートしていないとのことなので、mssというライブラリを使用した。ゲームのレベルが上がると、カードの行数が3列に増えるので、キャプチャ領域は少し広めに設定してある

2. 各カードの画像と座標の取得

  1. 画像をGray-Scaleに変換

  2. バイナリー化

  3. Bounding Box検出

  4. 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以外のゲームも自動化してみる予定

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5