薬剤師のプログラミング学習日記

プログラミングやコンピュータに関する記事を書いていきます

MPラーニング動画中のタップをPythonで自動化する

認定薬剤師の単位を取ることができるMPラーニングの動画を私はスマホで見ているのですが、続きを視聴するために1コンテンツ中に何度もタップを要求されます。(短いときには数十秒おきに!)
今回の記事では、このスマホのタップをPythonで自動化して、最後のテストの場面まで人の手によるタップなしで見ることができるよう試みました。


記事の趣旨について

そもそも、なぜ約30分の動画でわざわざ何度もタップさせるようにしているのかについては、だいたいの想像はつきます。講義を受けるのに居眠りしながらや友人に代返をしてもらっても、その講義の単位は認められませんというのと同じことでしょう。

私はこのコンテンツを見ながら料理をすることが多いのですが、間に小テストがあるわけでもないのに、動画1再生の間に何回もタップしなければならないというのは煩わしく思います*1。やはり、動画は止まることなく最後まで見たい、という欲求を持っていました。
ちょっと言い訳がましいかもしれませんが、チート(ずるや不正)目的ではなく、快適さを追い求めた結果ゆえに、という目で読んでいただければ幸いです。

環境と概要

・PC Windows10
・アンドロイドスマホ
動画の視聴はスマホで行います。MPラーニングのコンテンツはPCのブラウザ上でも見れるので、似たようなことはできるかもしれません。

今回やったことのほとんどの部分はこちらの記事を参考にさせてもらいました。
noitalog.tokyo
Androidのゲームを自動操作する方法という記事ですが、スマホ画面中の特定の場所(ボタン)をプログラムにタップさせるという意味では今回やることと同じです。「特定の場所」の決定には、OpenCVのテンプレートマッチングを使っていますね。
私も以前記事に書いたことがあります。
www.yakupro.info
なので、Pythonのインストール等の環境の構築部分は2つ上のリンク先をご参照いただければと思いますが、事前準備として何をどうするかということだけざっと箇条書きにしておきます。

  • Pythonのインストール
  • android-auto-play-opencvのインストール
  • Android Debug Bridge(adb)のダウンロード
  • Android自動操作ライブラリを使ったサンプルコードのダウンロード
  • アンドロイドスマホをPCに接続して動作確認

PC-スマホ接続にはケーブルを用いました。スマホ側はUSB Type-Cが多いとは思いますが、持っているPCは使いたいポートのUSBの形状を確認しておきましょう。(Type-C - Type-Cケーブルなのか、Type-C - Type-A(昔からあるやつ)なのか)

私は以下のようなフォルダ構成にしました。
└── work
   ├── android-auto-play-opencv-master
   │   ├── pypi
   │   ├── template
   │   ├── サンプル
   │   └── Main.py
   └── platform-tools


自動タップのためのテンプレート画像の作成

プログラムにタップさせるボタンの画像をテンプレートとして作成します。
最初は、動画が止まった画面を別のスマホで写真を撮り、その画像を加工して作成しましたが、それではうまくタップさせることができませんでした。
そこで、タップが必要な画面をキャプチャして保存して、その画像からトリミングするという方法で作成してやります。
動画が『次の学習へ▷』の画面で止まったら、次のコードを実行します。

import android_auto_play_opencv as am
import datetime # 日時を取得するために必要
 
adbpath = '..\\platform-tools\\'
 
def main():
    aapo = am.AapoManager(adbpath)
    # 画面キャプチャ
    aapo.screencap()
    # キャプチャ画像を保存
    aapo.imgSave('img/image_' + datetime.datetime.now().strftime('%H%M%S') + '.png')
 
if __name__ == '__main__':
    main()

pyファイルと同じ階層にimgフォルダができ中にスクリーンショット画像が保存されます。
続いて直後の「▷」がある画面でも同じコードを実行します。
次のような2つの画像が得られました。


これらの画像から、タップさせたい部分をペイントでトリミングして保存します。

しかし、この「▷」のマッチングがなかなか曲者でした。
この「▷」は背景に文字が重なっていたり色が付いていることも多く、テンプレート画像とは別物と認識されてしまい、ちょっとした違いでタップできないことが多かったのです。

自動タップの成功率を上げる工夫

上の画像を見ていただければわかりますが、「▷」のタップを必要とする画面上では他に紛らわしいものはありません。仮に全然関係ない部分を間違えてタップさせても困ることはないので、「▷」ぽいものはマッチさせようという方針で、次のような工夫をしてみることにしました。

  1. 極力背景が入らないようにテンプレート画像を作成する
  2. テンプレートマッチング結果の閾値を下げる(精度を緩くする)

1に関しては、三角形の頂点の部分をなくした次のような画像にしました。

こんなので大丈夫か、というくらいギリギリまで背景が映らないようにトリミングしました。

2については、 pypi/android_auto_play_opencv/MatchTemplateLib.pyファイルの上から15行目にある類似度の設定部分(閾値)を変更します。
私は次のように変更しました。

    #類似度の設定(0~1)
    THRESHOLD = 0.3    # 0.8から変更

正直、この部分についてはどの程度の効果があったのかはわかりません。
ほかには、cv2.matchTemplate関数の類似度の計算方法を変えてみるというのもありかもしれません。

実行結果

とりあえずここまでで、私のタップを必要とすることなく動画が進むようにはなりました。それでも、「▷」のところではたまに私のタップが必要となります。動画(講義)によってもかなり違いがみられるようです。

コードについては、ほぼリンク先のもののままですが、少し変更を加えています。

  • ずっとタップをし続けなければならないようなものではないので、ループ中は5秒間処理を待機させる。
  • 自動タップをすればどのボタンをタップしたかをPC画面上に表示させる。
  • プログラム開始からの経過時間(分)を表示させる。
import android_auto_play_opencv as am
import time
 
adbpath = '..\\platform-tools\\'
 
def main():
    aapo = am.AapoManager(adbpath)
    # ループの処理時間を計測
    start_time = time.time()
 
    while True:
        # 画面キャプチャ
        aapo.screencap()
 
        if aapo.touchImg('./template/tap1.png'):
            print('"次の学習へ"をタップ')
        if aapo.touchImg('./template/tap2.png'):
            print('">"をタップ')
        if aapo.touchImg('./template/tap3.png'):
            print('"テストへ"をタップ')
        aapo.sleep(5)
        elapsed_time = int(time.time() - start_time)
        print('経過時間(min): ', (elapsed_time % 3600) // 60)
 
if __name__ == '__main__':
    main()

プログラムを動かすと、「画面キャプチャ」という文字列やタップしたときには画像発見や座標の情報も表示されますが、必要なければpypi/android_auto_play_opencv/AapoManager.pyの該当する部分を消してやればいいと思います。

他にコードの改良をするとしたら、

  • タップされずに同じ場面で止まっていたら音を出して知らせてくれる
  • 「テストへ」がタップされたら音を出して知らせてくれる

あたりを思いつきましたが、ちゃんと集中して動画を見ていれば、そんなの必要ありませんね。ちょっと趣旨が変わってしまうので自重しました。

*1:たとえ、キッチンで椅子に腰かけ煮込みを待っている間で手が空いていても