根据函数的返回值更改传递给它的numpy数组元素。

3

我有一个包含RGBA值的数组,类似于这样:

# Not all elements are [0, 0, 0, 0]
array([[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0],
       ...,
       [0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])

我还有一个函数,它会返回一个最接近某个RGBA值的5个值中的一个(绿色、红色、橙色、棕色、白色)。

def closest_colour(requested_colour):
    min_colours = {}
    for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
        if name in ['green', 'red', 'orange', 'brown', 'white']:
            r_c, g_c, b_c = webcolors.hex_to_rgb(key)
            rd = (r_c - requested_colour[0]) ** 2
            gd = (g_c - requested_colour[1]) ** 2
            bd = (b_c - requested_colour[2]) ** 2
            min_colours[(rd + gd + bd)] = name
    return min_colours[min(min_colours.keys())]

我想将此函数应用于我的numpy数组的每个元素并更改这些元素。我尝试了以下方式:
img_array[closest_colour(img_array) == 'green'] = (0, 255, 0, 1)
img_array[closest_colour(img_array) == 'red'] = (255, 0, 0, 1)
img_array[closest_colour(img_array) == 'brown'] = (92, 64, 51, 1)
img_array[closest_colour(img_array) == 'orange'] = (255, 165, 0, 1)
img_array[closest_colour(img_array) == 'white'] = (255, 255, 255, 0)

但是我遇到了一个错误:
TypeError: unhashable type: 'numpy.ndarray'

我知道为什么会出现这个错误,但我也不知道有没有更有效的方法来做到这一点。 由于我正在处理一个相当大的数组(图像),所以有没有更有效的方法来实现呢?
2个回答

3

我会重写您的函数,让它更加向量化。首先,您无需为每个像素循环遍历整个CSS颜色字典:可以轻松预计算查找表。其次,您可以将所需的五种颜色映射到RGBA值,而不必使用名称作为中介。这将使您的生活变得更加轻松,因为您大部分时间将使用数字而不是字符串。

names = dict.fromkeys(['green', 'red', 'orange', 'brown', 'white'])
for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
    if name in names:
        names[name] = key
lookup = np.array([webcolors.hex_to_rgb(key) + (1,) for key in names.values()])

由于颜色数量较少,因此可以计算出到颜色的Nx5数组距离:

distance = ((rgba[..., None, :] - lookup)**2).sum(axis=-1)

如果您不想在距离中包含透明度,请从比较中将其移除:
distance = ((rgba[..., None, :3] - lookup[..., :3])**2).sum(axis=-1)

这将给你一个 Nx5 的距离数组(其中 N 可以是多个维度,因为有意使用 ... 而不是:)。最小值在以下位置。
closest = distance.argmin(-1)

现在,您可以直接将此索引应用于查找表:
result = lookup[closest]

以下是示例运行:

>>> np.random.seed(42)
>>> rgba = np.random.randint(255, size=(10, 4))
>>> rgba
array([[102, 179,  92,  14],
       [106,  71, 188,  20],
       [102, 121, 210, 214],
       [ 74, 202,  87, 116],
       [ 99, 103, 151, 130],
       [149,  52,   1,  87],
       [235, 157,  37, 129],
       [191, 187,  20, 160],
       [203,  57,  21, 252],
       [235,  88,  48, 218]])
>>> lookup = np.array([
...     [0, 255, 0, 1],
...     [255, 0, 0, 1],
...     [92, 64, 51, 1],
...     [255, 165, 0, 1],
...     [255, 255, 255, 0]], dtype=np.uint8)

>>> distance = ((rgba[..., None, :3] - lookup[..., :3])**2).sum(axis=-1)
>>> distance
array([[ 24644,  63914,  15006,  32069,  55754],
       [ 80436,  62586,  19014,  66381,  60546],
       [ 72460,  82150,  28630,  69445,  43390],
       [ 15854,  81134,  20664,  41699,  63794],
       [ 55706,  57746,  11570,  50981,  58256],
       [ 63411,  13941,   5893,  24006, 116961],
       [ 66198,  26418,  29294,   1833,  57528],
       [ 41505,  39465,  25891,   4980,  63945],
       [ 80854,   6394,  13270,  14809,  96664],
       [ 85418,  10448,  21034,   8633,  71138]])
>>> closest = distance.argmin(-1)
>>> closest
array([2, 2, 2, 0, 2, 2, 3, 3, 1, 3])
>>> lookup[closest]
array([[ 92,  64,  51,   1],
       [ 92,  64,  51,   1],
       [ 92,  64,  51,   1],
       [  0, 255,   0,   1],
       [ 92,  64,  51,   1],
       [ 92,  64,  51,   1],
       [255, 165,   0,   1],
       [255, 165,   0,   1],
       [255,   0,   0,   1],
       [255, 165,   0,   1]], dtype=uint8)

我也在重写原帖作者的函数,但你比我做得更快…而且做得比我好多了 :) - Vladimir Fokow
@VladimirFokow。好的点子。我会立即修复。 - Mad Physicist
1
@VladimirFokow。是的。完成了。 - Mad Physicist
另外,很抱歉打扰您,但我错误地将我的数组显示为NxM数组,实际上它是一个NxMxD数组,或者在代码中是array([[[0,0,0,0], [0,0,0,0], ...]])。因此,是否需要对代码进行更改? - Jamess11
@MadPhysicist 这个问题是整数溢出,但我已经修复了。再次感谢。 - Jamess11
显示剩余11条评论

2
您可以使用 numpy.apply_along_axis 函数:
np.apply_along_axis(closest_colour, axis=1, arr=img_array)

如果您只想用新值替换这些值,请让您的函数返回这些新值。


1
这是应用函数的正确方法,但它在底层只是一个for循环。此外,函数本身效率非常低。 - Mad Physicist

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