【问题标题】:Why is my Python PyAutoGUI / Image grab script slow?为什么我的 Python PyAutoGUI / 图像抓取脚本很慢?
【发布时间】:2021-06-30 17:04:55
【问题描述】:

所以我正在制作一个自动点击器,我想问我可以做些什么来优化我的代码以使其更快,它目前正在工作,但是它识别像素颜色然后点击太慢。我尝试过使用空间,它似乎是一样的。

如果它只是一个像素而不是一个盒子会更快吗?另外,是否有 a.sum() 的替代方法,而不是计算总数,使用原始值会更快。

该项目的目的是在屏幕上找到一个像素/框,当颜色值变为某种颜色(白色或灰色范围)时,它会单击一个按钮。

from pyautogui import *
from PIL import ImageGrab
from PIL import ImageOps
import pyautogui

import time
import keyboard
import random
from numpy import*
import os

emark = (1440, 1026) 
pyautogui.click(x=1063, y=544)

def image_grab():
    box = (emark[0]+1,emark[1],emark[0]+11,emark[1]+2)
    image = ImageGrab.grab(box)
    grayImage = ImageOps.grayscale(image)
    a = array(grayImage.getcolors())
    return a.sum()

def clickButton():
    #pyautogui.press('space') 
    pyautogui.click(x=1160, y=600)
 
while True:
    image_grab()
    if 1000 <= image_grab() <= 1700:
        clickButton()

