Sikuliデモプログラムを作ってみた(準備編)
http://qiita.com/miz21358/items/d301297dcb9d925172f6
Sikuliデモプログラムを作ってみた(キャプチャ編)
http://qiita.com/miz21358/items/d2efb093a6f7c1ab9c13
全体的な設定やアプリケーションの起動についてのTips
ソースコードにもコメントですべて記載しています。
GitHubへのリンクは準備編参照。
環境チェック
Sikuliは画像認識で処理を進めるため、OSやらバージョンやら異なると動かない場合が多いです。
なので、コーディングは実際に動かす環境と同じ環境で行った方が確実です。
複数環境で動かすのであれば、バージョン等を別保持しておき、適宜分岐させます。
#画像の文字列解析は正直あまり精度よくないです…日本語は特に。
#個人的には、画像の類似度(下記参照)をあげて、厳密に画像類似度判定させる事で逃げる事が多いですw
# OSチェック
if Env.getOS() != OS.WINDOWS:
print u"利用可能OSはWindows7/10のみです OS:{0} version:{1}".format(Env.getOS(), Env.getOSVersion())
sys.exit()
# OSのバージョンチェック
if "10" in Env.getOSVersion():
self.osVersion = GMSDLib.WINDOWS_10
elif "6" in Env.getOSVersion():
self.osVersion = GMSDLib.WINDOWS_7
else:
print u"利用可能OSはWindows7/10のみです OS:{0} version:{1}".format(Env.getOS(), Env.getOSVersion())
sys.exit()
デフォルト最小類似度
Settings.MinSimilarity = 0.9
find等での画像ヒット率に影響する最小類似度は、デフォルトでは0.7が指定されています。
0.7って結構ガバガバで、余計なものまでヒットする事が多いです。
#IDEで画像をクリックして、パターン設定ウィンドウを開き、色々試してみると分かりやすいです。
なので、デフォルト最小類似度をあげておいて、必要に応じて調整するといい感じ。
アプリケーションのフォーカスと処理高速化
複数のアプリケーションにまたがって処理する時には、その時に使用したいアプリケーションを最前面に持ってくる必要があります。
アプリケーションを最前面にする方法としては、例えば以下のような方法が考えられます。
- メニュー等で該当のプログラムのアイコンをクリックする
- App.focusを使用する
これの2についての説明。
単に開くだけなら、App.open でも openApp でも構わないのですが、フォーカスを切り替えるには、App.focus で返ってくる [App object]を保持しておく必要があります。
http://doc.sikuli.org/globals.html#App.focus
起動そのものも時間がかかる場合があるので、ちゃんとアプリケーションが起動するまで待ちます。
起動完了の判定は、引数に画像を指定して行います。
#引数に画像を設定する、って絵面的にとてもシュールw
ついでに、アプリケーションの領域(Region)を限定します。
ウィンドウ全体から探すより、限定された領域の中からのみ探す方が若干なりとも速いです。
http://doc.sikuli.org/globals.html#App.focusedWindow
おまけに、その範囲内の検索を諦める自動タイムアウト時間も短くしてしまいます。
findなどで画像を取得する時に、Sikuliは一定時間待機しますが、デフォルトでは3秒待ちます。
これが結構長い。見つからなかった場合、3秒経たないと次に行かないのでトロい。
ので、がっつり短くします。(注意事項要参照)
http://doc.sikuli.org/region.html#Region.setAutoWaitTimeout
そんなこんなを詰め込んだメソッドがこちら。
### --------------------------
# 指定されたアプリを開き、フォーカスを取得する
# appName: アプリウィンドウ名
# appExe: アプリ起動フルパス
# appImg: アプリが起動した事を確認する画像
def openFocusApp(appName, appExe, appImg):
print u"openFocusApp: {0}, {1}".format(appName, appExe)
#アプリをいったん閉じる
App.close(appName)
wait(2)
#アプリを開く
#この段階ではアプリ情報は保持しない
App.open(appExe)
try:
# 最大30秒まで、指定された画像が表示され、起動が完了するまで待つ
wait(appImg, 30)
except FindFailed as e:
# 指定時間待っても画像が取得できなかった場合、検索失敗エラーが発生する
logger(u"openFocusApp起動失敗 *** message:{0}".format(e.message))
sys.exit()
except Exception as e2:
#検索失敗以外はそのままエラーを投げる
raise e2
#アプリ名をキーとして、アプリにフォーカスしてオブジェクトを保持する
app = App.focus(appName)
print u"app.hasWindow(): {0}".format(app.hasWindow())
print u"app.getPID(): {0}".format(app.getPID())
print u"app.getName(): {0}".format(app.getName())
print u"{0}: オープン成功".format(appName)
wait(5)
#最前面に出ているアプリの範囲を取得し、検索範囲を少しでも限定する
#こうする事で多少なりとも処理が速くなる
reg = Region(App.focusedWindow())
#アプリのタイムアウトを設定する
#デフォルトは3秒待つので、動作がどうしても遅くなってしまう
#あらかじめ短く設定しておき、必要に応じて待機時間を長くとればサクサク動く
reg.setAutoWaitTimeout(0.1)
return (app, reg)
このメソッドで返ってきたappとregを保持しておくと何かと便利。
- フォーカスを切り替える場合
app.focus()
- アプリケーションの範囲内で画面操作を行う場合
reg.click("sample.png")
注意事項
- appNameはプロセスから検討をつけます。(ここが一番厄介)
- App.close は確かにアプリケーションが閉じますが、強制終了のような扱いを受けるらしく、次に起動した時に「不正終了しました」っぽいメッセージがアプリケーションによっては出てきます。
- メモ帳のようにパスが通ってると、appNameに"notepad"と指定しても、新しいプロセスが立ち上がってしまいます。こういう場合は、アイコンクリックとかで対応した方がいいと思います。
- setAutoWaitTimeoutを短く設定すると、処理はかなり早くなりますが、判定が速すぎて、実際の画面描画が追い付かない事象が発生します。setAutoWaitTimeoutを少し長めに設定しておくとか、表示された後に少し待つとか、要件によって対応してください。