2012-10-19 14 views
10

Jestem w trakcie budowania botów automatycznych w Pythonie na OS X 10.8.2 iw trakcie badania automatyzacji GUI Pythona odkryłem autopoprawę. Interfejs API do manipulowania myszą jest świetny, ale wydaje się, że metody przechwytywania ekranu opierają się na przestarzałych metodach OpenGL ...Python Uzyskaj wartość pikseli ekranowych w OS X

Czy istnieją skuteczne metody uzyskiwania wartości koloru piksela w systemie OS X? Jedynym sposobem, jaki mogę teraz wymyślić, jest użycie os.system("screencapture foo.png"), ale proces wydaje się mieć niepotrzebny narzut, ponieważ będę bardzo szybko sondować.

+0

raczej nie na temat; w jaką grę budujesz bota? – tMC

+0

'autopy.color.hex_to_rgb (autopy.screen.get_color (1, 1))'? – tMC

+0

Bejeweled Blitz, dla klasy projektu AI. Wszystkie funkcje ekranu autoportretu zwracają kolor czarny, jeśli przyjrzeć się źródłu, jest tam mnóstwo przestarzałych funkcji. – itsachen

Odpowiedz

15

Niewielka poprawa, ale używając opcji kompresji TIFF dla screencapture jest nieco szybciej:

$ 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 

ten ma wiele napowietrznych, jak mówisz (tworzenie podproces, pisanie/czytanie z dysku kompresowanie/dekompresowanie).

Zamiast tego można użyć PyObjC do przechwytywania ekranu przy użyciu CGWindowListCreateImage. Znalazłem zajęło to około 70ms (~ 14fps) do przechwytywania ekranu w pikselach 1680x1050, i mają wartości dostępnych w pamięci

Kilka losowych Uwagi:

  • Importowanie moduł Quartz.CoreGraphics jest najwolniejsza część, około 1 druga. To samo dotyczy importowania większości modułów PyObjC. W tym przypadku mało prawdopodobne, ale w przypadku krótkotrwałych procesów lepiej pisać narzędzie w ObjC
  • Podanie mniejszego obszaru jest nieco szybsze, ale niezbyt duże (~ 40ms dla bloku 100x100px, ~ 70ms dla 1680x1050) . Wydaje się, że większość czasu spędza się właśnie w rozmowie telefonicznej CGDataProviderCopyData - Zastanawiam się, czy istnieje sposób bezpośredniego dostępu do danych, ponieważ nie trzeba go modyfikować?
  • Funkcja ScreenPixel.pixel jest dość szybka, ale dostęp do dużej liczby pikseli jest nadal powolny (ponieważ 0.01ms * 1650*1050 ma około 17 sekund) - jeśli potrzebujesz dostępu do wielu pikseli, prawdopodobnie szybciej do struct.unpack_from je wszystkie za jednym razem.

Oto kod:

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

[Napisał post na blogu] (http://neverfear.org/blog/view/156/OS_X_Screen_capture_from_Python_PyObjC) z nieco bardziej rozbudowanym opisem kodu – dbr

+0

Niesamowite obejście! Świetne pisanie postów na blogu. – itsachen

+0

Czy wiesz, czy istnieje prosty sposób na pobranie zrzutu ekranu z próbką? Jak flaga CoreGraphics czy coś takiego? Byłoby to przydatne w przypadku takich rzeczy jak znalezienie lokalizacji ikonki na ekranie. –

1

natknąłem tego postu podczas poszukiwania rozwiązania, aby uzyskać zrzut ekranu w systemie Mac OS X służącym do przetwarzania w czasie rzeczywistym. Próbowałem używać ImageGrab z PIL, jak sugerowano w niektórych innych postach, ale nie mogłem wystarczająco szybko uzyskać danych (tylko około 0,5 fps).

Odpowiedź https://stackoverflow.com/a/13024603/3322123 w tym poście na wykorzystanie PyObjC uratowała mój dzień! Dzięki @dbr!

Jednak moje zadanie wymaga uzyskania wszystkich wartości pikseli zamiast pojedynczego piksela, a także komentarza do trzeciej nuty przez @dbr, dodałem nową metodę w tej klasie, aby uzyskać pełny obraz, na wypadek gdyby ktoś Inaczej może tego potrzebować.

Dane obrazu są zwracane jako tablica numpy o wymiarach (height, width, 3), które mogą być bezpośrednio użyte do przetwarzania końcowego w numpy lub opencv itd ... Uzyskiwanie z nich wartości pikseli również staje się dość banalne numpy indeksowanie.

Przetestowałem kod ze zrzutem ekranu 1600 x 1000 - pobieranie danych przy użyciu funkcji przechwytywania() trwało ~ 30 ms, a konwersja do tablicy np. Getimage() zajmuje tylko ~ 50 ms na moim MacBooku. Teraz mam> 10 fps, a jeszcze szybciej dla mniejszych regionów.

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) 

uwaga Wyrzucam kanał "alpha" z kanału BGRA 4.