如何用NumPy直接将图像导入为RGB三元组构成的2D数组?
我想知道是否存在NumPy函数或方法,能直接把图像导入成由RGB三元组组成的2D数组,而不是常规的数值3D数组。
目前我用的解法是:
from PIL import Image import numpy as np dt = np.dtype([('R','u1'), ('G','u1'), ('B','u1')]) img_in_2D_array = np.asarray(img, dtype='u1').view(dtype=dt).squeeze()
但我想找更简便的方式,比如通过某个函数/方法的参数直接实现,望指教。
我希望把图像表示成三元组的2D数组,是为了方便操作RGB值,比如将彩色图像转为黑白(我知道用np.average()能实现,但更复杂的操作可能不好直接完成)。
示例运行结果:
img_in_2D_array.shape # 返回 (375, 500) img_in_2D_array[0,0] # 返回 (58, 76, 28)
显示图像的代码:
Image.fromarray(img_in_2D_array.astype(dt), 'RGB')
注:我试过对3D图像数组用np.apply_along_axis(),但速度明显比2D数组的方式慢。
研究更新
我意识到上面的img_in_2D_array并不是新数组,只是现有3D数组的视图。这个视图表现得像三元组数组,但内存里还是原始的3D数组,不用担心内存问题。
以下是这个“2D数组”的基本属性示例:
from PIL import Image import numpy as np arr3D = np.asarray(img, dtype=np.uint8) dt = np.dtype([('R', 'u1'), ('G', 'u1'), ('B', 'u1')]) arr2D = arr3D.view(dtype=dt).squeeze() arr2D.base is arr3D # 返回True,说明是视图 arr2D[0,0] # 返回 (58, 76, 28),看起来像元组 type(arr2D[0,0]) # 返回numpy.void,不是物理元组 arr2D.shape # 返回 (375, 500),表现为2D数组 arr2D.ndim # 返回2 arr2D.nbytes # 返回562500,和arr3D.nbytes相等
这种方式能方便高效地应用向量化函数:
@np.vectorize def Y(args): R, G, B = args return 0.299 * R + 0.587 * G + 0.114 * B grey = Y(arr2D) Image.fromarray(grey.astype('u1'), 'L')
除了np.apply_along_axis,我不知道怎么直接对arr3D应用函数Y,但正如Cris指出的,这个方法只是隐藏了循环,速度很慢。用普通版Y测试:
def Y(args): R, G, B = args return 0.299 * R + 0.587 * G + 0.114 * B grey = np.apply_along_axis(Y, 2, arr3D) Image.fromarray(grey.astype('u1'), 'L')
功能一样,但速度慢4倍左右。
我觉得用2D视图的优势是能轻松给RGB分量应用任意函数Y,不过可能我忽略了更直接的方法,毕竟我还在学习阶段。
彩色转灰度方法速度测试
在Cris的建议下,我对比了所有能想到的彩色图像转灰度的方法,以下是重复100次的测试结果(图像尺寸(375, 500),uint8类型,旧款Intel i5处理器,Win10系统):
方法1:0.40 - 0.50 秒
grey = arr3D @ [0.299, 0.587, 0.114]
方法2:0.50 - 0.70 秒
grey = arr3D[:,:,0]*0.299 + arr3D[:,:,1]*0.587 + arr3D[:,:,2]*0.114
方法3:0.95 - 1.05 秒
grey = (arr3D * [0.299, 0.587, 0.114]).sum(axis=2)
方法4:1.0 - 1.10 秒
grey = np.average(image, weights=[0.299, 0.587, 0.114], axis=2)
方法5:14 - 15 秒
grey = Y(arr2D) # 参见上述定义的Y和arr2D
方法6:~300 秒
grey = np.apply_along_axis(Y, 2, arr3D) # 参见上述定义的Y
测试结果验证了Cris的建议:如果看重速度,要避免用@np.vectorize,更别说np.apply_along_axis了。有意思的是,np.average和.sum的速度只有矩阵乘积或直接数组运算的一半。
循环解法(供参考)
方法7:~225 秒
grey = loopY(arr3D)
其中loopY定义:
def loopY(image): res = np.empty(image.shape[:-1]) for i in range(image.shape[0]): for j in range(image.shape[1]): res[i, j] = Y(image[i, j]) return res
可以看到,np.apply_along_axis是最慢的方案,甚至比手动循环还慢。
内容的提问来源于stack exchange,提问作者isCzech

