【问题标题】:What's the best way to apply a function to the row/col dimensions of an numpy array将函数应用于 numpy 数组的行/列维度的最佳方法是什么
【发布时间】:2019-03-17 06:01:40
【问题描述】:

我有一个 3 维 numpy 数组。直观地说,它是二维的,其中每个 row-col 位置代表一种 RGB 颜色,它存储为三个数字的向量。 (如果将颜色存储为三元组会容易得多!)我有一个函数(基于答案here),可以将 RGB 三元组转换为颜色名称。是否有一种简单的方法(除了嵌套循环)将该函数应用于数组的行列元素。 (直接将其应用于数组本身是行不通的,因为 numpy 会尝试将该函数应用于 RGB 向量的每个元素。)

谢谢。

【问题讨论】:

  • 一种方法是np.vectorize
  • 所以你想要一个二维字符串数组——颜色名称?如果你展示这个函数,你会得到最大的帮助,并演示如何在一个小的 3d 数组上使用它(循环是可以的)。
  • stored as a triple 是什么意思?什么的三倍。如果您的数组是 (n,m,3) 形状的,那么 arr[i,j,:] 是一个点的“三重”,不是吗?

标签: python numpy


【解决方案1】:

IIUC,你可以只用np.dstackreshape,或者np.dstackconcatenate

np.dstack(arr).reshape(-1,3)
# equivalent:
np.concatenate(np.dstack(arr))

例如:

arr = np.random.randint(0,256,(3,5,5))
>>> arr
array([[[150,  38,  34,  41,  24],
        [ 76, 135,  93, 149, 142],
        [150, 123, 198,  11,  34],
        [ 24, 179, 132, 175, 218],
        [ 46, 233, 138, 215,  97]],

       [[194, 153,  29, 200, 133],
        [247, 101,  18,  70, 112],
        [164, 225, 141, 196, 131],
        [ 15,  86,  22, 234, 166],
        [163,  97,  94, 205,  56]],

       [[117,  56,  28,   1, 104],
        [138, 138, 148, 241,  44],
        [ 73,  57, 179, 142, 140],
        [ 55, 160, 240, 189,  13],
        [244,  36,  56, 241,  33]]])

>>> np.dstack(arr).reshape(-1,3)
array([[150, 194, 117],
       [ 38, 153,  56],
       [ 34,  29,  28],
       [ 41, 200,   1],
       [ 24, 133, 104],
       [ 76, 247, 138],
       [135, 101, 138],
       [ 93,  18, 148],
       [149,  70, 241],
       [142, 112,  44],
       [150, 164,  73],
       [123, 225,  57],
       [198, 141, 179],
       [ 11, 196, 142],
       [ 34, 131, 140],
       [ 24,  15,  55],
       [179,  86, 160],
       [132,  22, 240],
       [175, 234, 189],
       [218, 166,  13],
       [ 46, 163, 244],
       [233,  97,  36],
       [138,  94,  56],
       [215, 205, 241],
       [ 97,  56,  33]])

使用the answer you linked中提供的函数,您可以获得该图像最接近的颜色:

>>> [get_colour_name(i)[1] for i in np.dstack(arr).reshape(-1,3)]
['darkseagreen', 'forestgreen', 'black', 'limegreen', 'seagreen', 'mediumaquamarine', 'grey', 'indigo', 'blueviolet', 'sienna', 'yellowgreen', 'yellowgreen', 'rosybrown', 'lightseagreen', 'darkcyan', 'midnightblue', 'palevioletred', 'blueviolet', 'powderblue', 'goldenrod', 'dodgerblue', 'chocolate', 'sienna', 'gainsboro', 'saddlebrown']

【讨论】:

  • 谢谢。听起来很有希望。我希望保持原始形状——本质上是用颜色名称替换颜色向量。所有这些重塑会比简单的嵌套循环更快吗?
  • np.array([get_colour_name(i)[1] for i in np.dstack(arr).reshape(-1,3)]).reshape(arr.shape[1:]) 将为您提供正确形状的颜色名称。而且几乎可以肯定比嵌套循环更快
【解决方案2】:

您可以使用map 并尝试例如:

list(map(your_RGB2Name_function, 2D_np_array))

假设你有一个函数,它作用于数字列表

def dummy_fct(numlist):
    return '-'.join(map(str, numlist))

dummy_fct([1,2,3])
Out: '1-2-3'

当应用于许多这些数字列表的列表时,这显然不符合预期

