【问题标题】:Screenshot colour averaging of rectangles矩形的屏幕截图颜色平均
【发布时间】:2012-07-11 03:24:04
【问题描述】:

我编写了一个快速的 Python 脚本来返回围绕我的屏幕周边的矩形的平均颜色。 (这里的最终目标是让 RGB LED strips 围绕我的显示器,在电影中产生发光效果 - 就像 this (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 块,步长 = 1)

问题在于执行速度 - 因为这是循环块中的每个像素(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 毫秒)以允许串行传输开销。请记住我的屏幕分辨率是 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 写一些东西吗?

【问题讨论】:

  • resize 中使用的大多数模式都会产生较差的结果,因为它们没有采样足够的像素,这就是它们的速度如此接近的原因。 NEAREST 需要 1,BILINEAR 需要 2x2,BICUBIC 需要 3x3。这与 ANTIALIAS 将在该比例下使用的 170x160 相去甚远。
  • @MarkRansom 啊,谢谢你的解释。有趣的是,使用 autopy (1.35s) 和 PIL 的 ANTIALIAS (0.326s) 手动循环之间的区别 - 大大改进了!
  • 就个人而言,如果 PIL 还没有这样做的话,这似乎是一个明显的并行化候选者。如果这也没有提供足够的加速,那么在 GPU 本身上也应该很简单(在这种情况下速度会更高)。虽然 FSP 为 9 似乎还不错,并且使用并行代码应该没问题..
  • @Voo 你的意思是使用 GPU 进行并行化(截图->CUDA/OpenCL),还是直接在显卡上运行的东西?您如何建议提高屏幕截图本身的速度?
  • 续。请注意,也许您甚至可以以某种方式从 Cuda/OpenCL 访问帧缓冲区,在这种情况下,这将是迄今为止最简单的解决方案。编辑:This paper 似乎正是关于那些东西。因此,如果您需要性能,可能就是这样。甚至还有一个 Python cuda 包装器,尽管这在 C 中似乎更容易;)

标签: python c optimization screenshot python-imaging-library


【解决方案1】:

使用 Python 图像库。来自docs(在图像模块中):

getcolors

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”,这应该不会是明显的差异。

编辑:如果您真的对这里的进一步优化感兴趣,我想您会发现Fastest way to take a screenshot with python on windows 很有帮助。

【讨论】:

  • 我已经更新了我的问题 - 看起来 ImageGrab.grab() 平均需要 0.109 秒 - 只有 9 FPS!我意识到 9 FPS 和 30 FPS 之间的视觉差异很小,但优化代码是一个有趣的挑战,对吧?
  • 有趣,完全不是我所期望的。我已经链接到似乎表现更好的东西,虽然我自己没有测试过。
  • 感谢罗伯特的链接,我会用 gtk 破解的!我会把我的结果发回这里。
  • gnome ftp 好像挂了,我稍后再试。
【解决方案2】:

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

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

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

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

【讨论】:

  • 我已经更新了我的问题 - 看起来 ImageGrab.grab() 平均需要 0.116 秒,大约是 autopy.bitmap.capture_screen() 速度的 1.6 倍。调整大小稍快,不知道为什么。
  • 使用Image.NEAREST 根本不做任何平均——它只是从许多像素中选择一个像素。无法保证它选择的像素将代表该区域。
  • @MarkRansom 好点 - 我已经用其他调整大小的方法更新了我的问题timits
【解决方案3】:

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

PIL.ImageGrab.grab().resize((150, 100), PIL.Image.NEAREST).resize((15, 10), PIL.Image.ANTIALIAS)

【讨论】:

  • 为此干杯,比我的autopy 步进函数快得多。不过,我们仍然限制在 9 FPS - 所有这些实现的是更准确的颜色!对提高屏幕截图速度有什么想法吗?我可以试试c
猜你喜欢
  • 1970-01-01
  • 2012-01-28
  • 2022-11-18
  • 2013-12-20
  • 2020-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多