比PIL更快的Python读取屏幕像素的方法?

11 投票
5 回答
32106 浏览
提问于 2025-04-15 17:36

目前我正在使用AutoItv3的像素读取器来在一个运行Direct 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 个回答

3

你可以试试用SDL来实现这个功能(?)。根据这个问题,SDL可以访问屏幕。而且它还有Python的接口。

试试看也许有用?如果成功的话,速度肯定比用PIL进行全屏截图要快。

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

这是PIL库中抓取屏幕的代码,它不需要任何参数,直接抓取整个屏幕并把它转换成位图。

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的库,所以我在Windows 10上用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),等等……

附注:我的系统是Windows 2000,所以我在路径中用了winnt,你可能需要把它改成windows,或者完全去掉路径,只用user32.dllgdi32.dll也应该可以。

撰写回答