2012-10-19 12 views
10

我正在使用OS X 10.8.2在Python中構建自動化遊戲機器人程序,並且在研究Python GUI自動化的過程中發現了autopy。鼠標操作的API是偉大的,但似乎屏幕捕獲方法依賴於不推薦的OpenGL方法...Python在OS X中獲取屏幕像素值

是否有任何有效的方法獲取OS X中的像素的顏色值?我現在能想到的唯一方法是使用os.system("screencapture foo.png"),但這個過程似乎沒有必要開銷,因爲我將很快進行輪詢。

+0

寧靜的話題;你在製作一個機器人遊戲? – tMC

+0

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

+0

Bejeweled Blitz,它是一個AI項目課程。 如果您查看已棄用函數的源代碼,autopy中的所有screengrab函數都會返回黑色。 – itsachen

回答

15

小的改進,但使用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來捕獲屏幕。我發現,花了大約70毫秒(〜14fps)捕捉1680×1050像素的屏幕,並且在內存

一些隨機的筆記訪問的值:

  • 導入Quartz.CoreGraphics模塊是最慢的部分,約1第二。導入大部分PyObjC模塊也是如此。在這種情況下不太可能出現問題,但對於短暫的過程,您可能會更好地在ObjC中編寫工具。指定較小的區域會更快一些,但不是很大(對於100x100像素塊,約爲40毫秒,對於1680x1050約爲70毫秒) 。大部分時間似乎只花在CGDataProviderCopyData調用 - 我想知道是否有方法直接訪問數據,因爲我們不需要修改它?
  • ScreenPixel.pixel功能非常快,但訪問大量的像素仍然很慢(因爲0.01ms * 1650*1050大約17秒) - 如果您需要訪問大量的像素,可能會更快,他們都在一起去。

下面的代碼:

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

[寫了一篇博客文章](http://neverfear.org/blog/view/156/OS_X_Screen_capture_from_Python_PyObjC)更詳細地描述了代碼 – dbr

+0

真棒解決方法!太棒的博客文章了。 – itsachen

+0

您是否知道是否有簡單的方法來獲取縮減截圖?像CoreGraphics標誌或什麼的?這對於像在屏幕上查找精靈位置這樣的東西很有用。 –

1

我碰到這個職位來到同時尋找解決的辦法得到的截屏,用於實時處理的Mac OS X。我曾嘗試使用PIL中的ImageGrab,正如其他一些帖子中所建議的,但無法獲得足夠快的數據(僅約0.5 fps)。

答案https://stackoverflow.com/a/13024603/3322123在這篇文章中使用PyObjC救了我一天!謝謝@dbr!

但是,我的任務需要獲取所有像素值而不是單個像素,並且還要通過@dbr評論第三個註釋,我在該類中添加了一個新方法以獲取完整圖像,以防萬一別的可能需要它。

圖像數據以尺寸爲(高度,寬度,3)的numpy數組形式返回,可以直接用於numpy或opencv等後期處理...從中獲取單個像素值也變得非常簡單numpy索引。我測試了1600 x 1000屏幕截圖的代碼 - 使用capture()獲取數據約30 ms並將其轉換爲np數組getimage()在我的Macbook上只需要約50 ms。所以現在我有> 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通道的「alpha」通道。