Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What is going on with this article?
@frameair

screencapに対する考察 - adbでスクリーンキャプチャを取る方法

More than 1 year has passed since last update.

はじめに

Androidのscreencapに関する考察です。
今更?と突っ込みを受けそうな内容ですが……。

背景

Pythonを使い、Androidの画面をadbでキャプチャし、OpenCVのテンプレートマッチング機能やadbを使い、特定の場所を自動的にタップし、その作業を繰り返す、というプログラムを作っていました。

Androidの画面のキャプチャには、adb screencap -p /sdcard/screen.pngの様なコマンドを実行し、取得していたのですが、使用している端末がZenfone 3 Maxという、貧弱なスマホのせいか、画面のキャプチャに時間が掛かっている様に感じました。

そこで高速化をする為の方法を調べてみる事にしました。

結論としては、一定の効果はあったものの、劇的な効果は無い、微妙なものが出来上がりました。
また、最新の高性能のスマートフォンを使った場合には、今回記事にした工夫は、意味がないかも知れません。

とはいえ、得たものもあったので、自分の備忘録としてメモ書きです。

検証環境

OS : Windows 10 Version 1809(October 2018 Update)
Python : Anaconda, Python Version 3.7.0

CPU : Corei7-7700 3.60GHz
GPU : 無し(Intel Graphics)

Android Device : ASUS Zenfone 3 Max - ZC520TL

Linux、および、MacOSでは未検証です。検証の予定もありません。

adbでスクリーンキャプチャをとる方法

adbでスクリーンキャプチャを撮るには、下記のコマンドを実行します。
下記のように説明されている事が一般的な様です。

adbを使ったスクリーンキャプチャ
adb shell screencap -p /sdcard/screen.png
adb pull /sdcard/screen.png
adb shell rm /sdcard/screen.png

本体の/sdcard/screen.pngに、一度キャプチャ結果を書き出して、pullコマンドでファイルをPCへ取り出し、rmコマンドでファイルを消す、という手順です。

リダイレクトによるスクリーンキャプチャ

実は上記コマンド、下記でもOKの様です。
※Windows環境でしか確認していませんが……

ただし、cmd.exeで実行して下さい。Powershellだと失敗します。(リダイレクトがUnicodeで出力されるから)
Powershellで実行する方法は、面倒なので調べませんでした。

リダイレクトを使用
adb exec-out screencap -p > screen.png

exec-outオプションとは何かが、実は良く解っていません。
adbコマンドの説明を見たのですが、exec-outについて、何も書かれていないようです。

どうも、Binary Dataをそのまま出力する為のコマンドの様なのですが、それもはっきりしません。
更に言えば、古いVersionのadbでは動作しない様です

この情報について、参考にしたサイトは下記の通りです。
下記サイトにこのやり方が書かれていたので、動かしてみたら動いた、という次第です。

Note to Self: Fast Android Screen Capture
http://blog.macuyiko.com/post/2017/note-to-self-fast-android-screen-capture.html

因みに、下記はだめです。Linuxでは動作しますが、Windowsで失敗します。
原因は改行コードの取り扱いです。下記コマンドだと、改行コードの変換(LF→CR+LF)が発生するからです。

Windowsでは動作しない
adb shell screencap -p > screen.png

改行コードを変換(CR+LF→LFに変換)すれば、リダイレクトによって、スクリーンキャプチャを撮る事ができます。

(改行コードを変換する何か)を作れば、下記コマンドで実行できる筈です。

Windowsで動作させる例
adb shell screencap -p | (改行コードを変換する何か) > screen.png

-pオプションをつけない

ここからが本題です。

adbの-pオプションは、png形式で出力する事を指定するオプションです。
また、-pオプションを明示的に記載しなくとも、下記の様に出力ファイルの拡張子を「png」にすれば、-pオプションを付加したのと同じことになります。

PNG形式で出力される
adb shell screencap /sdcard/screen.png
adb pull /sdcard/screen.png

では、-pオプションを付加しないと、何が出力されるのでしょうか。

-pオプションをつけない
adb exec-out screencap > result.raw

調べてみた所、下記のフォーマットを持った、Rawデータ(1ピクセル毎にRGBAが記載されたデータ)が出力されます。

stackoverflow
https://stackoverflow.com/questions/22034959/what-format-does-adb-screencap-sdcard-screenshot-raw-produce-without-p-f

場所 バイト数 説明
1~4byte 4byte width
5~8byte 4byte height
9~12byte 4byte pixelformat(今回は無視しました)
13byte~ - 画素。RGBA順に並んでおり、4byteで1ピクセル。本来はpixelformatに従って並んでいる筈。

