截面颜色平均矩形

我写了一个快速的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显示块:(配置为5×5块,步骤= 1)

5x5截图

问题是实现的速度 – 因为这是一个块中每个像素的循环(2560 * 1600分辨率/ 5 = 320 * 512块=每块163,840像素),并且周边的每个块(16 * 163,840 = 2,621,440个循环) )。 总的来说,这需要花费2.814秒来完成。

如果我增加步长值,它会加速但不够:(这是使用围绕边框的更逼真的15×10块)

 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 – resize: (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 – 最近resize: (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写一些东西吗?

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

getcolors

im.getcolors()=>(count,color)元组列表或None

im.getcolors(maxcolors)=>(count,color)元组列表或None

(1.1.5中的新增内容)返回(计数,颜色)元组的未排序列表,其中计数是图像中相应颜色出现的次数。

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

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

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

快速获胜可能是使用resize操作(在PIL中)(您可以使用简单插值来获得速度)到5×5图像而不是平均区域,例如:

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

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

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

要加快resize操作,您可以分两步完成。 使用NEAREST用于第一个以尽可能最快的方式减少像素数,然后ANTIALIAS将它们合并为代表性样本。 它相当于您之前尝试过的步长,使用PIL函数完成。

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