Python 初心者から始める RPA チュートリアルです。今回は、PyAutoGUIの画像認識機能を利用してマウス操作を行ってみましょう。
この連載は、RPA で今の仕事を効率的にしたい+今、人気のプログラミング言語 Python を学習して副業や転職につなげる、という欲張り企画です。有料のノーコード RPA よりも少し難しいですが、その分学習する価値が高いのが、Python による RPAです。
- PyAutoGUIでスクリーンショット撮影の方法
- PyAutoGUIで画像認識を使う基本的な方法
- 画像認識のマッチ幅を広げる方法
- 画像認識の速度を向上させる方法
本連載では、本当の Python, プログラミング初心者でも手を動かしながら学習できるように、インストール方法やオススメのエディタ(編集ソフト)の説明も行っています。
PyAutoGUIでスクリーンショットを撮影する
実際に RPA として動作させる前に、PyAutoGUIを利用してスクリーンショットを撮影する機能をご説明します。数秒前の画面をベースに画像検索を行ったり、あるいはスクリーンショットを送信したりする RPA の開発に利用できます。
とはいえ、利用頻度はそれほど高くないでしょう。そのため、今回は対話モードで操作してみるだけにします(対話モードについては、第1回を参照してください)。
ただし、ファイルの保存場所が混乱するといけないので、Pythonファイルをまとめて保存しているフォルダなどで py を起動するようにしてくださいね(コマンドプロンプトやPowerShellを指定フォルダで簡単に起動する方法)。
import pyautogui as ag
ag.screenshot()
この時、
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python310\lib\site-packages\pyscreeze\__init__.py", line 144, in wrapper
raise PyScreezeException('The Pillow package is required to use this function.')
pyscreeze.PyScreezeException: The Pillow package is required to use this function.
このようなエラーがでてしまうことがあります。この場合は、quit()関数を呼び出して python の対話モードを一回終了しましょう。そうしたら、
py -m pip install Pillow
として、Pillow パッケージをインストールしてください。通常であれば、こういった依存関係にあるパッケージは同時にインストールされるものですが、今回の Pillow のようにインストールからもれている場合やもあるので、その場合は、手動でインストールしましょう。これ以外の場合でも、通常はエラーメッセージに ~~ package is required のような文言がでるので、必要なパッケージは調べなくても大体分かる様になっています。
では、改めて前述のscreenshot()関数を実行してみましょう(Pillow パッケージをインストールしましたが、importは不要です)。
すると、
<PIL.Image.Image image mode=RGB size=1920x1080 at 0x1EDFCD7E1A0>
このような結果が表示されます。これは、ファイルではなく変数・PCのメモリにスクリーンショットが保存されている状態です。
次に、
ag.screenshot('test.png')
としてみましょう。コマンドプロンプトを開始したフォルダに、test.pngという名前でスクリーンショットのファイルが作成されています。
これは、「現在のフォルダ」に作成されるため、スタートメニューなどから起動するとユーザーのホームフォルダに作成されてしまいます。
では、今度は「現在のフォルダ」にtestというフォルダを作成して、その中にスクリーンショットを保存してみます。
フォルダの中に保存するので、
ag.screenshot('test\test.png')
ですね。\は、フォントにより表示が変わりますが、¥記号の半角文字の意味です。¥キーで入力できます。
しかし、これを実行するとエラーが起きてしまいます。何故でしょうか?
実は、プログラミングにおいて \ 記号というのは特別な文字・通常は入力出来ない文字を入力する場合などに使用する文字(エスケープシーケンス・エスケープ文字)です。この場合、\tなので、タブ文字(タブキーで入力される空白文字)を示します。write()関数のときには、\nが改行・エンターキーを示すものということをやりました。では、どうしたらいいでしょうか?
この問題を回避するためには、複数の方法があります。
1. \文字をさらにエスケープする
\が特別な意味を持つなら、その意味をなくしてしまえばいい、ある意味一番正攻法な方法です。
ag.screenshot('test\\test.png')
上記のように、\を2回続けることで、「\という特別な文字そのもの」として指示します。おおよそどんなプログラミング言語でも使えて、
ag.write(Hello, python.\n\\ means escape')
というように、特別な文字と併用できます。
2.Windowsが悪いからPythonになんとかしてもらう
こちらはそもそも¥を全く使わない方法です。詳しい説明は省きますが、もともとはフォルダの区切りの文字は / (スラッシュ)の方が正統派です(そのため、Windowsでもファイル名に/は使えません)。そして、PythonはWindows上でも、フォルダの区切り文字を / として認識してくれます。
ag.screenshot('test/test.png')
そのため、上記のようにしても動作します。ファイル名・ファイルパスを利用する場合であればお手軽で間違いが少ない方法です。
ただ、もちろんファイルのパス以外で半角の¥を使いたいときには使用出来ません。後は、長いフルパス(例としては、c:\Program Files\Python\3.10\bin\)をエクスプローラーからコピーしたい場合には、結局 \ を / に置き換えないといけないという手間があります。
この記事を読んでいる方はあまり該当しないとは思いますが、Windows上で動く、他のプログラミング言語では対応しているもの・していないものがあるので、色々触ってみたいひとは混乱するという欠点もあります。
3.そもそも特別な文字なんてないことにしてしまう
問題なのは、\記号を特別な文字としてほしくないのに、特別な文字だとして処理されてしまうことです。Pythonには、このエスケープシーケンスを使用しない「raw文字列」という仕組みがあります。使用方法は簡単で、’hogehoge’, “fugafuga”のように、’シングルクォーテーションや”ダブルクォーテーションの前に、rをつけるだけです。
ag.screenshot(r'test\test.png')
このようにすることで、\記号が特別な文字を示すものではなくなり、エラーが出なくなります。エクスプローラーから文字列をコピーしてくる場合、なんの手間もなく一番楽な方法です。
欠点は、\nなどと併用できないことと、他のプログラミング言語とはほぼ互換性がないことです(プログラミング言語によって、’シングルクォーテーションをraw文字としたり、rの代わりに@マークを使用したりと違いが多くあります)。
範囲を指定してスクリーンショットを撮影する
特別指定しない場合、screenshot関数は画面全体、またはメインディスプレイ全体をスクリーンショットに収めます。ただ、一部だけほしい場合などもありますし、大きすぎるスクリーンショットでは作成に時間がかかる場合もあります。環境によると思いますが、1920×1080のサイズで、大体100ミリ秒かかるとされています。
範囲を指定したスクリーンショットの撮影には、
ag.screenshot(region = (10, 10, 200, 300))
ag.screenshot(r'test\test2.png', region = (10, 10, 200, 300))
このようにscreenshot関数に引数を渡します。上記の設定で、(10, 10)から、右に200ピクセル、下に300ピクセルの範囲、座標で言うと(210, 310)までをスクリーンショットにおさめます。座標(10, 10)から、座標(200, 300)までではないので気をつけてください。
ただ、残念ながらこのスクリーンショット機能はマルチディスプレイには対応できません。
PyAutoGUIで画像認識を行う基本的な方法
ではいよいよ RPA で画像認識を行います。前項で見た通り、PyAutoGUIのスクリーン系の機能はマルチディスプレイに対応していません。動作させる際は、対象をメインウィンドウに移動するように工夫してみてください。
メモ帳を起動して左上のアイコンのスクリーンショットを撮影します。Windows10 以降であれば、Windowsキー+Shiftキー+S で起動する「Snipping Tool」が楽だと思います。もちろん、別にスクリーンショットツールを有していれば、そちらを使ってもいいでしょう。普段、Pythonファイルを保存しているフォルダの中に、仮にScreenShotというフォルダを作り、その中に今撮影したアイコンファイルを格納します。
仮に、\myPython\ScreenShot\memoicon.png というファイルを作成します。
結果を確認しながら進めたいので、引き続き対話モードをmyPythonフォルダで起動して実験していきます。
ag.locateOnScreen(r'ScreenShot\memoicon.png')
画像の位置を探す関数は、locateOnScreen()です。最も簡単な使い方は、画像を保存してあるフォルダへのパスをlocateOnScreenに渡すことです。
注意点は、jpegファイルはできるだけ使わず、png(24bitか32bit)形式を使いましょう。JPEGファイルや、pngの8bit形式では、色情報の正確性が失われてしまうので、画像認識の精度が低下するためです。
では、メモ帳のウィンドウを画面に表示した状態で、locateOnScreenを実行して見ましょう。
Box(left=157, top=154, width=36, height=44)
このような結果が返されれば成功です。次は、あえてメモ帳のウィンドウを非表示にして実行してみます。すると、数秒間対話モードが固まったようになり、結果が出力されないで次の入力を待つ状態になります。見つからなかったということですね。
タスクバーにメモ帳のアイコンがでていても、そちらには反応しないことに注意します。大きさや背景の色が異なるためです。
画像の中心を求める
アイコンをクリックしたい場合、画像の範囲が分かっただけではクリックできません。一番安定してクリックできるのは、画像の中心です。
画像の中心は、(left + width / 2, top + height / 2)で求めることができますが、いちいち計算するのはちょっと面倒です。そこで、
ag.center(ag.locateOnScreen(r'ScreenShot\memoicon.png'))
としてみます。すると、
Point(x=175, y=176)
として、left(左端の座標)にwidth(画像の幅)の半分、top(上端の座標)にheight(画像の高さ)の半分が足された数字が返されます。
なおag.center(ag.locateOnScreen(r’ScreenShot\memoicon.png’))という書き方ですが、locateOnScreenを実行して、その結果をさらにcenter関数に渡して実行するという意味になります。
ちゃんと書く場合は、
locate = ag.locateOnScreen(r'ScreenShot\memoicon.png') #locateという変数にメモ帳アイコンの位置を保存する
point = ag.center(locate) #locateに保存された領域の中心をもとめ、pointという変数に保存する
point #pointに保存された内容を表示(対話モードのみ)
以上のようになります。
最後に、アイコンをクリックする場合ですが、
ag.click(point) #直接clickにpointを渡す
x, y = point #x, y座標に分解
ag.click(x, y) #x, y座標を別々に入力する
上記のどちらの方法でも動作します。実行して動作を確認してみてください。x, yに保存された数字も確認すると、より理解が深まります。
一発で画像の中心を求める場合
上記では、画像が見つかった範囲から、改めて画像の中心を計算しています。とはいえ、最初から画像の中心しか必要ないという場合もあるでしょう。
そういう場合には、locateCenterOnScreen()関数が使用できます。
使用方法自体は、locateOnScreenと一緒で、結果が最初から中心の座標になっていることだけが、異なる点となっています。
正確性(信頼性)を落とした画像認識
locateOnScreenは「まさにこれ」という画像の位置しか反応しませんでした。正確なのがPCの売りですが、Web操作などでは少し色が異なるといったことはよくあります。
そこで、confidence (信頼性)オプションを利用します。つまり、あんまり正確ではないけど、大体同じ(信頼しにくい)ものも見つかるようにします。
このconfidence オプションには画像認識の機械学習でよく使われるopenCVが必要なので、まずはopenCVをインストールします。quit()関数で、対話モードを抜けて、
py -m pip install opencv-contrib-python
として、OpenCVをインストールします。今は学習目的なので、extraモジュールを含むバージョンをインストールしていますが、商用の場合は、opencv-python パッケージをインストールするといいでしょう。(OpenCVそのものはオープンソースで商用利用・再配布も可能です。ただし、BSDライセンスのため、ライセンス文書の原文を添付する必要があることに注意してください)
では、もう一度対話モードに戻ります。
ag.locateOnScreen(r'ScreenShot\memoicon.png', confidence='0.6') #confidenceに低い値をいれる(1.0が最大)
Box(left=1794, top=81, width=36, height=44)
confidence に0.5や0.6などの低い値を設定すると、大分ゆるい判定になり、似たようなアイコンなどに引っかかるようになります。一方で、タスクバーのように「大きさ」の違うよく似たアイコンにはまだ引っかかりません。
マウスオーバーやその他の条件で色が変わってしまう画像や、半透明で背景が異なってしまう画像などに利用するといいでしょう。
画像認識の高速化
ここまで画像認識の実験を行って、PCの性能などにもよりますが、結構な時間がかかったのが体感できたと思います。もし、対話モードを使っていなかった場合は、locateOnScreenの実行速度などを体感できなかったと思いますので、1度対話モードを試してみてください。
通常の RPA であれば問題のある速度ではないですが、ある程度のリアルタイム性が求められる場合には、困ったことになる場合があります。
そこで、もう少し画像認識を高速化してみましょう。
高速化には、色情報を無視してグレースケールでの認識を行います。
ag.locateOnScreen(r'ScreenShot\memoicon.png', grayscale=True)
ag.locatCenterOnScreen(r'ScreenShot\memoicon.png', grayscale=True)
grayscale引数にTrueを設定することで、画像の明るさだけで判定を行います。実行してみると分かりますが、結果がこれまでよりずっと早く返ってくると思います。
また、注意点としては、grayscale=’True’ とはせずに、grayscale=True とクォーテーションでくくらないでください。これは、引数が真偽値という形式であるためです。
真偽値とは、その名の通り正しいかそうでないか、0か1かのどちらかを示す形式です(変数の型と言います)。クォートで囲ってしまうと、いろいろな文章を含むことができる文字列になってしまうためです。
Trueの反対はFalseで、grayscaleにTrueを入れていない場合は、常にFalseが代入されていることになっています。このいれなかった場合の値のことをデフォルト値と言います。
若い方であれば、標準的なことを「デフォ」とか、「デフォルト」とか言うと思いますが、元はプログラミング用語でした。ちなみに、デフォルトとは標準という意味ではなく、元々は金融用語と同じで不履行・怠慢を意味します。
プログラマが怠慢で値を入れないときに代わりに入れてくれるから、デフォルト値というわけですね。
おわりに
今回はPyAutoGUIで画像検索を行う方法を見ました。前回と今回で、PyAutoGUIの機能の8割は学習できました。ただ、Pulover’s Macro Creator や商用の RPA ツールと異なり、道具だけ渡されて後は「Pythonで書いてね」というのがPyAutoGUIの特徴です。
例えば、特定のウィンドウが表示されるまで待つには? 画像が出てくるのを待つには? というと、元となる道具があるので、それを使って自分で処理する必要があります。ここからが RPA ツールよりも難しくなりますが、一方でプログラミングの学習になる部分でもあります。
できるだけ初めての方にも分かるように説明していくので、頑張っていきましょう!(また、よく分からない部分があっても、ある程度そういうものだ…としてしまうのも一つの手段です)
- PyAutoGUIのスクリーンショットや画像認識は、メインディスプレイのみ対応
- \記号はエスケープ文字なので、そのもの使いたいときは\\や、r”などで対応する
- locateOnScreenで画像の位置が検索できる
- クリックしたいときは通常「中央」を求める
- confidenceである程度ルーズな検索ができる(OpenCVが必要)
- 速度が必要なときは、grayscale=True
- Trueは真偽値なので、クォーテーションでくくらない