Rawデータを使うメリットは、(恐らく)スピードの速さだと思います。

私の使っているスマホは、Zenfone 3 Maxという、性能が貧弱なスマホです。
そのため、PNG形式で出力しようとすると、本体のエンコード処理で、かなり時間が掛かってしまうのでは、と考えました。

RawデータをOpenCV形式に変換するプログラム

今回はPythonを使い、OpenCVで使用できるプログラムを作ってみました。以下がそのコードです。

動作としては、

  • adb exec-out screencapを実行。RawDataを取得。
  • Rawデータのフォーマットに従い、1~12byte目の情報を取得。高さと幅を求める。
  • numpyデータに変換
  • 配列の形状を変換(RGBAを要素として、高さ×幅の行列を作る)
  • 要素を入れ替える(OpenCVはBGRAの順序なので)
  • Alpha値を削除する

という手順です。

import subprocess
import numpy
import cv2

def capture_screen_1():
    '''
    スクリーンキャプチャを取るための関数。Rawデータを処理

    Returns
    ----------
    img : opencv Mat
        OpenCV形式のイメージ

    '''
    result = []

    # adb exec-out screencap
    try:
        result = subprocess.check_output(['adb', 'exec-out', 'screencap'])
    except:
        return None

    # wigth, heightを取得。
    wigth = int.from_bytes(result[0:4], 'little')
    height = int.from_bytes(result[4:8], 'little')
    _ = int.from_bytes(result[8:12], 'little')

    # ここのCopyは必須。そうでないと、編集が出来ない
    tmp = numpy.frombuffer(result[12:], numpy.uint8, -1, 0).copy() 

    # 配列の形状変換。
    # 1つの要素がRGBAである、height * widthの行列を作る。
    img = numpy.reshape(tmp, (height, wigth, 4))    

    # 要素入れ替え。
    # RawDataはRGB、OpenCVはBGRなので、0番目の要素と、2番目の要素を入れ替える必要がある。
    b = img[:, :, 0].copy()               # ここのコピーも必須
    img[:, :, 0] = img[:, :, 2]
    img[:, :, 2] = b

    # alpha値を削除。alpha値が必要な場合は、下記行は消しても良いかも?
    img2 = numpy.delete(img, 3, 2)

    return img2

