シャドバの勝ち負けを画像認識で判定する



この記事は リンク情報システム の「2020新春アドベントカレンダー TechConnect!」のリレー記事です。
TechConnect! は勝手に始めるアドベントカレンダーとして、engineer.hanzomon という勝手に作ったグループによってリレーされます。
(リンク情報システムのFacebookはこちらから)


テーマが自由だったので自由な記事となります。(使用技術は真面目です。)
チュートリアルを駆使し、頑張って画像認識をしました。

出来たもの

Shadowverse(Steam版)のWin/Lose判定を行うプログラムが出来ました。

(gifの画面はLose判定を確認するために描画しています。判定は描画しなくても可能です。)

見ての通り、Win/Lose画像とキャプチャ画面を比較して判定しているだけなので、
「特定のアプリケーションのキャプチャに、指定画像が出現しているかをリアルタイムで判定する」
ことがしたい人の参考にもなるかと思います。

使ったもの

  • Python 3.7.4
  • pip 19.3.1
    • numpy 1.17.3
    • opencv-contrib-python 4.1.1.26
    • Pillow 6.2.0

導入方法は後述する参考記事を参照してください。
片っ端からpipでインストールするだけで出来たので特に詰まった所は無かったです。

やり方

  1. ゲーム画面をスクリーンキャプチャする
  2. キャプチャした画像と、Win/Lose画像を比較
    1. 特徴点をDistanceで足切りし、一定以上の特徴点がマッチしていればWin/Loseと判断
  3. 一定回数以上Win/Lose判定が連続した場合に、Win画面/Lose画面と判定

ゲーム画面をスクリーンキャプチャする

以下の記事を参考にPILを使用してキャプチャを行いました。
[Python][Windows] Pythonでスクリーンキャプチャを行う

from PIL import ImageGrab
import numpy as np

TARGET_NAME = 'Shadowverse'
handle = win32gui.FindWindow(None, TARGET_NAME)

while True:
    rect = win32gui.GetWindowRect(handle)
    img = ImageGrab.grab(rect)
    ocv_im = np.asarray(img)

    #OpenCV用に色変換
    ocv_im = cv2.cvtColor(ocv_im, cv2.COLOR_BGR2RGB)

    #画面に描画(確認用)
    cv2.imshow("images", ocv_im, )

    #適当な方法でWaitをかける(描画時間の確保)
    cv2.waitKey(10)

キャプチャした画像と、Win/Lose画像を比較

(あらかじめWin/Lose画像はスクリーンショットから切り抜いておきます。)

以下の記事を参考に、AKAZEで特徴量マッチングを行います。
OpenCV 3とPython 3で特徴量マッチング(A-KAZE, KNN)

def MatchResultCheck(ocv_img):
    win_img = cv2.imread(WIN_IMAGE_PATH)
    lose_img = cv2.imread(LOSE_IMAGE_PATH)

    akaze = cv2.AKAZE_create()

    kp2, des2 = akaze.detectAndCompute(ocv_img, None)
    kp1_l, des1_l = akaze.detectAndCompute(lose_img, None)
    kp1_w, des1_w = akaze.detectAndCompute(win_img, None)

    is_win = False
    is_lose = False

    bf = cv2.BFMatcher()
    if not des2 is None :
        #Lose画像とキャプチャ画像で特徴量マッチング
        matches_l = bf.knnMatch(des1_l,des2, k=2)
        #Win画像とキャプチャ画像で特徴量マッチング
        matches_w = bf.knnMatch(des1_w,des2, k=2)

        #Lose判定
        good_l = []
        for match1, match2 in matches_l:
            if match1.distance < 0.75*match2.distance:
                good_l.append([match1])
        #Win判定
        good_w = []
        for match1, match2 in matches_w:
            if match1.distance < 0.75*match2.distance:
                good_w.append([match1])

        #確認用の画像を作成
        akaze_matches = cv2.drawMatchesKnn(lose_img,kp1_l,ocv_img,kp2,good_l,None,flags=2) 
        #画面に描画(確認用)
        cv2.imshow("match", akaze_matches, )

        if len(good_l) > 20 :
            print("is lose")
            is_lose = True

        if len(good_w) > 20 :
            print("is win")
            is_win = True

    return is_win, is_lose

Win/Loseの判定

マッチング結果にはDistance(どれだけマッチしているか)が格納されているので、Distanceで足切りを行います。

        #Lose判定
        good_l = []
        for match1, match2 in matches_l:
            #Distanceが一定以上の特徴点だけを抜き出す
            if match1.distance < 0.75*match2.distance:
                good_l.append([match1])

足切り後、一定以上特徴点が残っていれば、Win/Lose画像が存在していると判断します。

        if len(good_l) > 20 :
            print("is lose")
            is_lose = True

Win画面/Lose画面の判定

誤検知を抑止するために、一定回数以上連続でWin/Lose画像と判断した場合のみWin/Lose画面と判定します。
(python使ってるんだからもっと簡素に書けそう……)

    is_win, is_lose = MatchResultCheck(ocv_im)

    if is_lose :
        cnt_lose_match += 1
        if cnt_lose_match >= 4:
            print("is lose match")
            cnt_lose_match = 0
            cv2.waitKey(1000)
    else :
        cnt_lose_match = 0

出来なかったこと

同様に先攻/後攻も判定しよう!と思ったら上手く出来ませんでした……
Win/Loseと違って判定画像が小さすぎるのと、漢字なのが辛いのかなって思ってます。
引き続き精進していきます。

まとめ

スクリーンキャプチャに特定の画像が存在しているか判定できました。
判定結果をリアルタイムに描画できると面白いですね。

次回は@rysk001さんです。