在图像中找到RGB颜色的边界框

3
我正在处理页面分割算法。代码的输出将为每个区域分配一个独特颜色的像素图像。我希望处理图像以找到区域的边界框。我需要找到所有颜色,然后找到该颜色的所有像素,然后找到它们的边界框。
以下是示例图像。 Example output image showing colored zones 我目前从R,G,B通道的直方图开始。直方图告诉我数据位置。
img = Image.open(imgfilename)
img.load()
r,g,b = img.split()

ra,ga,ba = [ np.asarray(p,dtype="uint8") for p in (r,g,b) ]

rhist,edges = np.histogram(ra,bins=256)
ghist,edges = np.histogram(ga,bins=256)
bhist,edges = np.histogram(ba,bins=256)
print np.nonzero(rhist)
print np.nonzero(ghist)
print np.nonzero(bhist)

输出结果: (array([ 0, 1, 128, 205, 255]),) (array([ 0, 20, 128, 186, 255]),) (array([ 0, 128, 147, 150, 255]),)

我有些困惑了。通过视觉检查,我有颜色(0,0,0),(1,0,0),(0,20,0),(128,128,128)等。我应该如何将非零输出排列成np.where()的像素值呢?

我考虑将3行列narray压缩成24位打包RGB值的2-D平面,并在其中搜索。这似乎是一种蛮力而不优雅的方法。在Numpy中,有更好的方法来查找颜色值的边界框吗?


这么多令人难以置信的建议! - David Poole
3个回答

4

没有理由将其视为RGB彩色图像,它只是其他人进行分割可视化的结果。您可以轻松将其视为灰度图像,并且对于这些特定颜色,您不必自己做任何其他处理。

import sys
import numpy
from PIL import Image

img = Image.open(sys.argv[1]).convert('L')

im = numpy.array(img) 
colors = set(numpy.unique(im))
colors.remove(255)

for color in colors:
    py, px = numpy.where(im == color)
    print(px.min(), py.min(), px.max(), py.max())

如果您不能依赖于convert('L')给出唯一的颜色(即,您正在使用除给定图像中的颜色之外的其他颜色),则可以打包您的图像并获取唯一的颜色:

...
im = numpy.array(img, dtype=int)

packed = im[:,:,0]<<16 | im[:,:,1]<<8 | im[:,:,2]
colors = set(numpy.unique(packed.ravel()))
colors.remove(255<<16 | 255<<8 | 255)

for color in colors:
    py, px = numpy.where(packed == color)
    print(px.min(), py.min(), px.max(), py.max())

顺便说一句,我建议在查找边界框之前先删除小的连接组件。


对于np.unique的使用加1,比直方图好多了。其他方面...他肯定需要减少搜索空间,因为每次检查都非常昂贵。不过我不确定亮度是不是正确的选择,因为它可能会混淆不同颜色的文本。可能不会,但也有可能。我认为最好将整个图像除以16,以将相似的颜色混合在一起。 - Jaime
@Jaime,图像已经被分割了,如果没有被分割,那么在RGB中这样做会是一件非常糟糕的事情。使用亮度只是将所选颜色映射到表示不同区域分割的标签的“诡计”,它当然可能会失败,如果使用其他颜色。在这种情况下,最简单的方法是 colors = set(img.getdata())并为其中每个项目选择一个标签。 - mmgp
我不想将RGB转换为灰度。因为我在写论文,准确性是我的最大关注点。第二个建议(使用独特的24位打包方法)非常好用!但我需要对我的输入图像进行更多的预处理。 - David Poole
@DavidPoole 您没有真正的“RGB”图像,这是我答案的初始部分。无论那个分割程序做了什么,它都可以返回第一个值为1的区域,第二个值为2的区域等等。那也同样“RGB” 。将其转换为灰度在所示示例中有效,因为它只是将您在该特定图像中显示的颜色映射到/不同的/值上。如果它没有映射到不同的值(即,至少有两种颜色被映射到相同的灰度值),我甚至不会考虑显示它。 - mmgp
@mmgp 在 RGB->L 转换中发生碰撞的可能性非常小。但是我有数百张图像需要测试,所以零概率更好(与 24 位解决方案一样)。我不确定这个分割代码(XYCut)是如何分配颜色的。我有源代码,所以下一步是修改它直接输出区域。感谢您的帮助! - David Poole
@DavidPoole 如果我不能修改分段程序以便不需要着色就可以直接给我区域的话,我也会选择第二个代码片段。 - mmgp

2

编辑 将所有内容整合成一个工作程序,使用您发布的图像:

from __future__ import division
import numpy as np
import itertools
from PIL import Image

img = np.array(Image.open('test_img.png'))

def bounding_boxes(img) :
    r, g, b = [np.unique(img[..., j]) for j in (0, 1, 2)]
    bounding_boxes = {}
    for r0, g0, b0 in itertools.product(r, g, b) :
        rows, cols = np.where((img[..., 0] == r0) &
                              (img[..., 1] == g0) &
                              (img[..., 2] == b0))
        if len(rows) :
            bounding_boxes[(r0, g0, b0)] = (np.min(rows), np.max(rows),
                                            np.min(cols), np.max(cols))
    return bounding_boxes

In [2]: %timeit bounding_boxes(img)
1 loops, best of 3: 30.3 s per loop

In [3]: bounding_boxes(img)
Out[3]: 
{(0, 0, 255): (3011, 3176, 755, 2546),
 (0, 128, 0): (10, 2612, 0, 561),
 (0, 128, 128): (1929, 1972, 985, 1438),
 (0, 255, 0): (10, 166, 562, 868),
 (0, 255, 255): (2938, 2938, 680, 682),
 (1, 0, 0): (10, 357, 987, 2591),
 (128, 0, 128): (417, 1873, 984, 2496),
 (205, 186, 150): (11, 56, 869, 1752),
 (255, 0, 0): (3214, 3223, 570, 583),
 (255, 20, 147): (2020, 2615, 956, 2371),
 (255, 255, 0): (3007, 3013, 600, 752),
 (255, 255, 255): (0, 3299, 0, 2591)}

即使实际检查的颜色数量很少,速度也不是很快...


您可以通过以下方式找到颜色r0g0b0的边界框:

rows, cols = np.where((ra == r0) & (ga == g0) & (ba == b0))
top, bottom = np.min(rows), np.max(rows)
left, right = np.min(cols), np.max(cols)

与其遍历所有RGB颜色的2**24种组合,你可以仅使用非零直方图条目的笛卡尔积来大大减少搜索空间:

for r0, g0, b0 in itertools.product(np.nonzero(rhist),
                                    np.nonzero(ghist),
                                    np.nonzero(bhist)) :

您可能会遇到不存在的组合泄漏问题,但您可以通过检查rowscols是否为空元组来过滤掉这些组合。但在您的示例中,您已将搜索空间从2 ** 24个组合减少到了只有125个。


0

这只是我脑海中的一个解决方案。您可以从左上到右下迭代图像中的像素,并保存每种颜色的topbottomleftright值。对于给定的颜色,top值将是您看到的具有此颜色的第一行,而bottom将是最后一行,left值将是此颜色像素的最小列值,right是您找到的最大列值。

然后,对于每种颜色,您可以在所需颜色中从top-leftbottom-right绘制矩形。

我不知道这是否合格作为一个好的边界框算法,但我想这还可以。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接