PIL:通过将渐变应用于 NumPy 数组来生成图像

问题描述 投票:0回答:1

我有一个 2d NumPy 数组,其值从 0 到 1。我想将此数组转换为 Pillow 图像。我可以执行以下操作,这给了我一个漂亮的灰度图像:

arr = np.random.rand(100,100)
img = Image.fromarray((255 * arr).astype(np.uint8))

现在,我想应用自定义渐变,而不是制作灰度图像。

示例:如果我的渐变是

[color1, color2, color3]
,那么所有
0
都应该是
color1
,所有
1
都应该是
color3
,而
0.25
应该介于
color1
color2
之间。我已经能够编写一个简单的函数来执行此操作:

gradient = [(0, 0, 0), (255, 80, 0), (0, 200, 255)] # black -> orange -> blue

def get_color_at(x):
    assert 0 <= x <= 1
    n = len(gradient)
    if x == 1:
        return gradient[-1]
    pos = x * (n - 1)
    idx1 = int(pos)
    idx2 = idx1 + 1
    frac = pos - idx1
    color1 = gradient[idx1]
    color2 = gradient[idx2]
    color_in_between = [round(color1[i] * (1 - frac) + color2[i] * frac) for i in range(3)]
    return tuple(color_in_between)

因此

get_color_at(0)
返回
(0,0,0)
并且
get_color_at(0.75)
等于
(153, 128, 102)
,这是介于橙色和蓝色之间的棕褐色/棕色。

现在,我如何将其应用到原始 NumPy 数组?我不应该直接将

get_color_at
应用于 NumPy 数组,因为这仍然会给出一个 2d 数组,其中每个元素都是一个三元组。相反,我想我想要一个形状为
(n, m, 3)
的数组,这样我就可以将其提供给 Pillow 并创建一个 RGB 图像。

如果可能的话,我更愿意尽可能使用矢量化操作 - 我的输入数组非常大。如果有内置功能可以使用自定义渐变,我也很乐意使用它而不是我自己的

get_color_at
函数,因为我的实现非常幼稚。

提前致谢。

python numpy python-imaging-library colormap
1个回答
0
投票

方法 1:代码矢量化

您的代码几乎已经矢量化。它的几乎所有操作都可以在浮点数或浮点数数组上无差别地工作

这是矢量化版本

def get_color_atArr(arr):
    assert (arr>=0).all() and (arr<=1).all()
    n=len(gradient)
    gradient.append(gradient[-1])
    gradient=np.array(gradient, dtype=np.uint8)
    pos = arr*(n-1)
    idx1 = pos.astype(np.uint8)
    idx2 = idx1+1
    frac = (pos - idx1)[:,:,None]
    color1 = gradient[idx1]
    color2 = gradient[idx2]
    color_in_between = np.round(color1*(1-frac) + color2*frac).astype(np.uint8)

基本上,变化是,

  • 断言(不能在 numpy 数组中使用
    a<b<c
    表示法)。请注意,此断言会迭代数组的所有值以检查断言。那不是免费的。所以我把它包括在内,因为你这样做了。但您需要注意,这不是编译时验证。它确实运行代码来检查所有值,这是代码所有执行时间中不可忽略的部分。
  • 更多的是一种实现选择,而不是矢量化步骤(代码的纯翻译会将
    if x==1
    转换为某些
    np.where
    或掩码。但我对在浮动上使用
    ==
    无论如何都感到不舒服。所以我更喜欢我的方式,这不是图像的另一次迭代,它添加了一个哨兵(在唐纳德库斯的“哨兵”意义上:避免了一些字节。特殊情况)到渐变颜色,因此,即使
    arr
    确实是
    1.0
    ,渐变也会发生在最后一个颜色和最后一个颜色之间)。
  • frac
    在3D数组中广播,因此可以用作3d数组
    color1
    color2
  • 上的系数
  • 当然,
    int
    floor
    不能在numpy数组上使用

方法2:不重复发明轮子

Matplotlib(我确信还有许多其他库)已经有一个完整的颜色图模块来处理这种转换。让我们使用它吧

thresh=np.linspace(0,1,len(gradient))
cmap=LinearSegmentedColormap.from_list('mycmap', list(zip(thresh, np.array(gradient)/255.0)), N=256*len(gradient))
arr2 = cmap(arr)[:,:,:3]

这是使用

LinearSegmentedColormap
构建自定义颜色图。它采用
(threshold, color)
对列表作为第二个参数。 例如
[(0, (0,0,0)), (0.3, (1,0,0)), (0.8, (0,1,0)), (1, (0,0,1))]
,当 x 从 0 到 0.3 时,颜色图从黑色读取,然后当 x 从 0.3 到 0.8 时从红色读取到绿色,然后从绿色读取到蓝色。

在这种情况下,你的渐变可以转换为这样的列表,只需一个带有 linspace 的 zip。

它需要一个

N=
参数,因为它创建所有可能颜色的离散化(中间有插值)。这里我采取了一个夸张的选项(我的 N 超过了可以存在的不同颜色的最大数量,一次
uint8
d)

此外,由于它返回一个 RGBA 数组,并且为了与您所做的保持完全相同,我使用

[:,:,:3]
删除了 A。

当然,这两种方法都需要最终翻译成PIL,但你已经知道如何做到这一点。对于这个,它还需要 0 到 255 之间的映射,我可以用您自己的代码来完成:

Image.fromarray((255 * arr).astype(np.uint8))

请注意,在使用 matplotlib 颜色图时,您可能想浏览一下该模块所提供的功能。例如,数以亿计的现有颜色图可能适合您。或者其他方式来构建非线性颜色映射。

© www.soinside.com 2019 - 2024. All rights reserved.