【问题讨论】:

    标签: python pyautogui


    【解决方案1】:

    您不必进行所有颜色转换和 numpy 数组的工作。只需抓取一个像素,保存它的颜色值,然后抓取一个新像素,并测试是否相等。

    #! /usr/bin/env python3
    from PIL import ImageGrab
    import pyautogui
    
    emark = ( 1440, 1026 )
    pyautogui .click( x=1063, y=544 )
    
    pixel = ( emark[0]-1, emark[1]-1, emark[0]+1, emark[1]+1 )
    original_pixel_color = ImageGrab .grab( pixel ) .getpixel( (0,0) )
    ##  print( original_pixel_color )
    
    def image_grab():
        new_pixel_color = ImageGrab .grab( pixel ) .getpixel( (0,0) )
        return new_pixel_color == original_pixel_color
    
    def clickButton():
        ##  print('yup')
        pyautogui .click( x=1160, y=600 )
    
    while True:
        if not image_grab():
            clickButton()
            ##  I don't know if pyautogui has a built in pause function,  
            ##  or repeat delay, but you probably want a time.sleep(1)
            ##  or similar here, so it doesn't rapid-fire autoclick
            ##  500 times in a row, once that pixel changes color.
    

    您的脚本不需要,但我认为 getpixel() 是最快的,在那些返回可比较颜色数据的 ImageGrab 方法中。

    import timeit
    loops = 99999
    
    print( 'tobytes()', timeit .timeit( lambda : original_pixel_color .tobytes(), number=loops ) )
    print( 'getcolors()', timeit .timeit( lambda : original_pixel_color .getcolors(), number=loops ) )
    print( 'getpixel()', timeit .timeit( lambda : original_pixel_color .getpixel( (0,0) ), number=loops ) )
    

    编辑: ImageGrab .grab( pixel ) 抓取一个屏幕矩形,根据你给它的大小。

    ( emark[0] -1, emark[1] -1,  emark[0] +1, emark[1] +1 )  ##  x, y, w, h
    

    由于 PIL 的限制,最小坐标空间是 2x2,因此emark -1, emark +1
    我认为是对的,但我继续多年前的回忆在这里。他们可能会在 SO 某处说出确定的像素位置,但您可以尝试

    ( emark[0], emark[1],  emark[0] +2, emark[1] +2 )
    

    如果减一。无论哪种方式,它实际上只抓取一个像素。

    .getpixel( (0,0) ) 以元组的形式返回该像素的 RGB 值
    所以是的,你可以在那里使用你自己的值,而不是一开始就捕捉一个像素。

    original_pixel_color = ( 240, 240, 240 ) 
    
    def brighter_than():
        new_pixel_color = ImageGrab .grab( pixel ) .getpixel( (0,0) )
        r = new_pixel_color[0] >= original_pixel_color[0]
        g = new_pixel_color[1] >= original_pixel_color[1]
        b = new_pixel_color[2] >= original_pixel_color[2]
        return  r and g and b
    
    while True:
        if not brighter_than():
            clickButton()
    

    另外,在开始时,在您导入之后:

    故障保护
    在每次 PyAutoGUI 调用后设置 2.5 秒的暂停:

    import pyautogui
    pyautogui .PAUSE = 2.5
    

    https://pyautogui.readthedocs.io/en/latest/quickstart.html


    后记:好吧,在某些情况下,他们正在保存/显示图像,而不仅仅是获取颜色,因此可以删除很多内容。使用 sp 是因为它在当时更容易弄清楚,而且对于我正在做的事情来说足够快。

    import subprocess as sp
    
    xx, yy = 640, 480
    ww, hh = 1, 1
    pixel_location = f'{xx},{yy},{ww},{hh}'
    cmd = [ 'scrot', 'temp.tiff', '--autoselect', pixel_location, '--silent' ]
    
    sp .Popen( cmd )
    

    我知道 scrot 是一个 Linux 命令,但它与 OSX 中的屏幕截图类似,您只需将 args 调整为预期的任何值。我之所以这么说是因为它对我有用,但是调用一个额外的 BASH 进程会产生少量的开销和几毫秒的延迟,所以您可能希望直接调用库来代替...... em>


    认为答案在 Tiger 的回应中,而 n4p 将其中的大部分内容剔除掉,因此很有希望;但是这些函数调用也超出了我的知识范围,因此在这一点上进行有根据的猜测。没有办法在这里测试它,所以你必须尝试看看。如果可能的话,尝试通过使用快速结构解包来跳过 numpy 和枕头 - 这应该是最快的。

    import struct
    import pyautogui
    import Quartz.CoreGraphics as CG
    
    pyautogui .PAUSE = 2.5
    xx, yy = 500, 500
    ww, hh = 1, 1
    region = CG .CGRectMake( xx, yy, ww, hh )
    
    orig = ( 240, 240, 240 ) 
    
    def brighter_than():  ##  think these properties are right...
        pixel = CG .CGWindowListCreateImage( region, CG .kCGWindowListOptionOnScreenOnly, CG .kCGNullWindowID, CG .kCGWindowImageDefault )
    
        data = CG .CGDataProviderCopyData( CG .CGImageGetDataProvider( pixel ) )
        ##  backwards from RGBA, Mac stores pixels as BGRA instead
        b, g, r, a = struct .unpack_from( 'BBBB', data )  ##  BBBB = 4 Bytes
    
        return r >= orig[0] and g >= orig[1] and b >= orig[2]
    
    while True:
        if brighter_than():
            clickButton()
    

    其中一些信息来自 dbr 的 blog writeupCGRectMake() 来自 Converting CGImage to python image (pil/opencv) 。其他人都在使用region = CG.CGRectInfinite,它可能会抓取整个屏幕而不是一个像素。

    【讨论】:

    • 只是一些问题,我可以学习和理解,所以“original_pixel_color”功能是抓取像素坐标,它没有定义颜色?不确定 .grab(pixel) 和 .getpixel((0,0)) 的区别。然后 def image_grab 比较原始像素颜色和新像素颜色,如果它改变了 clickButton 函数启动。如果颜色为白色或灰色值范围(例如(240-255)),我该如何设置它,它将启动点击的像素的颜色,而不是颜色比较?总的来说,感谢您提供如此详细的答案以及您所做的额外结果测试。
    • 你好,这是完美的我已经设法将它们组合起来,所以它工作正常,我已将原始像素设置为 250,250,205 和任何高于现在激活点击的内容,我已将“如果不是”更改为'如果'比。现在唯一的问题是它仍然点击缓慢,与我的原始代码几乎相同。我在想这是不是点击的像素识别太慢了?或 pyauto.click 太慢,或者可能是我的硬件影响了它。我看到一个使用 win32 api 进行点击的视频,因为它的速度提高了 10 倍,但我在 MAC OS 上,有什么相同的建议吗?
    • 哦,对了,应该是if。我会在一秒钟内改变它。是的,我看到人们使用ctypes 获得了更好的结果。它调用 C 库而不是在 Python 中处理。我得想一想那在哪里。前几天有人问关于自动化的问题。他们在 GitHub 上有示例。让我喝杯茶,然后我会寻找Mac版本。
    • 你听说过 Python 中封装的 Pynput 和 Apple Script,我看过一些例子,看起来很繁琐。我不确定如何确定是什么让它更快或更慢?
    • PIL 可能真的很慢。我记得它实际上抓取了整个屏幕缓冲区,然后裁剪出您正在寻找的特定像素。唯一的好处是一个像素的颜色转换比许多像素更快。十几年前在 Linux 中做了一些事情,认为这比点击延迟更像是一个限制因素。您可以进行类似的timeit() 呼叫,其中包含几个屏幕截图和点击,以及它们返回的打印时间。 stackoverflow.com/questions/37099411/… -- 嗯 --stackoverflow.com/a/11048135/3342050
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-15
    • 1970-01-01
    相关资源
    最近更新 更多