10

OS X 10.8.2 上の Python で自動化されたゲーム ボットを構築している最中であり、Python GUI の自動化を調査している最中に autopy を発見しました。マウス操作 API は素晴らしいですが、スクリーン キャプチャ メソッドは非推奨の OpenGL メソッドに依存しているようです...

OS X でピクセルの色の値を取得する効率的な方法はありますか? 私が今考えることができる唯一の方法は使用することos.system("screencapture foo.png")ですが、非常に迅速にポーリングするため、プロセスには不要なオーバーヘッドがあるようです。

4

3 に答える 3

21

小さな改善ですが、TIFF 圧縮オプションを使用するscreencaptureと少し速くなります。

$ time screencapture -t png /tmp/test.png
real        0m0.235s
user        0m0.191s
sys         0m0.016s
$ time screencapture -t tiff /tmp/test.tiff
real        0m0.079s
user        0m0.028s
sys         0m0.026s

あなたが言うように、これには多くのオーバーヘッドがあります(サブプロセスの作成、ディスクからの書き込み/読み取り、圧縮/解凍)。

代わりに、PyObjC を使用して、CGWindowListCreateImage. 1680x1050 ピクセルの画面をキャプチャし、メモリ内で値にアクセスできるようにするのに約 70ms (~14fps) かかることがわかりました。

いくつかのランダムなメモ:

  • Quartz.CoreGraphicsモジュールのインポートは最も遅い部分で、約 1 秒かかります。ほとんどの PyObjC モジュールをインポートする場合も同様です。この場合は問題にならない可能性がありますが、短期間のプロセスの場合は、ツールを ObjC で記述した方がよい場合があります。
  • 小さい領域を指定すると少し速くなりますが、それほど大きくはありません (100x100 ピクセルのブロックで約 40 ミリ秒、1680x1050 で約 70 ミリ秒)。ほとんどの時間はCGDataProviderCopyData呼び出しだけに費やされているようです - データを変更する必要がないので、データに直接アクセスする方法があるのだろうか?
  • 関数は非常に高速ですが、ScreenPixel.pixel多数のピクセルへのアクセスは依然として低速です (約 17 秒であるため)。多数のピクセルにアクセスする必要がある場合は、一度にすべて0.01ms * 1650*1050アクセスする方がおそらく高速です。struct.unpack_from

コードは次のとおりです。

import time
import struct

import Quartz.CoreGraphics as CG


class ScreenPixel(object):
    """Captures the screen using CoreGraphics, and provides access to
    the pixel values.
    """

    def capture(self, region = None):
        """region should be a CGRect, something like:

        >>> import Quartz.CoreGraphics as CG
        >>> region = CG.CGRectMake(0, 0, 100, 100)
        >>> sp = ScreenPixel()
        >>> sp.capture(region=region)

        The default region is CG.CGRectInfinite (captures the full screen)
        """

        if region is None:
            region = CG.CGRectInfinite
        else:
            # TODO: Odd widths cause the image to warp. This is likely
            # caused by offset calculation in ScreenPixel.pixel, and
            # could could modified to allow odd-widths
            if region.size.width % 2 > 0:
                emsg = "Capture region width should be even (was %s)" % (
                    region.size.width)
                raise ValueError(emsg)

        # Create screenshot as CGImage
        image = CG.CGWindowListCreateImage(
            region,
            CG.kCGWindowListOptionOnScreenOnly,
            CG.kCGNullWindowID,
            CG.kCGWindowImageDefault)

        # Intermediate step, get pixel data as CGDataProvider
        prov = CG.CGImageGetDataProvider(image)

        # Copy data out of CGDataProvider, becomes string of bytes
        self._data = CG.CGDataProviderCopyData(prov)

        # Get width/height of image
        self.width = CG.CGImageGetWidth(image)
        self.height = CG.CGImageGetHeight(image)

    def pixel(self, x, y):
        """Get pixel value at given (x,y) screen coordinates

        Must call capture first.
        """

        # Pixel data is unsigned char (8bit unsigned integer),
        # and there are for (blue,green,red,alpha)
        data_format = "BBBB"

        # Calculate offset, based on
        # http://www.markj.net/iphone-uiimage-pixel-color/
        offset = 4 * ((self.width*int(round(y))) + int(round(x)))

        # Unpack data from string into Python'y integers
        b, g, r, a = struct.unpack_from(data_format, self._data, offset=offset)

        # Return BGRA as RGBA
        return (r, g, b, a)


if __name__ == '__main__':
    # Timer helper-function
    import contextlib

    @contextlib.contextmanager
    def timer(msg):
        start = time.time()
        yield
        end = time.time()
        print "%s: %.02fms" % (msg, (end-start)*1000)


    # Example usage
    sp = ScreenPixel()

    with timer("Capture"):
        # Take screenshot (takes about 70ms for me)
        sp.capture()

    with timer("Query"):
        # Get pixel value (takes about 0.01ms)
        print sp.width, sp.height
        print sp.pixel(0, 0)


    # To verify screen-cap code is correct, save all pixels to PNG,
    # using http://the.taoofmac.com/space/projects/PNGCanvas

    from pngcanvas import PNGCanvas
    c = PNGCanvas(sp.width, sp.height)
    for x in range(sp.width):
        for y in range(sp.height):
            c.point(x, y, color = sp.pixel(x, y))

    with open("test.png", "wb") as f:
        f.write(c.dump())
于 2012-10-23T06:24:39.397 に答える
2

リアルタイム処理に使用される Mac OS X でスクリーンショットを取得するソリューションを探しているときに、この投稿に出会いました。他の投稿で提案されているように、PIL の ImageGrab を使用してみましたが、十分な速度でデータを取得できませんでした (約 0.5 fps のみ)。

この投稿の答えhttps://stackoverflow.com/a/13024603/3322123 PyObjC を使用することで、私の一日が救われました! ありがとう@dbr!

ただし、私のタスクでは、1 つのピクセルだけでなく、すべてのピクセル値を取得する必要があります。また、@dbr による 3 番目のメモにコメントするために、このクラスに新しいメソッドを追加して、他の誰かが必要とする場合に備えて、完全な画像を取得する必要があります。 .

画像データは、(高さ、幅、3) の次元を持つ numpy 配列として返されます。これは、numpy や opencv などでの後処理に直接使用できます。そこから個々のピクセル値を取得することも、numpy インデックスを使用すると非常に簡単になります。

私は 1600 x 1000 のスクリーンショットでコードをテストしました。私の Macbook では、capture() を使用してデータを取得するのに約 30 ミリ秒かかり、それを np 配列 getimage() に変換するのに約 50 ミリ秒しかかかりませんでした。そのため、現在は 10 fps を超えており、小さな領域ではさらに高速です。

import numpy as np

def getimage(self):
    imgdata=np.fromstring(self._data,dtype=np.uint8).reshape(len(self._data)/4,4)
    return imgdata[:self.width*self.height,:-1].reshape(self.height,self.width,3)

注意: BGRA 4 チャネルから「アルファ」チャネルを破棄します。

于 2016-02-16T04:49:31.273 に答える