【问题标题】:Faster method of reading screen pixel in Python than PIL?在 Python 中读取屏幕像素的方法比 PIL 更快?
【发布时间】:2011-01-01 03:29:26
【问题描述】:

目前我通过 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 ,因此我尝试它的原因)

【问题讨论】:

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


    【解决方案1】:

    这是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或者你应该完全删除路径,只使用user32.dllgdi32.dll应该也可以工作。

    【讨论】:

    • 非常感谢。这明显加快了屏幕阅读速度。我现在可以随心所欲地阅读,几乎没有减速。 ;)
    • 我尝试了 ctypes 的上述语法,但无法使用 LoadLibrary。包装函数代码正是我想要的。
    • 我不敢相信这实际上对时间有效至少 30 次。我已经尝试过 GetPixel,但花了很多时间。
    • 看到my addition这个答案。
    【解决方案2】:

    评论 S.Mark 的解决方案:windll 已经将 user32 库加载到了 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文件。
    【解决方案3】:

    您也许可以通过 SDL (?) 来实现。基于this question,SDL 可以访问屏幕。它有 python 绑定。

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

    【讨论】:

      【解决方案4】:

      这是一个老问题,但在搜索 Python 屏幕抓取方法时,它在 Google 上的排名相当高,所以我认为提及 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 的 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

      【讨论】:

        【解决方案5】:

        作为@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"))
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-10-08
          • 2013-06-11
          • 2021-02-11
          • 2011-02-09
          • 1970-01-01
          • 2017-11-21
          • 1970-01-01
          相关资源
          最近更新 更多