ポケコン向け探索画像切取りスクリプト(通称:切るミーベイベー)

どしたのわさわさ


背景

ポケモンの自動化界隈ではいろんなポケモンの厳選が行われており、画像解析を利用したツールにPoke-Controller(以下、本記事ではポケコン)が挙げられます。自分もポケモンSVにて色コレクレーを捕まえたく、ジュナリ様が開発した神プログラムこと「SV_テラレイド逃げて自動色厳選」をポケコンのMODIFIED版にて利用させてもらってます。

ジュナリ様にはこの場をお借りして厚く御礼申し上げます!

さて、「SV_テラレイド逃げて自動色厳選」ですがver1.0.0の変更で画像の一致探索を行う関数isContainTemplateに、探索する範囲の座標が引数cropに明確に追加されておりました。

if self.isContainTemplate('SV_Raid_escape/sleep.png', threshold=0.8,
                          use_gray=True, show_value=self.Check_th,
                          crop=[848, 434, 914, 498]):
    self.press(Button.B, wait=1.2) #スリープ画面を検知したときの復帰処理

この変更により一致判定の検出時間が短くなるのですが、ポケコンをRaspberry Pi上で動かしてみるとすごく顕著に表れていました。
例えば、星5コレクレーの平均検出時間が以前だと7分台だったのに対して、ver1.0.0では6分を下回るケースもありました(実際に測定した結果もこの記事の後半にまとめましたので、そこまで読んでいただけると幸いです)。

自分が作成するプログラムも同様に範囲を明示しようかと思ったのですが、

  • アイコンなどはきれいに切り抜きたい

  • 切り抜いた画像の座標を把握しておき、ぽけこんで切り抜き判定を行う際に利用したい

  • 再現性がある切り抜きをしたい

  • ともかくたくさん"キル"し。。。間違えました。"切る"したい!(なんか匂わせてる?)

といった思いから、探索画像を手軽に切り抜ける本スクリプトを作成してみました。

作成物

前提条件

たぶんPythonと以下ライブラリがpipでインストールされてれば動くと思いますが、ポケコンが動く環境であれば動きます。

import cv2
import datetime
import numpy as np
import time
import os
import os.path
import sys

まず、Poke-Controllerのガイドをよく読んでください。

プログラム

ライセンス

(Poke-Controller-Modifiedの関数を一部改変して使用しているため)
MITライセンスとなります。詳細は同梱のLICENSEファイルを参照願います。

CutMeBaby.py

説明

画像ファイルを指定された座標の範囲で切る(Cut)するプログラムです。Cut(切る)Me(ミー)Baby(ベイベー)です。

使用例

$ python.exe .\CutMeBaby.py .\sample.png 993 644 1035 686
Src:  .\sample.png
area_993_644_1035_686
Save file succeeded: area_993_644_1035_686.png
$ python.exe .\CutMeBaby.py .\sample.png 993 644 1035 686 out
Src:  .\sample.png
area_993_644_1035_686
Save file succeeded: out.png
$

引数

  • 引数1:入力ファイル(必須)

  • 引数2~5:順に開始X座標、開始Y座標、終了X座標、終了Y座標(必須)

  • 引数6:出力ファイル(省略可)
    省略した場合はarea_"引数2"_"引数3"_"引数4"_"引数5".pngに出力

このスクリプトのいいところ

  • コマンド1回で簡単に画像を切り抜ける

  • 切り抜きたい箇所がるとき、引数6をとりあえず"out"とか指定しておけば引数2~5で少しずつ座標を調整していける
    座標が決まったら引数6を省略してしまえば、座標がファイル名に使用される

  • WindowsでもLinuxでもとりあえずポケコンがある環境なら動く
    (Linuxならもっとスマートにできるコマンドあるかもしれませんが。。。)

  • ネーミングセ…


matchDataData.py

説明

画像の一致判定を100回行い、最大、最小、平均時間を出力するプログラムです。

使用例

$ python.exe .\matchDataData.py .\sample.png .\out.png 896 480 1280 720
args[1]: .\sample.png
Width:1280, Height:720
area: [896, 480, 1280, 720]
Cnt:100, Match:100, Ave:3.93[ms], Min:0.00[ms], Max:16.19[ms]
$

引数

  • 引数1:一致探索対象となる画像ファイル(必須)

  • 引数5:一致探索される画像ファイル(必須)

  • 引数2~4:順に探索する開始X座標、開始Y座標、終了X座標、終了Y座標(省略可)
    省略した場合は全体が探索対象になります

測定結果

探索画像と探索される対象画像を用意し、いくつか探索範囲を変えてWindowsとRaspberry Piで探索時間を測定してみました。

条件

測定に用いた画像は、CutMeBaby.pyで使用したsample.pngとout.pngになります。探索範囲は以下の通りです。

$$
\begin{array}{r|l|l}
探索範囲& 探索座標&対象\\\hline
\text{1280x720}&範囲指定無し&全画面\\
\text{640x360}&[640, 360, 1280, 720]&画面4分の1\\
\text{320x180}&[960, 540, 1280, 720]&画面16分の1\\
\text{168x168}&[930, 581, 1098, 749]&探索画像16倍相当\\
\text{84x84}&[972, 623, 1056, 707]&探索画像4倍相当
\end{array}
$$

結果

  • Debian 1:6.1.63-1+rpt1 on Raspberry Pi 4B 8GBでの実行結果

