在 Numpy 中将 sRGB 向量化为线性转换
Vectorizing sRGB to linear conversion properly in Numpy
在我的图像编辑应用程序中,我有一个将 32 位浮点图像从 sRGB 转换为线性颜色的功能 space。公式为:
if value <= 0.04045: (value / 12.92)
if value > 0.04045: ((value + 0.055) / 1.055)^2.4)
我的图片是三维的numpy.ndarray,名字叫img32.
到目前为止我的实施:
boolarray = img32 <= 0.04045
lower = (img32 / 12.92) * boolarray.astype(np.int)
upper = np.power(((img32 + 0.055) / 1.055), 2.4) * np.invert(boolarray).astype(np.int)
img32 = lower + upper
因此,我正在创建一个新数组 boolarray,真值 <= 0.04045 并乘以它。
什么是更好的解决方案?
我试过类似的方法:
img32[img32 < 0.04045] = img32 / 12.92
第一步有效但第二步失败:
img32[img32 >= 0.04045] = np.power(((img32 + 0.055) / 1.055), 2.4)
可能是因为包裹在 np.power 函数中不起作用
感谢任何帮助。
一种简洁的方法是使用 np.where
,它让我们可以根据掩码在两个值之间进行选择。在我们的例子中,掩码可以是 img32 >= 0.04045
,当 True
时我们将选择 ((img32 + 0.055) / 1.055)**2.4
,否则选择 img32/12.92
。
所以,我们会有这样的实现 -
np.where( img32 >= 0.04045,((img32 + 0.055) / 1.055)**2.4, img32/12.92 )
如果您非常关心内存并希望将结果写回输入数组,您可以通过创建和有选择地设置与这两个条件相对应的元素来分三步完成,就像这样 -
mask = img32 >= 0.04045
img32[mask] = ((img32[mask] + 0.055) / 1.055)**2.4
img32[~mask] = img32[~mask] / 12.92
示例案例-
In [143]: img32 = np.random.rand(4,5).astype(np.float32)
In [144]: img32.nbytes
Out[144]: 80
In [145]: mask.nbytes
Out[145]: 20
因此,我们避免创建一个会花费我们 80
字节的输出数组,而是在掩码上使用 20
字节。因此,在内存中节省了输入数组大小的 75%
。请注意,这可能会导致性能略有下降。
b = (img32 < 0.04045)
img32[b] /= 12.92
not_b = numpy.logical_not(b)
img32[not_b] += 0.05
img32[not_b] /= 1.055
img32[not_b] **= 2.4
您也可以使用 numpy.piecewise:
In [11]: img32 = np.random.rand(800, 600).astype(np.float32)
In [12]: img_linear = np.piecewise(img32,
[img32 <= 0.04045, img32 > 0.04045],
[lambda v: v/12.92, lambda v: ((v + 0.055)/1.055)**2.4] )
In [13]: img_linear.shape
Out[13]: (800, 600)
In [14]: img_linear.dtype
Out[14]: dtype('float32')
在我的图像编辑应用程序中,我有一个将 32 位浮点图像从 sRGB 转换为线性颜色的功能 space。公式为:
if value <= 0.04045: (value / 12.92)
if value > 0.04045: ((value + 0.055) / 1.055)^2.4)
我的图片是三维的numpy.ndarray,名字叫img32.
到目前为止我的实施:
boolarray = img32 <= 0.04045
lower = (img32 / 12.92) * boolarray.astype(np.int)
upper = np.power(((img32 + 0.055) / 1.055), 2.4) * np.invert(boolarray).astype(np.int)
img32 = lower + upper
因此,我正在创建一个新数组 boolarray,真值 <= 0.04045 并乘以它。
什么是更好的解决方案?
我试过类似的方法:
img32[img32 < 0.04045] = img32 / 12.92
第一步有效但第二步失败:
img32[img32 >= 0.04045] = np.power(((img32 + 0.055) / 1.055), 2.4)
可能是因为包裹在 np.power 函数中不起作用
感谢任何帮助。
一种简洁的方法是使用 np.where
,它让我们可以根据掩码在两个值之间进行选择。在我们的例子中,掩码可以是 img32 >= 0.04045
,当 True
时我们将选择 ((img32 + 0.055) / 1.055)**2.4
,否则选择 img32/12.92
。
所以,我们会有这样的实现 -
np.where( img32 >= 0.04045,((img32 + 0.055) / 1.055)**2.4, img32/12.92 )
如果您非常关心内存并希望将结果写回输入数组,您可以通过创建和有选择地设置与这两个条件相对应的元素来分三步完成,就像这样 -
mask = img32 >= 0.04045
img32[mask] = ((img32[mask] + 0.055) / 1.055)**2.4
img32[~mask] = img32[~mask] / 12.92
示例案例-
In [143]: img32 = np.random.rand(4,5).astype(np.float32)
In [144]: img32.nbytes
Out[144]: 80
In [145]: mask.nbytes
Out[145]: 20
因此,我们避免创建一个会花费我们 80
字节的输出数组,而是在掩码上使用 20
字节。因此,在内存中节省了输入数组大小的 75%
。请注意,这可能会导致性能略有下降。
b = (img32 < 0.04045)
img32[b] /= 12.92
not_b = numpy.logical_not(b)
img32[not_b] += 0.05
img32[not_b] /= 1.055
img32[not_b] **= 2.4
您也可以使用 numpy.piecewise:
In [11]: img32 = np.random.rand(800, 600).astype(np.float32)
In [12]: img_linear = np.piecewise(img32,
[img32 <= 0.04045, img32 > 0.04045],
[lambda v: v/12.92, lambda v: ((v + 0.055)/1.055)**2.4] )
In [13]: img_linear.shape
Out[13]: (800, 600)
In [14]: img_linear.dtype
Out[14]: dtype('float32')