# 検証用コード
if __name__ == "__main__":
    img = capture_screen_1()

    # 表示させる部分。キーを押すと終了する。
    cv2.namedWindow('window')
    cv2.imshow('window', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

実行速度の検証

他に下記のプログラムを作り、実行速度を計測する事にしました。

  • (先ほど説明した)Rawデータを扱うプログラム
  • 'adb exec-out screencap -p`を実行し、OpenCV形式に変換するプログラム
  • PNG形式のファイルを本体に保存し、pullコマンドでPNGファイルを取り出し、OpenCVに形式に変換するプログラム

実行速度の計測は、各プログラムを20回ずつ実行し、平均値を取っています。
試行回数は非常に少ないと思いますが、傾向を掴むのが目的なので、まぁ良いかな、と。

プログラムを以下に記載します。少し長いです。

import subprocess
import numpy
import cv2
import time
import os

def capture_screen_1():
    '''
    スクリーンキャプチャを取るための関数。Rawデータを処理

    Returns
    ----------
    img : opencv Mat
        OpenCV形式のイメージ

    '''
    result = []

    # adb exec-out screencap
    try:
        result = subprocess.check_output(['adb', 'exec-out', 'screencap'])
    except:
        return None

    # wigth, heightを取得。
    wigth = int.from_bytes(result[0:4], 'little')
    height = int.from_bytes(result[4:8], 'little')
    _ = int.from_bytes(result[8:12], 'little')

    # ここのCopyは必須。そうでないと、編集が出来ない
    tmp = numpy.frombuffer(result[12:], numpy.uint8, -1, 0).copy() 

    # 配列の形状変換。
    # 1つの要素がRGBAである、height * widthの行列を作る。
    img = numpy.reshape(tmp, (height, wigth, 4))    

    # 要素入れ替え。
    # RawDataはRGB、OpenCVはBGRなので、0番目の要素と、2番目の要素を入れ替える必要がある。
    b = img[:, :, 0].copy()               # ここのコピーも必須
    img[:, :, 0] = img[:, :, 2]
    img[:, :, 2] = b

    # alpha値を削除。alpha値が必要な場合は、下記行は消しても良いかも?
    img2 = numpy.delete(img, 3, 2)

    return img2

def capture_screen_2():
    '''
    スクリーンキャプチャを取るための関数。PNG形式で取得。

    Returns
    ----------
    img : opencv Mat
        OpenCV形式のイメージ

    '''
    # adb exec-out screencap
    try:
        result = subprocess.check_output(['adb', 'exec-out', 'screencap', '-p'])
    except:
        return None

    # imdecodeで読み込み
    return cv2.imdecode(numpy.frombuffer(result, numpy.uint8), cv2.IMREAD_COLOR)


def capture_screen_3():
    '''
    スクリーンキャプチャを取るための関数。PNG形式で取得。PULLでとってくる。

    Returns
    ----------
    img : opencv Mat
        OpenCV形式のイメージ

    '''
    # adb shell screencap -p /sdcard/screen.png
    # adb pull /sdcard/screen.png result.png
    # adb shell rm /sdcard/screen.png
    try:
        subprocess.check_output(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png'])
        subprocess.check_output(['adb', 'pull', '/sdcard/screen.png', 'result.png'])
        subprocess.check_output(['adb', 'shell', 'rm', '/sdcard/screen.png'])
    except:
        return None

    # result.pngをオープン。すべて読み込む。
    with open('result.png', 'rb') as f:
        result = f.read()

    # result.pngを削除
    os.remove('result.png')

    # imdecodeで読み込み
    return cv2.imdecode(numpy.frombuffer(result, numpy.uint8), cv2.IMREAD_COLOR)


def benchmark_test(func):
    '''
    ベンチマーク
    '''
    CAP_COUNT = 20

    time_total = 0

    for _ in range(CAP_COUNT):
        start = time.time()
        func()
        result_time = time.time() - start
        time_total += result_time

    result_time = time_total / CAP_COUNT
    print ("{0} time:{1}".format(func.__name__, result_time) + "[sec]")

if __name__ == "__main__":     
    benchmark_test(capture_screen_1)
    benchmark_test(capture_screen_2)
    benchmark_test(capture_screen_3)

実行結果は下記の通り。検証環境は、一番最初に記した通りです。

上から、RawData、PNG(Redirect)、PNG(Pullコマンド使用)の順です。
Rawデータを扱うのが一番早く、pullコマンドを使うと遅くなるのは確かですが、その差は3倍程度です。

ある程度の効果はありましたが、そんなに高速化されているとは言えない様な気もします。微妙な結果と言ったところでしょうか。

実行結果、#以降は後で追加したコメント
capture_screen_1 time:1.0686011910438538[sec] # Raw Data (exec-out)
capture_screen_2 time:2.1270569443702696[sec] # PNG (exec-out -p)
capture_screen_3 time:3.3975932598114014[sec] # PNG (exec-out -p /sdcard/screen.png; adb pull ...)

結論

今回の結果を見る限り、素直にPNGフォーマットで出力しても、そんなに影響は無い様に思います。
とはいえ、2秒くらいは改善できているので、私が作成したプログラムでは、RawDataでキャプチャする方法を採用する事にしました。

更にスクリーンキャプチャを高速化する為には、minicapなど、スクリーンキャプチャを高速化する為のライブラリがある様です。
ただし、名前を調べただけで、詳しい内容までは調べておりません。

また、性能が低いスマホだからある程度の効果があったのであって、高性能なスマホでは、そんなに速度は変わらないのでは?と、考えています。
逆に、画素数が増える分、PNG形式の方が早いのかも知れません。

残念ながら、高性能なスマホを私は持っていません。その為、機種ごとの検証が出来ないのが残念です。
今後も高性能なスマホを買う予定はありませんので、この検証は実施せずに終わりそうです。

参考文献

Note to Self: Fast Android Screen Capture
http://blog.macuyiko.com/post/2017/note-to-self-fast-android-screen-capture.html

stackoverflow
https://stackoverflow.com/questions/22034959/what-format-does-adb-screencap-sdcard-screenshot-raw-produce-without-p-f

Androidの画面キャプチャを高速化するための試行錯誤
https://qiita.com/setsulla/items/14457accded130e971d2

8
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
frameair
情報工学を学んでいました。年齢は若くは無いです。 昔は派遣でエンジニアの真似事(LSI設計、工場の品質管理等)をしていました。 ある時発作で倒れて、2年半ほど引きこもり。 その後、就労継続支援事業所に通い、今は障がい者雇用で役所の臨時職員の仕事をしています。 精神科に通院中。広汎性発達障害、双極性障害、だそうです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
8
Help us understand the problem. What is going on with this article?