我写了一个快速的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 ms)以允许串行传输开销。请记住我的屏幕分辨率是2560 * 1600!
我听说过Python Imaging Library (PIL),但还没有时间研究ImageGrab
功能的速度,但看起来很有希望。
我可以直接从GPU读取像素值吗?
另一个想法 - 检测电影上/下边缘的最佳方法是什么? (如果宽高比是宽屏幕,屏幕截图的顶部/底部有黑条,有些矩形是黑色的。)
使用PIL的抓取():
>>> 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写一些东西吗?
答案 0 :(得分:3)
使用Python Imaging Library。从docs(在图像模块中):
<强> getcolors 强>
im.getcolors()=&gt; (计数,颜色)元组列表或无
im.getcolors(maxcolors)=&gt; (计数,颜色)元组列表或无
(1.1.5中的新增内容)返回(计数,颜色)元组的未排序列表,其中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非常有帮助。
答案 1 :(得分:1)
快速获胜可能是使用resize
操作(在PIL中)(您可以使用简单的插值来获得速度)到5x5图像而不是对区域进行平均,例如:
myimg = ImageGrab.grab()
resized = myimg.resize((5, 5), Image.NEAREST)
这应该产生与自己进行平均工作大致相同的效果。
虽然不太确定PIL的ImageGrab的速度(以及它与autopy
的比较方式),但它很容易尝试找出。
答案 2 :(得分:1)
要加快调整大小操作,您可以分两步完成。使用NEAREST用于第一个以尽可能最快的方式减少像素数,然后ANTIALIAS将它们合并为代表性样本。它相当于您之前使用PIL函数完成的步长。
PIL.ImageGrab.grab().resize((150, 100), PIL.Image.NEAREST).resize((15, 10), PIL.Image.ANTIALIAS)