dummy_fct([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Out: '[1, 2, 3]-[4, 5, 6]-[7, 8, 9]'

然后你可以使用map,它遍历一个可迭代对象(这里的外部列表,或者在你的情况下,你的numpy数组的第二维)并将函数应用于每个子列表:

list(map(dummy_fct, [[1, 2, 3], [4, 5, 6], [7, 8, 9]]))
Out: ['1-2-3', '4-5-6', '7-8-9']

【讨论】:

  • 谢谢。这本质上与列表推导或嵌套循环相同。我希望有一些 numpy 的魔法可以掠过前两个维度。
  • skim off the top two dimensions 是什么意思?如果您的函数一次只能处理一个点的 3 个值,则必须以一种或另一种方式对每个点调用一次。
  • 顶级两个维度?我建议您发布您选择的函数或接受完全相同的输入参数并返回完全相同类型的结果值的虚拟函数。另外,请发布您的数据的示例数组,希望在其上应用函数,以便我们可以努力解决问题,而不是猜测您的帧条件。
【解决方案3】:

如果您的函数不是为接受向量参数而设计的,那么除了使用循环并简单地隐藏它们或者可能是一些 jit 恶作剧但我不是后者方面的专家之外,没有任何魔法。

这就是秘密应用循环的魔法,那就是np.vectorize。要使其将一维子空间传递给您的函数,您可以使用 signature 关键字

pseudo_vect_func = np.vectorize(your_func, ('O',), signature='(m)->()')

我还添加了一个 otypes 参数,因为没有它矢量化似乎会盲目地选择U1,即在第一个字母之后截断

如果你想要真正的矢量化操作,这里有一个从头开始的方法。

如果您有一个包含 (color name, (r, g, b)) 值的列表或字典,并且可以通过最小距离匹配,那么您可以利用 KDTrees 进行高效查找:

import numpy as np
from scipy.spatial import cKDTree as KDTree

# set up lookup

# borrow a list of named colors from matplotlib
from matplotlib import colors
named_colors = {k: tuple(int(v[i:i+2], 16) for i in range(1, 7, 2))
                for k, v in colors.cnames.items()}

no_match = named_colors['purple']

# make arrays containing the RGB values ...
color_tuples = list(named_colors.values())
color_tuples.append(no_match)
color_tuples = np.array(color_tuples)
# ... and another array with the names in same order
color_names = list(named_colors)
color_names.append('no match')
color_names = np.array(color_names)
# build tree
tree = KDTree(color_tuples[:-1])

def img2colornames(img, tolerance):
    # find closest color in tree for each pixel in picture
    dist, idx = tree.query(img, distance_upper_bound=tolerance)
    # look up their names
    return color_names[idx]

# an example
result = img2colornames(face(), 40)
# show a small patch
import Image
Image.fromarray(face()[410:510, 325:425]).show()
# same as names, downsampled
print(result[415:510:10, 330:425:10])

输出:

[['darkgrey' 'silver' 'dimgray' 'darkgrey' 'black' 'darkslategrey'
  'silver' 'silver' 'dimgray' 'darkgrey']
 ['darkslategrey' 'gray' 'darkgrey' 'gray' 'darkslategrey' 'gray'
  'darkgrey' 'lightsteelblue' 'darkslategrey' 'darkslategrey']
 ['darkolivegreen' 'no match' 'dimgray' 'dimgray' 'darkslategrey' 'gray'
  'slategray' 'lightslategrey' 'dimgray' 'darkslategrey']
 ['dimgray' 'dimgray' 'gray' 'dimgray' 'dimgray' 'darkslategrey'
  'dimgray' 'dimgray' 'black' 'darkseagreen']
 ['no match' 'no match' 'darkolivegreen' 'dimgray' 'dimgray' 'no match'
  'darkkhaki' 'darkkhaki' 'no match' 'dimgray']
 ['darkkhaki' 'darkkhaki' 'darkkhaki' 'tan' 'tan' 'no match'
  'darkslategrey' 'no match' 'darkslategrey' 'dimgray']
 ['no match' 'no match' 'no match' 'no match' 'no match' 'no match'
  'no match' 'no match' 'no match' 'dimgray']
 ['no match' 'black' 'no match' 'no match' 'no match' 'no match'
  'no match' 'no match' 'no match' 'darkslategrey']
 ['darkkhaki' 'no match' 'olivedrab' 'darkolivegreen' 'darkolivegreen'
  'darkolivegreen' 'darkolivegreen' 'darkolivegreen' 'darkolivegreen'
  'darkolivegreen']
 ['darkseagreen' 'no match' 'no match' 'no match' 'no match' 'no match'
  'no match' 'no match' 'no match' 'no match']]

【讨论】:

  • 谢谢。我不太关心获得最佳颜色名称。我担心从颜色元素是 3 元素列表而不是 3 元组的图像开始。据我了解您的代码,您希望输入是彩色 3 元组的 2D 数组。关键函数调用是 img2colornames(face(), 40),它(通过 np 魔法)将 img2colornames 应用于数组的每个元素。我的数组是一个二维列表数组,每个包含三个元素。因此 np magic 将尝试将 img2colornames 应用于这些 3 元素列表的每个元素。 (还是我误会了?)
  • 我想要的是一个 np-magic 版本,它可以让我告诉它在应用函数之前要在数组中走多远。例如,我希望能够编写类似np.apply(img2colornames, <array>, 2) 的东西,它将img2colornames 应用于行列级别的每个<array> 元素,即使这些元素本身就是数组。
  • @RussAbbott signature 关键字就是这样做的——仅在另一端,它让您选择要保留多少深度。在我给出的示例中,它指定您的函数需要 1D 参数并返回 0D 结果。所以vectorize 只会循环遍历前两个维度,而保留最后一个维度。
  • 谢谢。这看起来像我想要的。现在我对如何编写有效签名感到困惑。我尝试了np.vectorize(lambda lst: tuple(lst), signature='(m,n,k) -> (m,n)'),希望这会将内部列表转换为元组。但我得到一个诊断结果not a valid gufunc signature: (m,n,k) -> (m,n)。这个应该怎么写?谢谢。
  • P.S.我尝试让签名引用函数而不是整个数组。 `np.vectorize(lambda lst: tuple(lst), signature='(k) -> ()' 但我得到了同样的错误信息: not a valid gufunc signature: (k) -> ()
猜你喜欢
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 2016-03-08
  • 2021-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多