目录
Ch7 图像数据处理
TfRecord相关API
图像处理API
tf.train.Coordinator() 与 tf.train.start_queue_runner()
tf.train.match_filenames_once()
Dataset数据集的使用方法
参考
tf.train.Feature()
image.tostring()
tf.train.Example
tf.python_io.TFRecordWriter()
example.SerializeToString()
tf.TFRecordReader()
reader.read()
tfrecord数据文件是一种将图像数据和标签统一存储的二进制文件,能更好的利用内存,在tensorflow中快速的复制,移动,读取,存储等。
tfrecord文件包含了tf.train.Example 协议缓冲区(protocol buffer,协议缓冲区包含了特征 Features)。你可以写一段代码获取你的数据, 将数据填入到Example协议缓冲区(protocol buffer),将协议缓冲区序列化为一个字符串, 并且通过tf.python_io.TFRecordWriter class写入到TFRecords文件。我们可以看一下tf.train.Example的protobuf文件,这里已经对tf.train.Example的结构做了比较清晰的描述。
message Example {
Features features = 1;
};
message Features{
map featrue = 1;
};
message Feature{
oneof kind{
BytesList bytes_list = 1;
FloatList float_list = 2;
Int64List int64_list = 3;
}
};
三种基础数据类型:bytes,float,int64
对应tf.train中三种类型:BytesList (字符串列表), FloatList (浮点数列表), Int64List (64位整数列表),构造它们三个依照下面的方式传入相对应的value。
tf.train.BytesList(value=[context_idxs.tostring()]
tf.train.Int64List(value=[1,2])
tf.train.FloatList(value=[0.1,0.2])
要将我们的数据写入 .tfrecords 文件,需要将每一个样本数据封装为tf.train.Example格式,再将Example逐个写入文件。Example格式中的数据基础类型是tf.train.Features。
tf.train.Feature(): 它的参数是BytesList, FloatList, Int64List三种
tf.train.Feature(bytes_list=tf.train.BytesList(value=)
tf.train.Feature(int64_list=tf.train.Int64List(value=)
tf.train.Feature(float_list=tf.train.FloatList(value=)
tf.train.Features(): 它的参数是一个字典,k-v对中 v 的类型是Feature,对应每一个字段。
tf.train.Features(feature={
"k1": tf.train.Feature(bytes_list=tf.train.BytesList(value=])),
"k2": tf.train.Feature(bytes_list=tf.train.BytesList(value=)),
"k3": tf.train.Feature(float_list=tf.train.FloatList(value=)),
})
tf.train.FeatureList(): 它的参数是一个Feature的list, [Feature1, Feature2,...]
"context_idxs": tf.train.FeatureList(feature=
[tf.train.Feature(int64_list=tf.train.Int64List(value[])])
tf.train.FeatureLists(): 它的参数是一个字典,k-v对中 v 的类型是FeatureList。
feature_lists=tf.train.FeatureLists(feature_list={
"k1": tf.train.FeatureList(feature=[tf.train.Feature(int64_list=tf.train.Int64List(value=[])]),
"k2": tf.train.FeatureList(feature=[tf.train.Feature(int64_list=tf.train.Int64List(value=v))])
})
我们需要根据我们的数据,找到每一个字段应该映射为 Feature或FeatureList, 多个Feature组成Features,多个FeatureList组成FeatureLists, 然后我们就定义了我们的一个训练数据对应的 Features,FeatureLists, 再将其封装为 tf.train.Example 就可以写入 tfrecords二进制文件了。
tf.train.Example(features=): 传入的features对应一个 tf.train.Features
tf.train.SequenceExample(context=, featurelists=): 传入的context对应一个 tf.train.Features, features_lists对应一个tf.train.FeatureLists
这样就需要选择使用Example还是SequenceExample, SequenceExample多了一个featurelists, 也就是说如果数据中存在字段,我们把它映射为了FeatureList而不是Feature, 那么就要用SequenceExample, 否则用Example。
那么什么样的数据需要映射为FeatureList或Feature?
我的理解是对于长度固定的字段类型,映射为Feature, 比如分类问题中的类别这个字段一般用一个数字表示,二分类就是0或1,那么就class=0映射为tf.train.Feature(tf.train.Int64List(value=[0])), 只要这个字段包含的数据维度是固定的,就可以封装为 Feature。
对于长度不固定的字段类型,映射为FeatureList。比如NLP样本有一个特征是一句话,那么一句话的长度是不固定的,NLP中一般是先分词,然后把每个词对应为该词在字典中的索引,一句话就用一个一维整形数组来表示 [2, 3, 5, 20, ...],这个数组的长度是不固定的,我们就映射为tf.train.FeatureList(feature=[tf.train.Feature(value=[v]) for v in [2, 3, 5, 20,...] ] )。
writer = tf.python_io.TFRecordWriter(out_file)
for row in train_data:
record = tf.train.SequenceExample(row) # 将一行数据转换为定义的Example格式
# record = tf.train.Example()
writer.write(record.SerializeToString())
4.数据读取
从TFRecords文件中读取数据, 首先需要用tf.train.string_input_producer生成一个解析队列。解析器首先读取解析队列,返回serialized_example对象,之后调用tf.parse_single_example操作将Example协议缓冲区(protocol buffer)解析为张量。
if __name__==’__main__’:
tfrecords_filename = "train.tfrecords"
test_write_to_tfrecords(tfrecords_filename)
filename_queue = tf.train.string_input_producer([tfrecords_filename],) #读入流中
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([], tf.int64),
'img_raw' : tf.FixedLenFeature([], tf.string),
}) #取出包含image和label的feature对象
image = tf.decode_raw(features['img_raw'],tf.int64)
image = tf.reshape(image, [7,30])
label = tf.cast(features['label'], tf.int64)
with tf.Session() as sess: #开始一个会话
init_op = tf.initialize_all_variables()
sess.run(init_op)
coord=tf.train.Coordinator()
threads= tf.train.start_queue_runners(coord=coord)
for i in range(20):
example, l = sess.run([image,label])#在会话中取出image和label
img=Image.fromarray(example, 'RGB')#这里Image是之前提到的
img.save('./'+str(i)+'_''Label_'+str(l)+'.jpg')#存下图片
print(example, l)
coord.request_stop()
coord.join(threads)
#设置图像维度以及各维度的大小
img_data.set_shape()
# tf.image.resize_image_with_crop_or_pad():
tf.image.resize_image_with_crop_or_pad(img_data, 1000, 1000) # 设定图像的长宽,来完成对图像的裁剪或者填充,如果长宽设置得比原图像要大,则用0像素点进行填充(填充部分显示为黑色)。如果长宽设置得比原图像小,则对原图像进行裁剪。
tf.image.central_crop()
tf.image.central_crop(img_data, 0.5)截取图像中间50%的图片1
#对角线进行翻转
tf.image.transpose_image(img_data)
# 上下翻转
tf.image.flip_up_down()
# 左右翻转
tf.image.flip_left_right()
# 以一定概率上下翻转图片
tf.image.random_flip_up_down()
# 以一定的概率左右翻转图片
tf.image.random_flip_left_right()
值得注意的是,tf中以上这些图像处理函数,由于除了对图像矩阵的维度大小做修改(裁剪、填充、翻转),实际上是不涉及对图像矩阵中的各个元素做加减类的操作的。所以我们直接拿着解码后的图像矩阵传进去就行了。但是如果涉及到对图像的亮度、对比度、饱和度、色相(一般来说CNN在进行训练时会对图像随机做这四种组合操作,以减少亮度、对比度、饱和度、色相对图像识别带来的影响)的修改的话,我们在传入api之前,不仅需要把image_raw先解码成三维的像素点分布在[0-255]的image_data,还需要把uint8类型的image_data,转换为分布在0.0-1.0之间的float32的实数形式,即image_foat, image_float = tf.image.convert_image_dtype(img_data, tf.float32)有利于保持计算精度。
# 将图片亮度减去0.5
tf.image.adjust_brightness(image_float, -0.5)
# 在[-max_delta, max_delta)的范围随机调整图片的亮度。
tf.image.random_brightness(image_float, max_delta=0.5)
# 将图片的对比度-5
tf.image.adjust_contrast(image_float, -5)
# 在[lower, upper]的范围随机调整图的对比度。
tf.image.random_contrast(image_float, lower, upper)
# 调整图片的色相
tf.image.adjust_hue(image_float, 0.1)
# 在[-max_delta, max_delta]的范围随机调整图片的色相。max_delta的取值在[0, 0.5]之间。
tf.image.random_hue(image_float, max_delta)
# 将图片的饱和度-5。
tf.image.adjust_saturation(image_float, -5)
# 在[lower, upper]的范围随机调整图的饱和度
tf.image.random_saturation(image_float, lower, upper)
# 将代表一张图片的三维矩阵中的数字均值变为0,方差变为1。
tf.image.per_image_whitening(image_float)
此函数为图像生成单个随机变形的边界框。函数输出的是可用于裁剪原始图像的单个边框。返回值为3个张量:begin,size和 bboxes。前2个张量用于 tf.slice 剪裁图像。后者可以用于 tf.image.draw_bounding_boxes 函数来画出边界框。
sample_distorted_bounding_box(
image_size,
bounding_boxes,
seed=None,
seed2=None,
min_object_covered=None,
aspect_ratio_range=None,
area_range=None,
max_attempts=None,
use_image_if_no_bounding_boxes=None,
name=None
)
image_size: 是包含 [height, width, channels] 三个值的一维数组。数值类型必须是 uint8,int8,int16,int32,int64 中的一种。
bounding_boxes: 是一个 shape 为 [batch, N, 4] 的三维数组,数据类型为float32,第一个batch是因为函数是处理一组图片的,N表示描述与图像相关联的N个边界框的形状,而标注框由4个数字 [y_min, x_min, y_max, x_max] 表示出来。例如:tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]]) 的 shape 为 [1,2,4] 表示一张图片中的两个标注框;tf.constant([[[ 0. 0. 1. 1.]]]) 的 shape 为 [1,1,4]表示一张图片中的一个标注框
begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box(
tf.shape(image_float), bounding_boxes=boxes, min_object_covered=0.4)
从张量中提取切片. 此操作从由begin指定位置开始的张量input中提取一个尺寸size的切片.切片size被表示为张量形状。
t = tf.constant([[[1, 1, 1], [2, 2, 2]],
[[3, 3, 3], [4, 4, 4]],
[[5, 5, 5], [6, 6, 6]]])
tf.slice(t, [1, 0, 0], [1, 1, 3]) # [[[3, 3, 3]]]
tf.slice(t, [1, 0, 0], [1, 2, 3]) # [[[3, 3, 3],
# [4, 4, 4]]]
tf.slice(t, [1, 0, 0], [2, 1, 3]) # [[[3, 3, 3]],
# [[5, 5, 5]]]
用于增加维度
# '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]
https://blog.csdn.net/dcrmg/article/details/79780331
获取符合正则表达式的文件列表
在使用TensorFlow构建模型并进行训练时,如何读取数据并将数据恰当地送进模型,是一个首先需要考虑的问题。以往通常所用的方法无外乎以下几种:
1.建立placeholder,然后使用feed_dict将数据feed进placeholder进行使用。使用这种方法十分灵活,可以一下子将所有数据读入内存,然后分batch进行feed;也可以建立一个Python的generator,一个batch一个batch的将数据读入,并将其feed进placeholder。这种方法很直观,用起来也比较方便灵活,但是这种方法的效率较低,难以满足高速计算的需求。
2.使用TensorFlow的QueueRunner,通过一系列的Tensor操作,将磁盘上的数据分批次读入并送入模型进行使用。这种方法效率很高,但因为其牵涉到Tensor操作,不够直观,也不方便调试,所有有时候会显得比较困难。使用这种方法时,常用的一些操作包括tf.TextLineReader,tf.FixedLengthRecordReader以及tf.decode_raw等等。如果需要循环,条件操作,还需要使用TensorFlow的tf.while_loop,tf.case等操作,更是难上加难。
因此,在这种情况下,TensorFlow在后续的更新中,自1.x版本开始,逐步开发引入了tf.data.Dataset模块,使其数据读入的操作变得更为方便,而支持多线程(进程)的操作,也在效率上获得了一定程度的提高。
# 数据集由张量进行构建
tf.data.Dataset.from_tensor_slices()
# DataSet由文本文件进行构建
tf.data.TextLineDataset()
# DataSet由TFRecord文件进行构建
tf.data.TFRecordDataset()
# 返回迭代器用于遍历数据集
dataset.make_one_shot_iterator()
# 返回一个输入数据的张量
x = iterator.get_next()
需要注意一下,get_next得到的数据是会自动更新的,不需要我们反复地去进行get操作。
例如:
import tempfile
import tensorflow as tf
input_data = [1, 2, 3, 5, 8]
dataset = tf.data.Dataset.from_tensor_slices(input_data)
# 定义迭代器。
iterator = dataset.make_one_shot_iterator()
# get_next() 返回代表一个输入数据的张量。
x = iterator.get_next()
y = x * x
with tf.Session() as sess:
for i in range(len(input_data)):
print(sess.run(y))
#会打印
#1
#4
#9
#25
#64
# 对数据进行shuffle和batching操作。
dataset = dataset.shuffle(shuffle_buffer).batch(batch_size)
# 将数据集重读N份
dataset.repeat(NUM_EPOCHS)
https://zhuanlan.zhihu.com/p/40588218
https://www.jianshu.com/p/78467f297ab5
https://www.jianshu.com/p/f580f4fc2ba0