对于图像自动上色项目来说,将RGB图像转化为Lab图像的方法是必须掌握的。博主在实践过程中,在rgb2lab()
和lab2rgb()
等函数上吃了很大亏,特此总结了一些注意事项。
本文涉及颜色空间转换函数和图像数据矩阵类型两方面的内容。
《Tensorflow:实战Google深度学习框架(第2版)》中有一句话:
大多数图像处理API支持整数和实数类型的输入。如果输入是整数类型,这些API会在内部将输入转化为实数后处理,再将输出转化为整数。如果有多个处理步骤,在整数和实数之间的反复转化将导致精度损失,因此推荐在图像处理前将其转化为实数类型。
编程中常见的一种警告:
/Library/Python/2.7/site-packages/skimage/util/dtype.py:141: UserWarning: Possible precision loss when converting from float64 to uint8
.format(dtypeobj_in, dtypeobj_out))
/Library/Python/2.7/site-packages/skimage/util/dtype.py:141: UserWarning: Possible precision loss when converting from float64 to uint16
.format(dtypeobj_in, dtypeobj_out))
Possible precision loss when converting from float to uint的意思是这意味着可能发生的精度损失。
总之,我们在使用图像处理API前,最好将图像数据转化为float型。
既然这样,我们就必须知道整型和实型图像数组的区别:
整型RGB图像数组元素值范围是0~255,实型RGB图像数组元素值范围是0~1;
Lab图像数组元素范围是:L的范围是0~100(代表亮度),a和b的范围均为-128~127(代表颜色),不管Lab图像数组是整型还是实型,范围都是这个。
另外再补充一点,skimage的颜色空间转换函数(比如rgb2lab()
,lab2rgb()
)的参数可以是整型或者实型,返回值都是实型。
注意到以上所讲的整型和实型的RGB图像数组范围不一致,这是我们需要注意的地方。下面进行整型和实型的RGB图像数组的使用对比:
现在我手头上有int型RGB图像数组rgb_image
,要将图像转化为Lab格式只需下面一句:
lab_image = rgb2lab(rgb_image)
返回的lab_image
数组是float型的。
和上述情况形成对比的是:为了照顾精度,要先将rgb_image
转化为float型,再使用rgb2lab()
。将整型RGB图像数组转化为实型后,由于要符合实型RGB图像数组的数据范围,所以要除以255.0f
:
…… # "……"代表将rgb_image数组元素转化为float型的操作,由于仅做了数据类型转化,
# 所以数据范围依旧是0~255
rgb_image = rgb_image / 255.0f # 作用就是使图像数组数据范围符合实型RGB图像数组的要求。
# 如果不加这一句,则得到的lab_image图像数组范围超出正常,
# 且程序不会报错
lab_image = rgb2lab(rgb_image)
上述操作返回的lab_image
也是float型数组,注意上述代码的/255.0f
尤为重要,不然得到的lab_image
数组元素数值范围会特别大。
既然整型和实型的RGB数组使用有区别,那么整型和实型的Lab图像数组有没有区别呢?
当然没有:
若我手头上有Lab图像数组,由于整型和实型的Lab图像数组范围一致,所以使用lab2rgb()
时不需要改变数组元素值的范围:
rgb_image = lab2rgb(lab_image) # 无论lab_image是整型还是实型,这一句都足够了
根据之前所讲,当我们给rgb2lab()
传入一个实型数组时,需要检查这个数组的范围是否在0~1之间,如果你将整型RGB数组转化为实型RGB数组的操作仅仅是改变了数组元素的类型,而没有改变数值范围,则程序不会报错,但得到的Lab图像数组范围会很大。
tensorflow有个函数可以将整型RGB图像转化为实型RGB图像,转化的过程不仅改变数组元素的类型,还会自动将数值范围限制在0~1.
下面给出博主实践用的代码(注意每个代码片的缩进是同步的),结合代码理解:
import tensorflow as tf
from skimage.color import rgb2lab, lab2rgb
image_raw_data = tf.gfile.FastGFile("rgb_picture/2.jpg", 'r').read() # 读取一个RGB图像
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data) # 此时img_data是整型图像数组的tensor
print img_data.eval()
输出的是整型RGB图像数矩阵,范围在0~255:
[[[ 1 94 215]
[ 0 93 214]
[ 0 93 214]
...
[ 0 105 223]
[ 0 105 223]
[ 0 105 223]]
[[ 1 94 215]
[ 0 93 214]
[ 0 93 214]
...
利用tensorflow的函数将图像转换为float型图像
img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32)
print img_data.eval() # 输出的图片数据矩阵,数据范围都在0~1间。
# 加eval()是因为img_data是tensor,获取其值需要用eval()或者sess.run(),下同。
输出float型RGB图像数据矩阵,且数值范围自动限制在0~1间:
[[[0.00392157 0.36862746 0.8431373 ]
[0. 0.3647059 0.83921576]
[0. 0.3647059 0.83921576]
...
[0. 0.41176474 0.8745099 ]
[0. 0.41176474 0.8745099 ]
[0. 0.41176474 0.8745099 ]]
...
然后将图像转化为Lab格式():
img_data = rgb2lab(img_data.eval())
print img_data # 返回的lab图片数据矩阵是float型,但数据范围不在-1~1间
输出Lab图像数组如下,可以看到这个Lab图像数组虽为实型,但不像实型的RGB图像数组,它的数值范围和整型Lab图像数组是一致的:
[[[ 42.63580875 24.91225502 -68.08540347]
[ 42.27894392 25.14236119 -68.09560274]
[ 42.27894392 25.14236119 -68.09560274]
...
[ 46.21493852 20.89131147 -66.81553317]
[ 46.21493852 20.89131147 -66.81553317]
[ 46.21493852 20.89131147 -66.81553317]]
...
然后将这个Lab作为别的图像颜色空间转换函数的参数,不需要修改它的数值范围,不像不符合要求的RGB图像矩阵要手动除以255以确保符合实型图像数据-1~1的范围要求。
img_data = lab2rgb(img_data) # 如果你多此一举地将作为Lab图像数组的img_data
# 的数值范围限制在-1~1间,程序会报错
print img_data # 返回的rgb图片数据矩阵是float型,数据范围在-1~1间
瞧瞧这返回的RGB图像矩阵,毫无疑问,范围又被限制回-1~1间:
[[[3.92156872e-03 3.68627461e-01 8.43137327e-01]
[0.00000000e+00 3.64705893e-01 8.39215782e-01]
[0.00000000e+00 3.64705893e-01 8.39215782e-01]
...
[0.00000000e+00 4.11764747e-01 8.74509950e-01]
[0.00000000e+00 4.11764747e-01 8.74509950e-01]
[0.00000000e+00 4.11764747e-01 8.74509950e-01]]
...
(1)颜色空间转换函数的返回值均为float型,如果返回的是RGB图像矩阵,则其范围符合-1~1的范围要求
(2)颜色空间转换函数的参数可以是整型图像矩阵也可以是实型,如果参数是RGB图像数组,则传入实型数组时,要确保将数据范围限制在-1~1之间(方式是除以255),更确切讲其实是0~1间,因为RGB值不可能为负;如果传入的是Lab图像数组,不管数组元素是整型还是实型,都无需关心其范围,反倒是将数据限制在-1~1间会出错。