Pythonで指定した範囲のスクリーンショットを撮る

# 概要
みんな大好き大乱闘スマッシュブラザーズ(以下スマブラ)！！
私事ながら，スマブラ配信をしています．[チャンネル登録お願いします！](https://www.youtube.com/channel/UCKys_FIMcKfIn5L4HOyy0vQ)
スマブラ×機械学習でなんかできないかなー，と思っていたところこんな記事を発見しました．
[画像認識でスマブラの戦績を自動で作成するツールを作ろう](https://note.com/kelp168/n/nf717de3a2d22)

最終的に何を作るかは置いといて，面白そうだしとりあえずやってみよう！
配信画面からスマブラのスクリーンショットを撮る→画像認識でなんかする
ざっくりこんな感じで進めます．
今回の目標は範囲を指定してスクリーンショットを撮ることです．
配信画面全体ではなく，
![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3073654/77ff4522-9f7f-b4d0-bd3a-693deabb789d.png)
ゲーム画面のみを指定してスクリーンショットを撮りたい！
![screenshot1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3073654/05a7ddd9-22b9-32ec-819c-79a68c5aaefa.png)

# スクリーンショットを撮る
pyautoguiってやつを使えばスクリーンショットを撮れるらしい．
```
$ pip install pyautogui
```
どうやらスクリーンショット以外にも，マウスやキーボードの操作も行えるみたい．気になる方はドキュメント読んでみてください．
[pyautoguiのドキュメント](https://pyautogui.readthedocs.io/en/latest/)

今回はスクリーンショットを撮って画像認識をしたいだけなので，スクリーンショットを撮る方法だけ紹介します．
```python
import pyautogui
# 全画面のスクリーンショットを撮る場合
img1 = pyautogui.screenshot('my_screenshot_all.png')
# 範囲を指定する場合
img2 = pyautogui.screenshot('my_screenshot_region.png', region=(left,top,width,height))
```
はい，これだけで全画面のスクリーンショットが指定したファイル名で保存されます．
超簡単！！だけど，全画面のスクリーンショットを一枚撮って保存するだけで私の環境だと0.26秒ぐらいかかりました．うーん，遅いけどまぁいいか．

途中アクセスを要求されたら，
セキュリティとプライバシー→画面収録
からターミナルに画面収録の許可を与えてください．与えないとデスクトップしか撮れません．
![スクリーンショット 2022-12-21 2.50.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3073654/92920ff0-777c-281e-e8c4-fb0d1613a704.png)

# スクリーンショットを撮る範囲を指定する
本来はゲーム画面を自動で検出したかったけど，配信レイアウトは人それぞれなので今回は汎用性を上げるためにクリックで指定できるようにします．
マウスのクリックを検知して位置を取得できれば，スクリーンショットを撮る範囲の左上と右下を指定できるはず！
pynputってのでクリックを検知できるらしい．まずはインストール！
```
pip install pynput
```
とりあえず，公式のサンプルコードのクリックに関する部分を動かしてみる．
```python
from pynput import mouse

def on_click(x, y, button, pressed):
    print('{0} at {1}'.format(
        'Pressed' if pressed else 'Released',
        (x, y)))
    if not pressed:
        # Stop listener
        return False

# Collect events until released
with mouse.Listener(on_click=on_click) as listener:
    listener.join()
```
今度は入力監視のアクセスを要求されたので，ターミナルに許可のチェックを入れます．
![スクリーンショット 2022-12-22 1.53.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3073654/734652f7-a8c7-9cda-e269-7fe23f9c5fcb.png)

下記の実行結果から見てわかるように，クリックをすると位置が表示されて離すと終了するって感じですね．
```
Pressed at (1038.02734375, 455.2265625)
Released at (1038.02734375, 455.2265625)
```
コードをちょっと変えて範囲指定できるようにしました．
```python
from pynput import mouse

left_up_x, left_up_y, right_down_x, right_down_y = [0,0,0,0]
num_press = 0

def main():
    with mouse.Listener(on_click=on_click) as listener:
        listener.join()
    # 範囲の確認
    print(f"left_up_x: {left_up_x}")
    print(f"left_up_y: {left_up_y}")
    print(f"right_down_x: {right_down_x}")
    print(f"right_down_y: {right_down_y}")
    
def on_click(x, y, button, pressed):
    global left_up_x, left_up_y, right_down_x, right_down_y, num_press
    if pressed:
        # 1回目のクリックはウィンドウの切り替え用で何もしない
        if num_press == 0:
            num_press = 1
        # 2回目のクリックはスクリーンショットを撮る範囲の左上を指定
        elif num_press == 1:
            num_press = 2
            left_up_x, left_up_y = x, y
        # 3回目のクリックはスクリーンショットを撮る範囲の右下を指定して終了
        else:
            right_down_x, right_down_y = x, y
            return False

if __name__ == "__main__":
    main()
```
```
left_up_x: 948.53125
left_up_y: 479.04296875
right_down_x: 1190.75
right_down_y: 637.22265625
```
範囲指定は問題なくできてそうですね．
これであとはスクリーンショット撮るだけや！
と思ったら次でしっかり沼りました．
# 指定した範囲のスクリーンショットを撮る
まずはpyautogui.screenshotのregionで指定してスクリーンショットを撮ってみる．
```python
import pyautogui

def main():
    with mouse.Listener(on_click=on_click) as listener:
        listener.join()
    # 範囲の指定が終わったらスクリーンショットを撮る
#     print(f"left_up_x: {left_up_x}")
#     print(f"left_up_y: {left_up_y}")
#     print(f"right_down_x: {right_down_x}")
#     print(f"right_down_y: {right_down_y}")
    im = pyautogui.screenshot("screenshot.png", region=(left_up_x, left_up_y, right_down_x-left_up_x, right_down_y-left_up_y))    
```
これだとなぜか指定した範囲とは違う範囲のスクリーンショットが撮られました．
結論から言うとx座標，y座標を全て2倍するとうまくいったのですが，なぜそうなったのかは未だに分かっていません．
```python
im = pyautogui.screenshot("screenshot.png", region=(left_up_x*2, left_up_y*2, (right_down_x-left_up_x)*2, (right_down_y-left_up_y)*2))
```
これでうまくいきました．
pyautoguiで取得している解像度と，実際の解像度に差があるのかも？と思って調べてみました．
まず，pyautoguiから
```python
print(pyautogui.size())
```
```
Size(width=1470, height=956)
```
次はMacbook Air
![スクリーンショット 2022-12-22 3.59.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3073654/d9cefc70-d50a-1df7-c5f2-b8f3721027c2.png)
うーん，どちらも1470×956ですね．
スクリーンショット範囲の左と上の端をどちらも0にして，幅2940，高さ1912にするとちょうど全画面のスクリーンショットが撮れるのですが，，，なんでですかね？分かる方いたら是非教えてください．
# 終わりに
範囲指定してスクリーンショット撮るだけでも結構時間がかかってしまった．
[次回](https://qiita.com/kenshin-627/items/098703383374e1e7b65b)は，対戦開始・終了タイミング取得の前準備として，スクリーンショットからエッジ検出，直線検出をしたいと思います！