使用 Python 解析像素数据的最有效/最快捷的方法?

Dan*_*haw 4 python image-processing pixels python-imaging-library

我创建了一个简单的 Python 脚本,只要特定程序运行,它就会被激活。该程序将信息发送到屏幕,脚本需要抓取和分析这些信息。

脚本的部分逻辑可以表示如下:

while a certain condition is met:
    function to continuously check pixel information on a fixed area of the screen()
    if pixel data (e.g. RGB) changes:
        do something
    else:
        continues to check
Run Code Online (Sandbox Code Playgroud)

我已经找到了可以做到这一点的东西,但没有我想要的那么快。这是使用具有任意值的 Python 成像库 (PIL) 的解决方案:

import ImageGrab

box = (0,0,100,100) # 100x100 screen area to capture (0x0 is top left corner)
pixel = (60,20) #target pixel coordenates (must be within the box's boundaries)
im = ImageGrab.grab(box) #grabs the image area (aka printscreen) -> source of bottleneck
hm = im.getpixel(pixel) # gets pixel information from the captured image in the form of an RGB value
Run Code Online (Sandbox Code Playgroud)

然后我可以获取该 RGB 值并将其与该函数获得的先前值进行比较。如果它改变了,那么屏幕上就会发生一些事情,这意味着程序做了一些事情,因此脚本可以相应地运行。然而,脚本需要快速反应,特别是因为这只是一个更大的函数的一部分,有自己的错综复杂和缺陷,所以我正在一点一点地优化代码,从这个开始。

此解决方案将脚本限制为在 i7 4770k cpu 上每秒迭代约 30 次。看起来很快,但是将它与其他函数一起添加,这些函数本身以类似的速度解析像素信息,事情开始增加。我的目标是在单个函数上每秒至少进行 200 次迭代,也许 150 次迭代,以便结束脚本可以每秒运行 5-10 次迭代。

所以,长话短说:还有什么其他方法可以更快地解析屏幕上的像素?

Dan*_*haw 7

好吧,经过一番挖掘,事实证明确实可以用 Python 和简单的 pywin32 模块做我想做的事(感谢 Mark Hammond)。不需要“更强大”的语言或将工作外包给 numpy 之类的。这是,5 行代码(6 行与导入):

import win32ui
window_name = "Target Window Name" # use EnumerateWindow for a complete list
wd = win32ui.FindWindow(None, window_name)
dc = wd.GetWindowDC() # Get window handle
j = dc.GetPixel (60,20)  # as practical and intuitive as using PIL!
print j
dc.DeleteDC() # necessary to handle garbage collection, otherwise code starts to slow down over many iterations
Run Code Online (Sandbox Code Playgroud)

就是这样。它将在每次迭代时返回所选像素的数字 (COLORREF),这是一种表示颜色(就像 RGB 或十六进制)的方式,最重要的是,我可以解析数据!如果您不相信这里有我台式电脑上的一些基准测试(标准 Python 构建 CPython 和 i7 4770k):

我之前的解决方案围绕着一个虚拟秒表(您可以自己运行并检查它):

    import ImageGrab, time
    box = (0,0,100,100) #100 x 100 square box to capture
    pixel = (60,20) #pixel coordinates (must be within the box's boundaries)
    t1 = time.time()
    count = 0
    while count < 1000:
        s = ImageGrab.grab(box) #grabs the image area
        h = s.getpixel(pixel) #gets pixel RGB value
        count += 1
    t2 = time.time()
    tf = t2-t1
    it_per_sec = int(count/tf)
    print (str(it_per_sec) + " iterations per second")
Run Code Online (Sandbox Code Playgroud)

每秒获得 29 次迭代。让我们将其用作进行比较的基本速度。

这是 BenjaminGolder 使用 ctypes 指出的解决方案:

from ctypes import windll
import time
dc= windll.user32.GetDC(0)
count = 0
t1 = time.time()
while count < 1000:
    a= windll.gdi32.GetPixel(dc,x,y)
    count += 1
t2 = time.time()
tf = t2-t1
print int(count/tf)
Run Code Online (Sandbox Code Playgroud)

平均每秒 54 次迭代。这是一个惊人的 86% 改进,但这不是我正在寻找的数量级改进。

所以,最后,它来了:

name = "Python 2.7.6 Shell" #just an example of a window I had open at the time
w = win32ui.FindWindow( None, name )
t1 = time.time()
count = 0
while count < 1000:
    dc = w.GetWindowDC()
    dc.GetPixel (60,20)
    dc.DeleteDC()
    count +=1
t2 = time.time()
tf = t2-t1
it_per_sec = int(count/tf)
print (str(it_per_sec) + " iterations per second")
Run Code Online (Sandbox Code Playgroud)

像素渴求脚本每秒大约迭代 16000 次。是的,16000。这比之前的解决方案至少快了 2 个数量级,并且提高了29600%。它是如此之快以至于 count+=1 增量减慢了它的速度。我对 100k 次迭代做了一些测试,因为 1000 次对于这段代码来说太低了,平均值保持大致相同,14-16k 次迭代/秒。它还在 7-8 秒内完成了工作,而之前的那些是在我开始写这篇文章时开始的,而且……好吧,它们仍在继续。

好吧,就是这样!希望这可以帮助任何有类似目标并面临类似问题的人。请记住,Python 找到了方法。