ウィンドウタイトルを指定してその領域だけを定期的に保存する

PythonのOpenCVを使って何か(リアルタイムな)便利なものを作るぞーと意気込んだとき。
意気込んだまま挫折する理由は主に以下の2点です。

* OpenCVで対象を検出する検出器を作る力量(熱量)がない→簡単につくれるツールがある→学習用画像を大量に集めることに挫折する
* 画面キャプチャを取得するとき、ウィンドウを指定できず、画面全体を取得してしまう

大体後者で毎回「こんなことも(簡単に)出来ないのか…」と諦めてましたが、ようやくちょっと進んだ。

python入れてないPCで使いたかったのでビルドまで試し。
[Releases · khsk/Python-App-Capture]

# <i class="fa fa-cogs"></i> 検証環境

* Python 2.7 (WinPython-32bit-2.7.10.3)
* Spyder 3.0.0 (同梱)
* PyQt4
* Windows7
* cx_Freeze

# 画面キャプチャの困りごと

Pythonでキャプチャを取ろうとすると、おそらくほとんどの人は**ImageGrab.grab()**に行き着くと思います。
そして画面全体か、xyを(計算して)与えないといけないと知り、がっくし来るんじゃないでしょうか。

もうちょっと頑張って調べると、今度はSeleniumやpywinauto、winxpguiなど、自動化の範疇になり、アプリ起動→キャプチャまでをプログラムでするようなものが引っかかって、ユーザーが操作しているものを簡単に対象にするような記事が見つかりにくい。

そこから更に頑張ろうとしても、それらノイズに負けて諦めてた。

望んでいた動きとしては、プロセス名(～.exe)を指定すれば、そのウィンドウ情報を取得して、範囲指定に使えるウィンドウサイズなどが欲しかった。
というか［Alt］＋［Print Screen］。

***

でも最近、
[Pythonでウィンドウハンドルを列挙する（Windows） - Qiita]
をみつけて、求めていることに近そうだったんですけど、win32apiで、C++？とかの領域だしで覚えること多そう…難しそう…と躊躇してましたが、これをとっかかりにもう少し調べて
[Pythonでウィンドウタイトルからハンドル取得 | 際物コレクション的なサムシング！]
を見つけて、プロセスじゃなくてタイトル名ですが許容できますし、これぐらい抽象化してくれていると自分でも触れる！と奮い立ちました。
中身のコードも読めるレベルで簡単でしたし。

# 指定ウィンドウを保存するコード

```py

import cv2
import pyws as m
import numpy as np
import winxpgui

try :
    handle = m.getid('ウィンドウ名(一部でも化)')
    rect = winxpgui.GetWindowRect(handle)
except IndexError as e:
    return
img = ImageGrab.grab(rect)
img = np.asarray(img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.imwrite('保存先/' + datetime.now().strftime("%Y%m%d%H%M%S") + '.jpg', img)

````

かんたんにできた！
ハンドルからのキャプチャは[pywsの画像認識]を参考にしていたんですが、
`winxpgui.GetWindowRect`がウィンドウ全体の、画面上の座標
`winxpgui.GetClientRect`がアプリの描画領域の大きさを返すようで、pywsではキャプチャの起点を`winxpgui.GetWindowRect`、大きさを`winxpgui.GetClientRect`を使って計算しています。
おそらくフルスクリーンではいけるのでしょうが、こちらの環境では、
ウィンドウを含めた左上から描画領域分のサイズだけキャプチャすることになり、
ウィンドウ分の右下が欠けてしまいました。
`winxpgui.GetClientRect`だけでは絶対座標がわからないので、
アプリの描画部分のみ取得するにはウィンドウ分のマージンを手動で含めなければならないのかな、と思い、うまみがないのでウィンドウ含めた全体を取得することにしました。

# Qt化

取得できたことで、後はwhileループを組み、手動で毎回間隔とタイトルをソースごといじれば自分の用途には叶うなと思ってたんですが、毎回終了・起動も面倒だし、ループの先頭に変数のスイッチ(if continueする)を入れてアプリのボタンでオン・オフできれば楽だな、とQtを触ってみました。

結果的に、whileループと変数スイッチで作ろうとしていたものはシグナルプログラミングと`QTimer`という素晴らしいもので書け、毎回コードをいじろうと思っていた部分もフォーム化できていい事ずくめでした。
画面遷移しないレベルですが、Qtのライブラリ規模から思っていたよりも作るのが簡単でした(ほぼコピペですけど)

![2017-10-05_11h41_01.png](https://qiita-image-store.s3.amazonaws.com/0/62354/a1dd3c46-bcf6-7685-d482-e0c1c8ebeab3.png)

入力欄を作れば前回終了時の値を復元しないとソース書き換えより手間が増えるので、なし崩し的にコンフィグファイルの読み書きも覚えました。


…Qt5…?


# 役立つの？

たたないです。
といいますかもっと便利なKIOKUなどなどフリーソフトがたくさんあるので。
ただ、保存を別の処理に書き換えられるので、

1. これで画像収集
2. あつめた画像で検出器作成
3. 保存箇所を検出器を使った処理に書き換え

でリアルタイム処理の算段がついたかなと言う感じ。です。

# 参考

* [Pythonでウィンドウハンドルを列挙する（Windows） - Qiita]
* [Pythonでウィンドウタイトルからハンドル取得 | 際物コレクション的なサムシング！]
* [cx_FreezeでPythonをexeにしてみた - lisz-works]
* [PyQtのConnectで引数付きの関数を渡す方法 - drilldripper’s blog]  
  スゴイ、助かった。インスタンスを渡せばシグナル時の値をそのまま使えてよかった。
* [Pillowで読み込んだデータをOpenCVで扱えるように変換するとブルーになる話 - Qiita]
* [PyQtではじめるGUIプログラミング]  
  コードと画面と適切な題材(ラーメンタイマー)のお陰で初心者でも流用でやりたいことができた。スゴイ。



***

* [PyAutoGUI？pywinauto？ | 粉末@それは風のように (日記)]


[Pythonでウィンドウハンドルを列挙する（Windows） - Qiita]:https://qiita.com/katabamisan/items/8de5a36c829819b0c67e

[Pythonでウィンドウタイトルからハンドル取得 | 際物コレクション的なサムシング！]:http://hayatejvn.com/?p=82

[pywsの画像認識]:https://github.com/hayate212/pyws/blob/master/source/pyws.py#L62

[cx_FreezeでPythonをexeにしてみた - lisz-works]:http://www.lisz-works.com/entry/python-cx-freeze

[PyQtのConnectで引数付きの関数を渡す方法 - drilldripper’s blog]:http://drilldripper.hatenablog.com/entry/2016/08/06/175641

[PyAutoGUI？pywinauto？ | 粉末@それは風のように (日記)]:https://funmatu.wordpress.com/2017/06/01/pyautogui%EF%BC%9Fpywinauto%EF%BC%9F/

[Pillowで読み込んだデータをOpenCVで扱えるように変換するとブルーになる話 - Qiita]:https://qiita.com/yusukeasari/items/3eaddfbb3a25f37d8e08

[PyQtではじめるGUIプログラミング]:https://www.slideshare.net/RansuiIso/pyqtgui

[Releases · khsk/Python-App-Capture]:https://github.com/khsk/Python-App-Capture/releases