目標:介紹如何對圖像數據進行預處理使訓練獲得的神經網絡模型儘量小地被無關因素所影響。但與此同時,複雜的預處理過程可能致使訓練效率的降低。爲了減小預處理對於訓練速度的影響,TensorFlow 提供了多線程處理輸入數據的解決方案。python
TensorFlow 提供了一種統一的格式來存儲數據(TFRecord)。TFRecord 文件中的數據都是經過 tf.train.Example Protocol Buffer
的格式存儲的。git
tf.train.Example
的定義:算法
message Example { Features features = 1; }; message Features { map<string, Feature> feature = 1; }; message Features { oneof kind { BytesList bytes_list = 1; FloatList float_list = 2; Int64List int64_list = 3; } };
train.Example 的數據結構比較簡潔,包含了一個從屬性名稱到取值的字典。其中屬性名稱爲一個字符串,屬性的取值能夠爲字符串(BytesList),實數列表(FloatList)或者整數列表(Int64List)。數組
%pylab inline import tensorflow as tf import numpy as np from tensorflow.examples.tutorials.mnist import input_data
Populating the interactive namespace from numpy and matplotlib
def __int64__feature(value): '''生成整數型的屬性''' return tf.train.Feature(int64_list= tf.train.Int64List(value=[value])) def __bytes__feature(value): '''生成字符型的屬性''' return tf.train.Feature(bytes_list= tf.train.BytesList(value=[value]))
mnist = input_data.read_data_sets('E:/datasets/mnist/data', dtype= tf.uint8, one_hot= True)
Extracting E:/datasets/mnist/data\train-images-idx3-ubyte.gz Extracting E:/datasets/mnist/data\train-labels-idx1-ubyte.gz Extracting E:/datasets/mnist/data\t10k-images-idx3-ubyte.gz Extracting E:/datasets/mnist/data\t10k-labels-idx1-ubyte.gz
images = mnist.train.images # 訓練數據所對應的正確答案,能夠做爲一個屬性保存在 TFRecord 中 labels = mnist.train.labels # 訓練數據的圖像分辨率,這能夠做爲 Example 中的一個屬性 pixels = images.shape[1] num_examples = mnist.train.num_examples
# 輸出 TFRecord 文件的地址 filename = 'E:/datasets/mnist/output.tfrecords' # 建立一個 writer 來寫 TFRecord 文件 writer = tf.python_io.TFRecordWriter(filename) for index in range(num_examples): # 將圖片矩陣轉化爲一個字符串 image_raw = images[index].tostring() # 將一個樣例轉換爲 Example Protocol Buffer,並將全部的信息寫入到這個數據結構 examle = tf.train.Example(features = tf.train.Features(feature={ 'pixels': __int64__feature(pixels), 'label': __int64__feature(np.argmax(labels[index])), 'image_raw': __bytes__feature(image_raw) })) # 將一個 Example 寫入 TFRecord 文件 writer.write(examle.SerializeToString()) writer.close()
以上程序將 MNIST 數據集中全部的訓練數據存儲到一個 TFRecord 文件中。當數據量很大時,也能夠將數據寫入到多個 TFRecord 文件。網絡
import tensorflow as tf # 建立一個 reader 來讀取 TFRecord 文件中的樣例 reader = tf.TFRecordReader() # 建立一個隊列來維護輸入文件列表 filename_queue = tf.train.string_input_producer(['E:/datasets/mnist/output.tfrecords']) # 從文件中讀出一個樣例。也可以使用 read_up_to 函數一次性讀取多個樣例 _, serialized_example = reader.read(filename_queue) # 解析讀入的一個樣例。若是須要解析多個樣例,能夠用 parse_example 函數 features = tf.parse_single_example(serialized_example, features= { # TensorFlow 提供兩種不一樣的屬性解析方法。一種是 tf.FixedLenFeature,解析結果爲一個 Tensor # 另外一種方法爲 tf.VarLenFeature,解析結果爲 SparseTensor,用於處理稀疏數據 # 解析數據的格式須要和寫入數據的格式一致 'image_raw': tf.FixedLenFeature([], tf.string), 'pixels': tf.FixedLenFeature([], tf.int64), 'label': tf.FixedLenFeature([], tf.int64), }) # tf.decode_raw 能夠將字符串解析成圖片對應的像素數組 images = tf.decode_raw(features['image_raw'], tf.uint8) labels = tf.cast(features['label'], tf.int32) pixels = tf.cast(features['pixels'], tf.int32) sess = tf.Session() # 啓動多線程 coord = tf.train.Coordinator() threads = tf.train.start_queue_runners(sess= sess, coord= coord) # 每次運行能夠讀取 TFRecord 文件中的一個樣例。當全部樣例都讀完以後,在此樣例中程序會再重頭讀取 for i in range(10): image, label, pixel = sess.run([images, labels, pixels]) sess.close()
INFO:tensorflow:Error reported to Coordinator: <class 'tensorflow.python.framework.errors_impl.CancelledError'>, Run call was cancelled INFO:tensorflow:Error reported to Coordinator: <class 'tensorflow.python.framework.errors_impl.CancelledError'>, Run call was cancelled
經過對圖像的預處理,能夠儘可能避免模型受到無關因素的影響。在大部分圖像識別問題中,經過圖像預處理過程能夠提升模型的準確率。數據結構
一張 RGB 色彩模式的圖片可看做一個三維矩陣,矩陣的每個數字表示圖像上不一樣位置,不一樣顏色的亮度。然而圖像在存儲時並非直接記錄這些矩陣中的數字,而是記錄通過壓縮編碼以後的結果。所要將一張圖像還原成一個三維矩陣,須要解碼的過程。TensorFlow 提供了對 jpeg 和 png 格式圖像的編碼/解碼函數。多線程
import matplotlib.pyplot as plt import tensorflow as tf import numpy as np
# 讀取圖像的原始數據 image_raw_data = tf.gfile.FastGFile('E:/datasets/cat.jpg', 'rb').read() # 必須是 ‘rb’ 模式打開,不然會報錯
with tf.Session() as sess: # 將圖像使用 jpeg 的格式解碼從而獲得圖像對應的三維矩陣 # tf.image.decode_jpeg 函數對 png 格式的圖像進行解碼。解碼以後的結果爲一個張量, ## 在使用它的取值以前須要明確調用運行的過程。 img_data = tf.image.decode_jpeg(image_raw_data) # 輸出解碼以後的三維矩陣。 print(img_data.eval())
[[[162 161 140] [162 162 138] [161 161 137] ..., [106 140 46] [101 137 47] [102 141 52]] [[164 162 139] [163 161 136] [163 161 138] ..., [104 138 43] [102 139 46] [108 138 50]] [[165 163 140] [165 163 138] [163 161 136] ..., [104 135 41] [102 137 43] [108 139 45]] ..., [[207 200 181] [206 199 180] [206 199 180] ..., [109 84 53] [107 84 53] [106 81 50]] [[205 200 180] [205 200 180] [206 199 180] ..., [106 83 49] [105 82 51] [106 81 50]] [[205 200 180] [205 198 179] [205 198 179] ..., [108 86 49] [105 82 48] [104 81 49]]]
with tf.Session() as sess: plt.imshow(img_data.eval()) plt.show()
with tf.Session() as sess: # 數據類型轉換爲實數方便程序對圖像進行處理 img_data = tf.image.convert_image_dtype(img_data, dtype= tf.float32) img_data = tf.image.convert_image_dtype(img_data, dtype= tf.uint8) # 將表示一張圖片的三維矩陣從新按照 jpeg 格式編碼並存入文件中 ## 打開這張圖像,能夠獲得和原始圖像同樣的圖像 encoded_image = tf.image.encode_jpeg(img_data) with tf.gfile.GFile('E:/datasets/output.jpg', 'wb') as f: f.write(encoded_image.eval())
通常地,網上獲取的圖片的大小是不固定的,可是神經網絡輸入節點的個數是固定的。因此在將圖像的像素做爲輸入提供給神經網絡以前,須要將圖片的大小統一。圖片的大小調整有兩種方式:dom
tf.image.resize_images
函數中。with tf.Session() as sess: resized = tf.image.resize_images(img_data, [300, 300], method=0) # TensorFlow的函數處理圖片後存儲的數據是float32格式的,須要轉換成uint8才能正確打印圖片。 print("Digital type: ", resized.dtype) print("Digital shape: ", resized.get_shape()) cat = np.asarray(resized.eval(), dtype='uint8') # tf.image.convert_image_dtype(rgb_image, tf.float32) plt.imshow(cat) plt.show()
Digital type: <dtype: 'float32'> Digital shape: (300, 300, 3)
tf.image.resize_images
的 method
參數:函數
Method 取值 | 圖像大小調整算法 |
---|---|
0 | 雙線性插值法(Bilinear interpolation) |
1 | 最近鄰居法(Nearest neighbor interpolation) |
2 | 雙三次插值法(Bicubic interpolation) |
3 | 面積插值法(Area interpolation) |
tf.image.resize_image_with_crop_or_pad
函數能夠調整圖像的大小。若是原始圖像的尺寸大於目標圖像這個函數會自動裁取原始圖像中居中的部分;若是目標圖像大於原始圖像,這個函數會自動在原始圖像的四周填充全 \(0\) 背景。學習
with tf.Session() as sess: croped = tf.image.resize_image_with_crop_or_pad(img_data, 1000, 1000) padded = tf.image.resize_image_with_crop_or_pad(img_data, 3000, 3000) plt.imshow(croped.eval()) plt.show() plt.imshow(padded.eval()) plt.show()
tf.image.central_crop
函數能夠按比例裁剪圖像。其中比例取值:\((0, 1]\) 的實數。
with tf.Session() as sess: central_cropped = tf.image.central_crop(img_data, 0.5) plt.imshow(central_cropped.eval()) plt.show()
以上的函數都是截取或填充圖像的中間部分。使用 tf.image.crop_to_bounding_box
和 tf.image.pad_to_bounding_box
函數能夠裁剪或填充給定區域的圖像。
with tf.Session() as sess: # 上下翻轉 flipped1 = tf.image.flip_up_down(img_data) plt.imshow(flipped1.eval()) plt.show() # 左右翻轉 flipped2 = tf.image.flip_left_right(img_data) plt.imshow(flipped2.eval()) plt.show() #對角線翻轉 transposed = tf.image.transpose_image(img_data) plt.imshow(transposed.eval()) plt.show()
在不少圖像識別問題中,圖像的翻轉不會影響識別結果。因而在訓練圖像識別的神經網絡時,能夠隨機地翻轉訓練圖像,這樣訓練獲得的模型能夠識別不一樣角度的實體。於是隨機翻轉訓練圖像是一種零成本的很經常使用的圖像預處理方式。TensorFlow 提供了方便的 API 完成隨機圖像翻轉的過程。如下代碼實現了這個過程:
with tf.Session() as sess: # 以必定機率上下翻轉圖片。 flipped = tf.image.random_flip_up_down(img_data) plt.imshow(flipped.eval()) plt.show() # 以必定機率左右翻轉圖片。 flipped = tf.image.random_flip_left_right(img_data) plt.imshow(flipped.eval()) plt.show()
和圖像翻轉相似,調整圖像的亮度、對比度、飽和度和色相在不少圖像識別應用中都不會影響識別結果。因此在訓練神經網絡模型時,能夠隨機調整訓練圖像的這些屬性,從而使訓練獲得的模型儘量小的受到無關因素的影響。如下代碼能夠完成此功能:
with tf.Session() as sess: # 將圖片的亮度 -0.5。 adjusted = tf.image.adjust_brightness(img_data, -0.5) plt.imshow(adjusted.eval()) plt.show() # 將圖片的亮度 +0.5 adjusted = tf.image.adjust_brightness(img_data, 0.5) plt.imshow(adjusted.eval()) plt.show() # 在[-max_delta, max_delta)的範圍隨機調整圖片的亮度。 adjusted = tf.image.random_brightness(img_data, max_delta=0.5) plt.imshow(adjusted.eval()) plt.show()
with tf.Session() as sess: # 將圖片的對比度 -5 adjusted = tf.image.adjust_contrast(img_data, -5) plt.imshow(adjusted.eval()) plt.show() # 將圖片的對比度 +5 adjusted = tf.image.adjust_contrast(img_data, 5) plt.imshow(adjusted.eval()) plt.show() # 在[lower, upper]的範圍隨機調整圖的對比度。 lower = 7 upper = 88 adjusted = tf.image.random_contrast(img_data, lower, upper) plt.imshow(adjusted.eval()) plt.show()
with tf.Session() as sess: '''調節色相''' adjusted = tf.image.adjust_hue(img_data, 0.1) plt.imshow(adjusted.eval()) plt.show() adjusted = tf.image.adjust_hue(img_data, 0.3) plt.imshow(adjusted.eval()) plt.show() adjusted = tf.image.adjust_hue(img_data, 0.6) plt.imshow(adjusted.eval()) plt.show() adjusted = tf.image.adjust_hue(img_data, 0.9) plt.imshow(adjusted.eval()) plt.show() # 在[-max_delta, max_delta]的範圍隨機調整圖片的色相。max_delta的取值在[0, 0.5]之間。 adjusted = tf.image.random_hue(img_data, 0.5) plt.imshow(adjusted.eval()) plt.show()
with tf.Session() as sess: # 將圖片的飽和度-5。 adjusted = tf.image.adjust_saturation(img_data, -5) plt.imshow(adjusted.eval()) plt.show() # 將圖片的飽和度+5。 adjusted = tf.image.adjust_saturation(img_data, 5) plt.imshow(adjusted.eval()) plt.show() # 在[lower, upper]的範圍隨機調整圖的飽和度。 #adjusted = tf.image.random_saturation(img_data, lower, upper)
with tf.Session() as sess: # 將表明一張圖片的三維矩陣中的數字均值變爲0,方差變爲1。 image = img.imread('E:/datasets/cat.jpg') adjusted = tf.image.per_image_standardization(image) cat = np.asarray(adjusted.eval(), dtype='uint8') plt.imshow(cat) # imshow 僅支持 uint8 格式 plt.show()
標準化處理可使得不一樣的特徵具備相同的尺度(Scale)。這樣,在使用梯度降低法學習參數的時候,不一樣特徵對參數的影響程度就同樣了。tf.image.per_image_standardization(image)
,此函數的運算過程是將整幅圖片標準化(不是歸一化),加速神經網絡的訓練。主要有以下操做,\(\frac{x - mean}{adjusted\_stddev}\),其中\(x\)爲圖片的 RGB 三通道像素值,\(mean\)分別爲三通道像素的均值,\(adjusted\_stddev = \max \left(stddev, \frac{1.0}{\sqrt{image.NumElements()}}\right)\)。
- \(stddev\)爲三通道像素的標準差,image.NumElements()
計算的是三通道各自的像素個數。
import tensorflow as tf import matplotlib.image as img import matplotlib.pyplot as plt import numpy as np sess = tf.InteractiveSession() image = img.imread('E:/datasets/cat.jpg') shape = tf.shape(image).eval() h,w = shape[0],shape[1] standardization_image = tf.image.per_image_standardization(image) #標準化 fig = plt.figure() fig1 = plt.figure() ax = fig.add_subplot(111) ax.set_title('orginal image') ax.imshow(image) ax1 = fig1.add_subplot(311) ax1.set_title('original hist') ax1.hist(sess.run(tf.reshape(image,[h*w,-1]))) ax1 = fig1.add_subplot(313) ax1.set_title('standardization hist') ax1.hist(sess.run(tf.reshape(standardization_image,[h*w,-1]))) plt.ion() plt.show()
在不少圖像識別數據集中,圖像中須要關注的物體一般會被標註框圈出來。使用 tf.image.draw_bounding_boxes
函數即可以實現。
with tf.Session() as sess: # 將圖像縮小一些,這樣可視化可以讓標註框更加清晰 img_data = tf.image.resize_images(img_data, [180, 267], method= 1) # tf.image.draw_bounding_boxes 函數要求圖像矩陣中的數字爲實數類型。 # tf.image.draw_bounding_boxes 函數的輸入是一個 batch 的數據,也就是多張圖像組成的四維矩陣,因此須要將解碼後的圖像加一維 batched = tf.expand_dims(tf.image.convert_image_dtype(img_data, tf.float32), 0) # 給出每張圖像的全部標註框。一個標註框有四個數字,分別表明 [y_min, x_min, y_max, x_max] ## 注意這裏給出的數字都是圖像的相對位置。好比在 180 * 267 的圖像中 [0.35, 0.47, 0.5, 0.56] 表明了從 (63, 125) 到 (90, 150) ### 的圖像。 boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]]) result = tf.image.draw_bounding_boxes(batched, boxes) plt.imshow(result[0].eval()) plt.show()
和隨機翻轉圖像、隨機調整顏色相似,隨機截取圖像上有信息含量的部分也是一個提升模型健壯性(robustness)的一種方式。這樣可使訓練獲得的模型不受被識別物體的大小的影響。tf.image.sample_distorted_bounding_box
函數能夠完成隨機截取圖像的過程。
with tf.Session() as sess: boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]]) # 能夠經過提供隨機標註框的方式來告訴隨機截取圖像的算法哪些部分是 「有信息量」 的。 begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box( tf.shape(img_data), bounding_boxes=boxes) # 經過標註框可視化隨機截取獲得的圖像 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) plt.imshow(image_with_box[0].eval()) plt.show() # 截取隨機出來的圖像,由於算法帶有隨機成分,因此每次的結果均可能不相同 distorted_image = tf.slice(img_data, begin, size) plt.imshow(distorted_image.eval()) plt.show()