雀魂の画面から画像認識で対戦情報を持ってくる（Vol. 2）

[≪前の記事](https://qiita.com/xenepic_takku/items/20a7f502d6cea2ea5be8)　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　[次の記事≫](https://qiita.com/xenepic_takku/items/4aa94bece50364002497)

# 前回までのあらすじ
node.jsで画面のスクショを取る方法を色々探った結果、`python-shell`を使うとnode.jsからpythonが実行できるとのこと。
pythonでは`pillow`を使用するとスクショが取れるようなので、これを組み合わせてスクショ問題を解決しよう！

# pillow
さて、じゃあまずpythonでpillowをインストールして、実際にスクリーンショットが撮れるか試してみます。

```
pip install pillow
```
でインストール完了。

```python:examplePillow.py
from PIL import ImageGrab

ImageGrab.grab().save('screenshot.jpg')
```
プログラムとも呼べないような2行のコードを書いて、いざ実行すると、
おぉ！ちゃんと保存できている！
いやーなんて素晴らしいんだpython！すごいぞpython！それゆけpython！
<img width="400" alt="screenshot.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381769/961291fc-a805-4099-836d-cf8e3983d3cf.jpeg">

# マルチモニターへの対応
一つ気になったのは、私マルチモニターで作業してるんですけど、メインモニターの画像しか保存出来てないんですよね。
配信とかしてるとメインにOBSを持ってきてサブで色々やることも多いし、うーんどうしよ？
と思って色々探っていると、

> [Python]マルチモニター環境でのウィンドウキャプチャ - Qiita
https://qiita.com/danupo/items/e196e0e07e704796cd42

という記事を発見。
ウィンドウ名を部分一致で検索して、ヒットした場合はそのウィンドウのスクショを、ヒットしなかった場合は全画面のスクショをビットマップにして保存しています。
検索ワードを「メモ帳」にして動かしてみると、おぉ！プライマリモニターだけじゃなくて、サブモニターにメモ帳がある場合もスクショを持ってこれる！
じゃあ雀魂で検索すれば、雀魂画面のスクショを持ってこれるじゃん！
と思って実行したところ、次のような画像が得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381769/d6751548-4317-12a4-2d49-c10802642619.png)
※お使いのデバイスは正常です

なんでやねーん！！
ただ、デスクトップをスクショした場合は正しく映るんですよね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381769/3591c98f-4c6a-cbef-013c-061a5f8b0c7e.png)

プログラムの流れとしては、
```py:windowCapture.py
    # ターゲットウィンドウ名を探す
    for process_name in process_list:
        if window_name in process_name:
            hnd = win32gui.FindWindow(None, process_name)
            break
    else:
        # 見つからなかったら画面全体を取得
        hnd = win32gui.GetDesktopWindow()
```
ここでhndにターゲットとなるウィンドウオブジェクトを入れて
```py:windowCapture.py
    # ウィンドウサイズ取得
    x0, y0, x1, y1 = win32gui.GetWindowRect(hnd)
    # ウィンドウのデバイスコンテキスト取得
    windc = win32gui.GetWindowDC(hnd)
    srcdc = win32ui.CreateDCFromHandle(windc)
    memdc = srcdc.CreateCompatibleDC()
    # デバイスコンテキストからピクセル情報コピー, bmp化
    bmp = win32ui.CreateBitmap()
    bmp.CreateCompatibleBitmap(srcdc, width, height)
    memdc.SelectObject(bmp)
    memdc.BitBlt((0, 0), (width, height), srcdc, (0, 0), win32con.SRCCOPY)
```
ウィンドウのデバイスコンテキストを取得してから、そのビットマップ情報を取得という流れのようです。

> - デバイスコンテキストとは
ディスプレイやプリンターなどのデバイスの、描画属性に関する情報を保持しているWindowsのデータ構造体。
そのグラフィックオブジェクトには、アプリケーションをスクロ＾るするためのビットマップ情報なども含まれる。

このhndに代入されるオブジェクトがブラウザならうまく映らず、デスクトップならうまく映る。
ブラウザ以外のメモ帳やエディタもちゃんと映ります。
ブラウザはChrome、Edge、Firefoxで試しましたがどれもうまく映りませんでした。
なんぞや...？

色々調べてみたんですけど、こちら原因は分かりませんでした。
最近のブラウザって、ハードウェアアクセラレータとか使ってるじゃないですか。
あの辺が悪さしてるのかなぁ。

...
..
.


なので方向性を変えて、当初の`ImageGrab.grab()`を使う手段でマルチスクリーン対応を目指してみることにしました。

> pyautoguiを【超適当に】マルチディスプレイ環境に対応させる Part1 - Qiita
https://qiita.com/kznSk2/items/a6833c095aec3b8ce72e

pyautoguiというライブラリの中でImageGrab.grab()を使用しており、それをマルチスクリーンに対応させている記事でした。
方法はというと単純で
```py
ImageGrab.grab(all_screens = True)
```
のようにパラメータを追加するだけ！

# コード完成

だいぶ回り道をしましたが、以上のことをまとめてようやくコードが一段落しました。

```py:windowCapture.py
import win32gui
import cv2
from PIL import ImageGrab
import ctypes
from ctypes import wintypes

def WindowCapture(window_name: str, bgr2rgb: bool = False):
    # 現在アクティブなウィンドウ名を探す
    process_list = []
    targetHit = False

    def callback(handle, _):
        process_list.append(win32gui.GetWindowText(handle))
    win32gui.EnumWindows(callback, None)

    # ターゲットウィンドウ名を探す
    for process_name in process_list:
        if window_name in process_name:
            targetHit = True
            hnd = win32gui.FindWindow(None, process_name)
            break
        else:
            # 見つからなかったら画面全体を取得
            hnd = win32gui.GetDesktopWindow()

    # 前面に移動
    win32gui.SetForegroundWindow(hnd)
    
    # ウィンドウサイズを取得
    f = ctypes.windll.dwmapi.DwmGetWindowAttribute
    rect = ctypes.wintypes.RECT()
    if targetHit:
        DWMWA_EXTENDED_FRAME_BOUNDS = 9
    else: # 画面全体のときは調整しない
        DWMWA_EXTENDED_FRAME_BOUNDS = 0

    f(  ctypes.wintypes.HWND(hnd),
        ctypes.wintypes.DWORD(DWMWA_EXTENDED_FRAME_BOUNDS),
        ctypes.byref(rect),
        ctypes.sizeof(rect)
    )
 
    # 取得したウィンドウサイズでスクリーンショットを撮る
    img = ImageGrab.grab((rect.left, rect.top, rect.right, rect.bottom), all_screens=True)

    return img

img = WindowCapture('雀魂')
img.show()
```

~~ぱくり~~引用だらけのコードですが、まぁそんなもんでしょ！
これでサブモニターにウィンドウがあっても、雀魂のスクショを持ってくることが出来ます！
やったねたえちゃん！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381769/e87166c7-e68d-ca16-e660-3fd898837fec.png)

さて、今回の記事は一旦ここまで。
次はいよいよパターンマッチングを使って、スクショから牌姿情報を取得していきます！
初めてのopenCV。
怖いけど楽しみ。


[次の記事≫](https://qiita.com/xenepic_takku/items/4aa94bece50364002497)