截面颜色平均矩形

时间:2012-05-18 14:20:45

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

我写了一个快速的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)

5x5 Screenshot

问题在于实现的速度 - 因为这是块中每个像素的循环(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写一些东西吗?

3 个答案:

答案 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)