$ python matchDataData.py sample.png out.png
args[1]: sample.png
Width:1280, Height:720
area: []
Cnt:100, Match:100, Ave:336.48[ms], Min:335.31[ms], Max:345.71[ms]
$ python matchDataData.py sample.png out.png 640 360 1280 720
args[1]: sample.png
Width:1280, Height:720
area: [640, 360, 1280, 720]
Cnt:100, Match:100, Ave:85.96[ms], Min:85.62[ms], Max:88.00[ms]
python matchDataData.py sample.png out.png 960 540 1280 720
args[1]: sample.png
Width:1280, Height:720
area: [960, 540, 1280, 720]
Cnt:100, Match:100, Ave:20.23[ms], Min:20.01[ms], Max:24.04[ms]
$ python matchDataData.py sample.png out.png 930 581 1098 749
args[1]: sample.png
Width:1280, Height:720
area: [930, 581, 1098, 749]
Cnt:100, Match:100, Ave:6.80[ms], Min:6.67[ms], Max:8.40[ms]
$ python matchDataData.py sample.png out.png 972 623 1056 707
args[1]: sample.png
Width:1280, Height:720
area: [972, 623, 1056, 707]
Cnt:100, Match:100, Ave:1.76[ms], Min:1.71[ms], Max:2.70[ms]

$$
\def\arraystretch{1.5}
\begin{array}{r|r|r|r}
探索範囲
&\hspace{11pt}Ave\hspace{14pt}
&\hspace{11pt}Min\hspace{14pt}
&\hspace{11pt}Max\hspace{14pt}\\\hline
\text{1280x720}&336.48ms&335.31ms&345.71ms\\
\text{640x360}&85.96ms&85.62ms&88.00ms\\
\text{320x180}&20.23ms&20.01ms&24.04ms\\
\text{168x168}&6.80ms&6.67ms&8.40ms\\
\text{84x84}&1.76ms&1.71ms&2.70ms
\end{array}
$$

  • Windows 10 Pro on Macbook Pro(Intel(R) Core(TM) i5-6267U CPU,16GB)での実行結果

$ python.exe .\matchDataData.py .\sample.png .\out.png
args[1]: .\sample.png
Width:1280, Height:720
area: []
Cnt:100, Match:100, Ave:82.86[ms], Min:76.76[ms], Max:122.67[ms]
$ python.exe .\matchDataData.py .\sample.png .\out.png 640 360 1280 720
args[1]: .\sample.png
Width:1280, Height:720
area: [640, 360, 1280, 720]
Cnt:100, Match:100, Ave:21.42[ms], Min:19.91[ms], Max:29.92[ms]
$ python.exe .\matchDataData.py .\sample.png .\out.png 960 540 1280 720
args[1]: .\sample.png
Width:1280, Height:720
area: [960, 540, 1280, 720]
Cnt:100, Match:100, Ave:5.67[ms], Min:4.95[ms], Max:8.94[ms]
$ python.exe .\matchDataData.py .\sample.png .\out.png 930 581 1098 749
args[1]: .\sample.png
Width:1280, Height:720
area: [930, 581, 1098, 749]
Cnt:100, Match:100, Ave:2.47[ms], Min:1.96[ms], Max:5.00[ms]
$ python.exe .\matchDataData.py .\sample.png .\out.png 972 623 1056 707
args[1]: .\sample.png
Width:1280, Height:720
area: [972, 623, 1056, 707]
Cnt:100, Match:100, Ave:0.55[ms], Min:0.00[ms], Max:1.99[ms]

$$
\def\arraystretch{1.5}
\begin{array}{r|r|r|r}
探索範囲
&\hspace{11pt}Ave\hspace{14pt}
&\hspace{11pt}Min\hspace{14pt}
&\hspace{11pt}Max\hspace{14pt}\\\hline
\text{1280x720}&82.86ms&76.76ms&122.67ms\\
\text{640x360}&21.42ms&19.91ms&29.92ms\\
\text{320x180}&5.67ms&4.95ms&8.94ms\\
\text{168x168}&2.47ms&1.96ms&5.00ms\\
\text{84x84}&0.55ms&0.00ms&1.99ms
\end{array}
$$

考察

当たり前ですが、Raspberry PiにしろWindowsにしろ、探索範囲を絞れば絞るほど探索時間が短くなっております。探索に要する時間がどれだけ許容できるかはそのプログラム次第ではありますが、ボタンやスティックの入力のON/OFFを認識するまでの時間が一つの指標になるかと思います。自分の体感でがありますが、ポケコンにしろArduinoにしろ、"ボタンやスティックの入力に最低30~40ms程度ON/OFFが継続されないとNintendo Switch側が認識しない"感があります。

なので、探索時間について個人的な意見で述べると、ポケコンの場合は10ms前後に収まっていれば御の字ではないかと思ってます。

まとめ

なんちゃってで作ってみましたが、地味ーーーーに役立つスクリプトだと思うので一度お試しいただけると幸いです。

ちなみに本スクリプトで切り抜いた画像をポケコンのisContainTemplate()関数で使用する場合、引数cropを座標を指定することになるのですが、あくまで自分の場合は探索画像に対して上下左右に5ずつ余裕を持たせて設定してます。(例えば、切り抜いた画像の範囲が[993, 644, 1035, 686]なら、探索範囲は[988, 639, 1040, 691])

if self.isContainTemplate('out.png', threshold=0.8,
                          use_gray=True, show_value=self.Check_th,
                          crop=[988, 639, 1040, 691]):

あと、短い記事って読みやすくていいですね。さぁ次はどんな記事を書きましょうか?どうする!折部やs……