这是我的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的分辨率
将图像变成10001000的图像,则需要对图像进行剪裁:
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} ymin,xmin,ymax,xmax],这些值是按照百分比表示的相对位置,图像坐标轴原点在图像左上角,横轴是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)
最基本的图像处理方法就是这些,如果以后还有再补充。