【发布时间】: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