Python中比PIL更快的屏幕像素读取方法是什么?

11

目前我正在使用AutoItv3中的像素读取器执行一些在直接X运行的程序中的操作;一个游戏。现在程序可以正常工作,但是作为练习,我一直在用Python重新编写它。现在我能够做到:

import ImageGrab  # Part of PIL
    image = ImageGrab.grab() #Define an area to capture.
    rgb = image.getpixel((1, 90)) #What pixel do we want?

这可以得到我想要的像素信息,但我需要很快地执行此操作(需要每秒执行3次或更快),结果是它严重影响此基于DirectX的游戏的帧率。

有没有更快的方法在Python中读取特定的屏幕像素?即使将其限制为每0.3秒运行一次也会导致比实际需要的压力更大(我实际上认为Python对于这个特定用途比AutoIt更快,因此我正在尝试它)

5个回答

20

这是PIL的grabscreen源代码,它不接受任何参数,并且它抓取整个屏幕并将其转换为位图。

PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
{
    int width, height;
    HBITMAP bitmap;
    BITMAPCOREHEADER core;
    HDC screen, screen_copy;
    PyObject* buffer;

    /* step 1: create a memory DC large enough to hold the
       entire screen */

    screen = CreateDC(";DISPLAY", NULL, NULL, NULL); 
    screen_copy = CreateCompatibleDC(screen); 

    width = GetDeviceCaps(screen, HORZRES);
    height = GetDeviceCaps(screen, VERTRES);

    bitmap = CreateCompatibleBitmap(screen, width, height);
    if (!bitmap)
        goto error;

    if (!SelectObject(screen_copy, bitmap))
        goto error;

    /* step 2: copy bits into memory DC bitmap */

    if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, SRCCOPY))
        goto error;

    /* step 3: extract bits from bitmap */

    buffer = PyString_FromStringAndSize(NULL, height * ((width*3 + 3) & -4));
    if (!buffer)
        return NULL;

    core.bcSize = sizeof(core);
    core.bcWidth = width;
    core.bcHeight = height;
    core.bcPlanes = 1;
    core.bcBitCount = 24;
    if (!GetDIBits(screen_copy, bitmap, 0, height, PyString_AS_STRING(buffer),
                   (BITMAPINFO*) &core, DIB_RGB_COLORS))
        goto error;

    DeleteObject(bitmap);
    DeleteDC(screen_copy);
    DeleteDC(screen);

    return Py_BuildValue("(ii)N", width, height, buffer);

error:
    PyErr_SetString(PyExc_IOError, "screen grab failed");

    DeleteDC(screen_copy);
    DeleteDC(screen);

    return NULL;
}

所以,当我深入探究后,发现 C 语言的方式很好。

http://msdn.microsoft.com/en-us/library/dd144909(VS.85).aspx

而 Python 可以使用 ctypes,所以这是我的方案,使用 ctypes(在 Windows 10 中,winnt 已被替换为 Windows):

>>> from ctypes import *
>>> user= windll.LoadLibrary("c:\\winnt\\system32\\user32.dll") #I am in windows 2000, may be yours will be windows
>>> h = user.GetDC(0)
>>> gdi= windll.LoadLibrary("c:\\winnt\\system32\\gdi32.dll")
>>> gdi.GetPixel(h,1023,767)
16777215 #I believe its white color of RGB or BGR value, #FFFFFF (according to msdn it should be RGB)
>>> gdi.GetPixel(h,1024,767)
-1 #because my screen is only 1024x768

您可以编写一个如下的函数包装器来调用 GetPixel 函数:

from ctypes import windll
dc= windll.user32.GetDC(0)

def getpixel(x,y):
    return windll.gdi32.GetPixel(dc,x,y)

然后你可以像这样使用:getpixel(0,0)getpixel(100,0)等等...

PS: 我的操作系统是Windows 2000,因此我将winnt添加到路径中,你可能需要更改为windows或者完全删除路径,只使用user32.dllgdi32.dll也行。


非常感谢。这显著加快了屏幕阅读速度。现在我可以随意快速阅读,几乎没有任何减速。;) - ThantiK
我尝试了ctypes的上述语法,但在LoadLibrary中无法使其工作。包装器函数代码正是我要找的。 - Octipi
我简直不敢相信,这个可以每秒至少运行30次的时间。我试过使用GetPixel,但花费的时间太长了。 - Marek
请查看我对这个答案的补充 - Ekrem Dinçel

7

对S.Mark的解决方案进行评论:user32库已经被windll加载到windll.user32中,因此您可以使用以下代码替换dc = ...行:

def getpixel(x,y):
    return gdi.GetPixel(windll.user32.GetDC(0),x,y)

最好是:

dc= windll.user32.GetDC(0)

谢谢,今天早上我尝试了cdll.user32.GetDC(0),但它没有起作用,所以我手动导入了dll文件。 - YOU

3

你可能可以通过SDL (?)来完成。根据这个问题,SDL可以访问屏幕,并且它有Python绑定。

也许值得一试?如果成功了,它肯定比在PIL中进行全屏截图要快。


2

这是一个老问题,但是当搜索Python屏幕截图方法时,在谷歌排名非常高,因此我认为提到ImageGrab模块现在支持抓取屏幕区域可能会很有用:

PIL.ImageGrab.grab(bbox=None)
Parameters: bbox – What region to copy. Default is the entire screen.
Returns:    An image

http://pillow.readthedocs.io/en/3.1.x/reference/ImageGrab.html

稍微扩大一下范围,现在有一个名为pyscreenshot的替代品,也可以将屏幕的某个部分保存为PIL/Pillow图像。与仅适用于Windows和OS X的ImageGrab不同,此模块还适用于Linux。

import pyscreenshot as ImageGrab
im=ImageGrab.grab(bbox=(10,10,510,510)) # X1,Y1,X2,Y2
im.show()

https://pypi.python.org/pypi/pyscreenshot


2
作为对@YOU回答的补充,此函数将给定像素的RGB值作为元组返回。我已经将输出与PIL.ImageGrab进行了比较,结果很好:
from ctypes import windll
dc= windll.user32.GetDC(0)

def getpixel(x,y):
    return tuple(int.to_bytes(windll.gdi32.GetPixel(dc,x,y), 3, "little"))

请注意,此代码没有错误处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接