9

我编写了一个快速的 Python 脚本来返回围绕我的屏幕周边的矩形的平均颜色。(这里的最终目标是在我的显示器周围安装RGB LED 灯条,以便在看电影时产生发光效果 - 像这样(youtube),但更有趣,因为我自己制作)。

我目前正在使用autopy将屏幕作为位图(“屏幕截图”)获取,获取每个像素值以及 RGB <-> HEX 转换。

简化版:

step = 1
width = 5
height = 5

b = autopy.bitmap.capture_screen()

for block in border_block(width, height): # for each rectangle around the perimeter of my screen

    R,G,B = 0,0,0
    count = 0

    for x in xrange(block.x_min, block.x_max, step):
        for y in xrange(block.y_min, block.y_max, step):
            r,g,b = autopy.color.hex_to_rgb(image.get_color(x, y))
            R += r; G += g; B += b
            count += 1

   block.colour = "#{:06x}".format(autopy.color.rgb_to_hex(R/count,G/count,B/count))

然后我使用matplotlib: (这被配置为 5x5 块,step = 1)显示块

5x5 截图

问题在于执行速度 - 因为这是循环块中的每个像素(2560*1600 分辨率/5 = 320*512 块 = 每块 163,840 像素),以及周边的每个块(16*163,840 = 2,621,440 循环)。总的来说,这需要 2.814 秒才能完成。

如果我增加步长值,它会加快速度,但还不够:(这是在边界周围使用更逼真的 15x10 块)

Step    Time (s)
1       1.35099983215
2       0.431000232697
5       0.137000083923
10      0.0980000495911
15      0.095999956131
20      0.0839998722076
50      0.0759999752045

那是因为屏幕截图本身大约需要 0.070 秒 - 这意味着我被限制为 12.8 FPS。

>>> timeit.Timer("autopy.bitmap.capture_screen()", "import autopy").timeit(100)/100
0.06874468830306966

问题:

  • 有没有更快的截屏和平均屏幕区域的方法?

    我不太担心准确性,但希望能够以大约 30 FPS 的速度返回这些值,理想情况下更快(20-30 ms)以允许串行传输开销。请记住我的屏幕分辨率是 2560*1600!

    我听说过Python Imaging Library (PIL),但还没有时间研究该ImageGrab函数的速度,但它看起来很有希望。

  • 我可以直接从 GPU 读取像素值吗?

  • 另一个想法 - 检测电影顶部/底部边缘的最佳方法是什么?(如果宽高比是宽屏,屏幕截图顶部/底部有黑条,一些矩形是黑色的)。


使用 PIL 的 grab()

>>> timeit.Timer("ImageGrab.grab()", "from PIL import ImageGrab").timeit(100)/100
0.1099840205312789

PIL - 调整大小:(ChristopheD)

>>> timeit.Timer("PIL.ImageGrab.grab().resize((15, 10), PIL.Image.NEAREST)", "import PIL").timeit(100)/100
0.1028043677442085

>>> timeit.Timer("PIL.ImageGrab.grab().resize((15, 10), PIL.Image.ANTIALIAS)", "import PIL").timeit(100)/100
0.3267692217886088

注意:这是对上面获得的结果的改进,但我们仍然限制在 9 FPS 或 3 FPS 与完全抗锯齿。


PIL - 最接近然后调整大小:(Mark Ransom)

>>> for step in [1,2,5,10,15,20,50]:
    print step, timeit.Timer("PIL.ImageGrab.grab().resize(("+str(2560/step)+", "+str(1600/step)+"), PIL.Image.NEAREST).resize((15, 10), PIL.Image.ANTIALIAS)", "import PIL.ImageGrab").timeit(100)/100

结果:

Step  Time(s)
1     0.333048412226
2     0.16206895716
5     0.117172371393
10    0.102383282629
15    0.101844097599
20    0.101229094581
50    0.100824552193

autopy在顶部手动循环要快得多,但我们仍然限制在 ~9 FPS(“步长”为 10)。

注意:这不包括所需的 RGB 到 HEX 转换


谁能想出一个更快的方法 - 即截取部分截图?我应该用C写一些东西吗?

4

3 回答 3

3

使用 Python 图像库。从文档(在图像模块中):

获取颜色

im.getcolors() => (计数,颜色)元组列表或无

im.getcolors(maxcolors) => (计数,颜色)元组列表或无

(1.1.5 中的新功能)返回 (count, color) 元组的未排序列表,其中 count 是相应颜色在图像中出现的次数。

Image 模块还包含一个crop() 方法,您可以使用该方法将每个矩形插入到getcolors() 中。您可以轻松地从中获取加权平均值。

它应该比在 python 中手动运行循环要快得多。我不确定它是否足够快以实时使用,但您会获得显着的速度提升。您也可以每秒截取几次屏幕截图,因为以 60 fps 和 10 fps 向 LED 发送信号的可能性不大。不要将其视为“限制为 12.8 FPS”,将其视为“只能每 5 帧更新一次 LED”,这应该不会是明显的差异。

编辑:如果您真的对这里的进一步优化感兴趣,我想您会发现在 windows 上使用 python 截屏的最快方法非常有帮助。

于 2012-05-18T14:57:45.980 回答
1

要加快调整大小操作,您可以分两步完成。对第一个使用 NEAREST 以尽可能快的方式减少像素数量,然后使用 ANTIALIAS 将它们合并到一个有代表性的样本中。它等同于您之前尝试过的步长,使用 PIL 函数完成。

PIL.ImageGrab.grab().resize((150, 100), PIL.Image.NEAREST).resize((15, 10), PIL.Image.ANTIALIAS)
于 2012-05-18T16:40:02.577 回答
1

一个快速的胜利可能是对resize5x5 图像使用操作(在 PIL 中)(您可以使用简单的插值来提高速度)而不是对区域进行平均,例如:

myimg = ImageGrab.grab()
resized = myimg.resize((5, 5), Image.NEAREST) 

这应该产生与自己进行平均工作大致相同的效果。

虽然不太确定 PIL 的 ImageGrab 的速度(以及它与 的比较autopy),但尝试找出它很容易。

于 2012-05-18T14:45:03.677 回答