我有一个 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
函数,因为我的实现非常幼稚。
提前致谢。
您的代码几乎已经矢量化。它的几乎所有操作都可以在浮点数或浮点数数组上无差别地工作
这是矢量化版本
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)
基本上,变化是,
a<b<c
表示法)。请注意,此断言会迭代数组的所有值以检查断言。那不是免费的。所以我把它包括在内,因为你这样做了。但您需要注意,这不是编译时验证。它确实运行代码来检查所有值,这是代码所有执行时间中不可忽略的部分。if x==1
转换为某些 np.where
或掩码。但我对在浮动上使用 ==
无论如何都感到不舒服。所以我更喜欢我的方式,这不是图像的另一次迭代,它添加了一个哨兵(在唐纳德库斯的“哨兵”意义上:避免了一些字节。特殊情况)到渐变颜色,因此,即使 arr
确实是 1.0
,渐变也会发生在最后一个颜色和最后一个颜色之间)。frac
在3D数组中广播,因此可以用作3d数组color1
和color2
int
或floor
不能在numpy数组上使用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 颜色图时,您可能想浏览一下该模块所提供的功能。例如,数以亿计的现有颜色图可能适合您。或者其他方式来构建非线性颜色映射。