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




---
この記事は [リンク情報システム](https://www.lis.co.jp) の「[2020新春アドベントカレンダー TechConnect!](https://qiita.com/dgkz/items/5131349d56354b33ee97)」のリレー記事です。
TechConnect! は勝手に始めるアドベントカレンダーとして、engineer.hanzomon という勝手に作ったグループによってリレーされます。
（リンク情報システムの[Facebookはこちら](https://www.facebook.com/lis.co.jp/)から）

---

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

# 出来たもの
Shadowverse（Steam版）のWin/Lose判定を行うプログラムが出来ました。
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289156/22a54985-7bd4-5d9d-90b4-0087c0b6014d.gif)
（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でスクリーンキャプチャを行う](https://qiita.com/koara-local/items/6a98298d793f22cf2e36)

```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）](https://qiita.com/hitomatagi/items/caac014b7ab246faf6b1)

```python
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で足切りを行います。

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

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

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

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

```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さんです。