Saltar al contenido

Python Obtiene el valor de píxeles de la pantalla en OS X

Te recomendamos que revises esta resolución en un ambiente controlado antes de enviarlo a producción, un saludo.

Solución:

Una pequeña mejora, pero usando la opción de compresión TIFF para screencapture es un poco más rápido:

$ 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

Esto tiene mucha sobrecarga, como usted dice (la creación de subprocesos, escritura / lectura de disco, compresión / descompresión).

En su lugar, puede usar PyObjC para capturar la pantalla usando CGWindowListCreateImage. Descubrí que tomó alrededor de 70 ms (~ 14 fps) capturar una pantalla de 1680×1050 píxeles y tener los valores accesibles en la memoria

Algunas notas al azar:

  • Importando el Quartz.CoreGraphics El módulo es la parte más lenta, aproximadamente 1 segundo. Lo mismo es true para importar la mayoría de los módulos PyObjC. Es poco probable que importe en este caso, pero para procesos de corta duración, es mejor escribir la herramienta en ObjC.
  • Especificar una región más pequeña es un poco más rápido, pero no enormemente (~ 40ms para un bloque de 100x100px, ~ 70ms para 1680×1050). La mayor parte del tiempo parece dedicarse solo al CGDataProviderCopyData llamar – Me pregunto si hay una manera de acceder a los datos directamente, ya que no necesitamos modificarlos.
  • los ScreenPixel.pixel La función es bastante rápida, pero el acceso a una gran cantidad de píxeles sigue siendo lento (ya que 0.01ms * 1650*1050 es de unos 17 segundos): si necesita acceder a muchos píxeles, probablemente sea más rápido struct.unpack_from todos a la vez.

Aquí está el código:

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())

Encontré esta publicación mientras buscaba una solución para obtener una captura de pantalla en Mac OS X utilizada para el procesamiento en tiempo real. Intenté usar ImageGrab de PIL como se sugiere en algunas otras publicaciones, pero no pude obtener los datos lo suficientemente rápido (con solo alrededor de 0.5 fps).

¡La respuesta https://stackoverflow.com/a/13024603/3322123 en esta publicación para usar PyObjC me salvó el día! ¡Gracias @dbr!

Sin embargo, mi tarea requiere obtener todos los valores de píxeles en lugar de un solo píxel, y también para comentar la tercera nota de @dbr, agregué un nuevo método en esta clase para obtener una imagen completa, en caso de que alguien más pudiera necesitarlo. .

Los datos de la imagen se devuelven como un número array con una dimensión de (altura, ancho, 3), que se puede usar directamente para el posprocesamiento en numpy o opencv, etc. Obtener valores de píxeles individuales también se vuelve bastante trivial usando la indexación de numpy.

Probé el código con una captura de pantalla de 1600 x 1000: obtener los datos usando capture () tomó ~ 30 ms y convertirlos a un np array getimage () toma solo ~ 50 ms en mi Macbook. Así que ahora tengo> 10 fps e incluso más rápido para regiones más pequeñas.

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)

tenga en cuenta que desecho el canal “alfa” del canal BGRA 4.

Todo esto fue tan útil que tuve que volver para comentar / sin embargo, no tengo la reputación … Sin embargo, tengo un código de muestra de una combinación de las respuestas anteriores para una captura de pantalla / guardado rápido como un rayo gracias a @dbr y @qqg!

import time
import numpy as np
from scipy.misc import imsave
import Quartz.CoreGraphics as CG

image = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)

prov = CG.CGImageGetDataProvider(image)
_data = CG.CGDataProviderCopyData(prov)

width = CG.CGImageGetWidth(image)
height = CG.CGImageGetHeight(image)

imgdata=np.fromstring(_data,dtype=np.uint8).reshape(len(_data)/4,4)
numpy_img = imgdata[:width*height,:-1].reshape(height,width,3)
imsave('test_fast.png', numpy_img)

Aquí puedes ver las reseñas y valoraciones de los usuarios

Si tienes algún recelo y disposición de modernizar nuestro tutorial eres capaz de dejar una anotación y con gusto lo interpretaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *