Tensorflow入门——图像数据处理之必背方法合集

这是我的Tensorflow图像数据处理学习笔记,博主做项目的时候由于手生,要经常查资料降低效率,于是决定将图像处理这种高频操作的方法背下来。
参考资料:《Tensorflow实战Google深度学习框架(第2版)》。本文适当分析代码,并记录实践过程中遇到的所有问题。

⚠️本文二、的代码缩进和一、同步,其所有代码都在一、定义的会话中。从三、开始零散介绍函数,所以缩进不和一、同步。

一、读取图像、解码并转换成三维矩阵

import tensorflow as tf

# 以下函数参数是我用的图片的相对目录
image_raw_data = tf.gfile.FastGFile("flower_photos/daisy/5547758_eea9edfd54_n.jpg", 'r').read()
with tf.Session() as sess:
    img_data = tf.image.decode_jpeg(image_raw_data)

利用tf.gfile.FastGfile()进行读取,此时读出的是经过压缩编码的图像数据,需要用tf.image.decode_jpeg()进行解码操作;
处理完毕,测试结果:

	print type(img_data)
	print img_data

上面两行运行结果:

<class 'tensorflow.python.framework.ops.Tensor'>
Tensor("DecodeJpeg:0", shape=(?, ?, ?), dtype=uint8)

说明img_data是张量,如果要输出三维矩阵,则需要运行:

	print sess.run(img_data)
	print img_data.eval()

⚠️Tensorflow中的eval()和普通eval()
上网查了下普通eval()的作用:
eval()是评估函数,将字符串当成有效的python表达式来求值,去掉最外层引号,然后进行进一步处理。比如eval("print(20)"),结果是输出20。在这里,eval()的作用是将解码后的图像数据转化为三位矩阵。

而Tensorflow中eval()的作用是计算一个张量的取值
《Tensorflow实战Google深度学习框架(第2版)》中有一句原话:
可以通过tf.Tensor.eval函数来计算一个张量的取值,但注意这个eval()只能在会话中使用:

with tf.Session() as sess:
	with tf.gfile.GFile("gray_picture/1.jpg", "wb") as f:
    	f.write(encode_image.eval())

如果不在会话中使用,就是错的了:

with tf.gfile.GFile("gray_picture/1.jpg", "wb") as f:
    f.write(encode_image.eval())

这样会报错:

ValueError: Cannot evaluate tensor using `eval()`: No default session is registered. Use `with sess.as_default()` or pass an explicit session to `eval(session=sess)

意思是要么在默认的会话中使用eval(),要么给eval()指定一个确定的会话,总之,tensorflow的eval()必须在会话中使用。

总之要使用张量的值,要么用sess.run(),要么用eval():

# 两行等价:
print sess.run(img_data)
print img_data.eval()

二、图像数据编码和保存

⚠️二、的代码缩进和一、同步,其所有代码都在一、定义的会话中。从三、开始零散介绍函数,所以缩进不和一、同步。

将解码的图像数据重新编码:

	encode_image = tf.image.encode_jpeg(img_data)

然后利用tf.gfile模块的函数进行图片保存操作:

	with tf.gfile.GFile("output", "wb") as f:
	    f.write(encode_image)

这样会报错:

TypeError: Expected binary or unicode string, got <tf.Tensor 'EncodeJpeg:0' shape=() dtype=string>

原因是那个f.write()参数不应当是张量(Tensor),而应该是这个张量的值,所以正确的使用方法是将其更改为:

		f.write(encode_image.eval())
		# 或者:f.write(sess.run(encode_image))

可见,如果要直接使用tensor的值,需要用eval()或者sess.run()进行转换,当然无论用哪一种,都必须在会话中完成。

三、图像大小调整

这部分的img_data均代表二中已经解码的图像数据
(1)完整保存图像信息
Tensorflow提供四种不同的方法,并将它们封装到tf.image.resize_images()中,函数使用示例:

resized_img = tf.image.resize_images(img_data, [300, 300], method=0)

img_data是二中已经进行解码的图像,method的取值代表特定图像大小调整算法:

method 算法
0 双线性插值法(Bilinear interpolation)
1 最近邻居法(Nearest neighbor interpolation)
2 双三次插值法(Bicubic interpolation)
3 面积插值法(Area interpolation)

不同算法调整出来的只有细微差别。

⚠️多数图像处理的API支持实数或者整数的输入,如果是整数输入,则API会自动将其转化为实数类型在函数内部进行处理,然后将处理结果再转化为整数返回,这样就有精度损失。所以要主动将图像数据转化为实数类型再当作函数的参数:

img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32)

(2)裁剪或者填充的方式
假设原始图像好似20002000的分辨率
将图像变成1000
1000的图像,则需要对图像进行剪裁:

cropped = tf.image.resize_image_with_crop_or_pad(img_data, 1000, 1000)

将图像变成3000*3000的图像,则需要对图像进行填充(自动进行全0填充,0代表黑色):

padded = tf.image.resize_image_with_crop_or_pad(img_data, 3000, 3000)

(3)通过比例调整图像大小
通过以下函数按比例裁剪图像:

central_cropped = tf.image.central_crop(img_data, 0.5)

上面的作用是将img_data中间部分裁剪出来,新图像长宽均为老图的0.5倍。

四、图像翻转

img_data指的是一中已经解码的图像
(1)上下翻转:

new_img_data = tf.image.flip_up_down(img_data)

(2)左右翻转:

new_img_data = tf.image.flip_left_right(img_data)

(3)沿对角线翻转:

new_img_data = tf.image.transpose_image(img_data)

对角线指的是左上右下对角线
(4)以50%概率上下或者左右翻转图像:

new_image_data = tf.image.random_flip_up_down(img_data)	# 上下
new_image_data = tf.image.random_flip_up_down(img_data)	# 左右

五、色彩调整

调整亮度、对比度、饱和度和色相在很多图像识别应用中都不会影响识别结果,反而能使模型尽可能少地受到无关因素的影响。

此部分的代码示例中,img_data依旧代表经过解码操作的图像数据。
(1)亮度
将图像的亮度增加或者减少:

adjusted = tf.image.adjust_brightness(img_data, -0.5)

作用是将图像的亮度减少0.5。
以下的作用是在[-max_delta, max_delta)内随机调整亮度:

adjusted = tf.image.random_brightness(image, max_delta)

补充一个截断操作

adjusted = tf.clip_by_value(adjusted, 0.0, 1.0)

作用是将adjusted中的值,大于1.0的部分令其等于1.0,小于0.0的部分,令其等于0.0。
为什么要截断操作:色彩调整的API可能使得像素的实数值超过[0.0, 1.0]的范围,所以应当让其值限制在[0.0, 1.0]间,否则不仅图像无法正常可视化,而且以此为输入的神经网络的训练质量也会受到影响。

(2)对比度
将图像的对比度减少到0.5

adjusted = tf.image.adjust_contrast(img_data, 0.5)

在一定范围内随机调整图像对比度:

adjusted = tf.image.random_constrast(image, lower, upper)

(3)色相
将色相增加一定值

adjusted = tf.image.adjust_hue(img_data, 0.1)

随机增加色相,max_delta的取值在[0, 0.5]之间

adjusted = tf.image.random_hue(image, max_delta)

(4)饱和度
将饱和度减少-5:

adjusted = tf.image.adjust_saturation(img_data, -5)

在[lower, upper]范围内随机调整饱和度

adjusted = tf.image.random_saturation(image, lower, upper)

(5)图像标准化
就是指将图像上的亮度均值变为0,方差变为1:

adjusted = tf.image.per_image_standardization(img_data)

六、处理标注框

(1)
标注框就是图像中的方框,用来圈出特定部位。

先介绍tf.expand_dims(),这个函数作用是将图像在指定的位置添加一维:

# 此部分代码来源:https://www.cnblogs.com/mdumpling/p/8053376.html
# 't' is a tensor of shape [2]
shape(expand_dims(t, 0)) ==> [1, 2]
shape(expand_dims(t, 1)) ==> [2, 1]
shape(expand_dims(t, -1)) ==> [2, 1]
# 't2' is a tensor of shape [2, 3, 5]
shape(expand_dims(t2, 0)) ==> [1, 2, 3, 5]
shape(expand_dims(t2, 2)) ==> [2, 3, 1, 5]
shape(expand_dims(t2, 3)) ==> [2, 3, 5, 1]

显然,函数参数中的整数代表添加的一维在新数据中的下标(第一个数组的位置下标是0,如果下标是-1则代表从最后一个向后循环寻找一位)。

在图像中添加标注框的函数是tf.image.draw_bounding_boxes(),这个函数要求图像矩阵中的数据是实数,且输入应当是多张图片组成的四维矩阵(如果只有一张图片,则其组成的四维矩阵中,第一维大小是1),以下代码将图像转化为实数矩阵,然后进一步转化为四维矩阵:

batched = tf.expand_dims(
				tf.image.convert_image_dtype(img_data, tf.float32),
				0)		# 0的意思是添加一维后,新的维应当是第0维,
						# 也就是图像矩阵变成了:[1, 这张图片本来的三个维度]

得到图像的四维实数矩阵batched后,就可以加入标注框了:

# step1,给出所有想加入的标注框
boxes = tf.constant([[[0.5, 0.5, 1.0, 1.0], [0.5, 0.0, 1.0, 0.5]]])
# step2,给图像画上指定的标注框,以上给出了两个标注框
result = tf.image.draw_bounding_boxes(batched, boxes)

注意标注框中的形式是[ y m i n , x m i n , y m a x , x m a x y_{min}, x_{min}, y_{max}, x_{max} ],这些值是按照百分比表示的相对位置,图像坐标轴原点在图像左上角,横轴是x,纵轴是y。

(2)图像随机截取
标注框在这里有两个作用:一是告诉图像随机截取算法,原图像的哪些部分是有信息量的;二是在随机截取算法完成后,输出带标注框的图像,标注框表示待截取部分的边界。

还是利用(6)中讲的标注框boxes来辅助完成,其负责告诉随机截取图像的算法哪些部分是“有信息量”的,即待截取部分只能从标注框内选择:

# begin是开始截取的位置(截取后新图片的左上角),size是截取大小,bbox_for_draw是生成的截取区域的边界
# begin,zize,bbox_for_draw是随机生成的,可能每次生成的都不一样
# 在这里参数min_object_covered=0.4代表截取部分至少包含标注框40%的内容
begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box(
		tf.shape(img_data), 
		bounding_boxes=boxes,
		min_object_covered=0.4)

# 通过标注框可视化,显示图像和带截取的部分的边界(以标注框的形式显示)
batched = tf.expand_dims(
		tf.image.convert_image_dtype(img_data, tf.float32), 0)	# 先将图片变成四维实数矩阵
image_with_box = tf.image.draw_bounding_boxes(batched, bbox_for_draw)	
# image_with_box就是带标注框的图像,标注框表示待截取区域的边界

# 获得截取后的图像:
distorted_image = tf.slice(img_data, begin, size)

最基本的图像处理方法就是这些,如果以后还有再补充。

发布了36 篇原创文章 · 获赞 41 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/umbrellalalalala/article/details/86659402