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

时间:2010-01-04 05:41:27

标签: python windows winapi directx python-imaging-library

目前我正在通过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 个答案:

答案 0 :(得分:14)

这是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,所以这是我使用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,或者只需使用{{1}即可完全删除路径}和user32.dll也应该有用。

答案 1 :(得分: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)

答案 2 :(得分:3)

您可以通过SDL(?)来完成。根据{{​​3}},SDL可以访问屏幕。它有python绑定。

可能值得一试?如果它工作,它肯定比在PIL中进行全屏捕获更快。

答案 3 :(得分:2)

这是一个老问题,但在搜索Python屏幕抓取方法时,它在Google上排名很高,所以我认为提到ImageGrab模块现在支持抓取屏幕区域可能很有用: / p>

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的ImageGrab的替代品,它还将屏幕的某些部分保存到PIL / Pillow图像。该模块也适用于Linux,与ImageGrab不同,后者仅适用于Windows和OS X.

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

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

答案 4 :(得分:1)

除了@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"))

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