tensorflow學習筆記——影象資料處理
喜歡攝影的盆友都知道影象的亮度,對比度等屬性對影象的影響是非常大的,相同物體在不同亮度,對比度下差別非常大。然而在很多影象識別問題中,這些因素都不應該影響最後的結果。所以本文將學習如何對影象資料進行預處理使訓練得到的神經網路模型儘可能小地被無關因素所影響。但與此同時,複雜的預處理過程可能導致訓練效率的下降。為了減少預處理對於訓練速度的影響,後面也學習多執行緒處理輸入資料的解決方案。
在大部分影象識別問題中,通過影象預處理過程可以提高模型的準確率。當然在TensorFlow中提供了幾類影象處理函式,下面一一學習。
1,影象編碼處理
我們知道一張RGB色彩模式的影象可以看成一個三維矩陣,矩陣中的每個數表示了影象上不同位置,不同顏色的亮度。然而影象在儲存時並不是直接記錄這些矩陣中的數字,而是記錄經過壓縮編碼之後的結果。所以要將一張影象還原成一個三維矩陣,需要解碼的過程。TensorFlow提供了對JPEG和png格式影象的編碼/解碼函式。以下程式碼示範瞭如何使用TensorFlow中對 JPEG 格式影象的編碼/解碼函式。
#_*_coding:utf-8_*_ # matplotlib.pyplot 是一個python 的畫圖工具。下面用這個來視覺化 import matplotlib.pyplot as plt import tensorflow as tf # 讀取影象的原始資料 picture_path = 'kd.jpg' image_raw_data = tf.gfile.FastGFile(picture_path, 'rb').read() with tf.Session() as sess: # 將影象使用JPEG的格式解碼從而得到影象對應的三維矩陣 # TensorFlow提供了 tf.image.decode_png 函式對png格式的影象進行解碼 # 解碼之後的結果為一個張量,在使用它的取值之前需要明確呼叫執行的過程 img_data = tf.image.decode_jpeg(image_raw_data) # 輸出解碼之後的三維矩陣 # print(img_data.eval()) ''' # 輸出解碼之後的三維矩陣如下: [[[4 6 5] [4 6 5] [4 6 5] ... [35 29 31] [26 20 24] [25 20 26]]] ''' # 使用 pyplot工具視覺化得到的影象 plt.imshow(img_data.eval()) plt.show() # 將資料的型別轉化成實數方便下面的樣例程式對影象進行處理 # img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32) # 將表示一張影象的三維矩陣重新按照JPEG格式編碼並存入檔案中 # 開啟這種圖片可以得到和原始影象一樣的影象 encoded_image = tf.image.encode_jpeg(img_data) with tf.gfile.GFile('output.jpg', 'wb') as f: f.write(encoded_image.eval())
下圖顯示了上面程式碼可視化出來的一張影象:
2,影象大小調整
一般來說,網路上獲取的影象大小是不固定的,但神經網路輸入節點的個數是固定的。所以在將影象的畫素作為輸入提供給神經網路之前,需要先將影象的大小統一。這就是影象大小調整需要完成的任務。影象大小調整有兩種方式,第一種是通過演算法使得新的影象儘量儲存原始影象上的所有資訊。TensorFlow提供了四種不同的方法,並且將他們封裝到了 tf.image.resize_images 函式,下面程式碼示範瞭如何使用這個函式。
# 載入原始影象 # 讀取影象的原始資料,然後解碼 picture_path = 'kd.jpg' image_raw_data = tf.gfile.FastGFile(picture_path, 'rb').read() with tf.Session() as sess: # 將影象使用JPEG的格式解碼從而得到影象對應的三維矩陣 # TensorFlow提供了 tf.image.decode_png 函式對png格式的影象進行解碼 # 解碼之後的結果為一個張量,在使用它的取值之前需要明確呼叫執行的過程 img_data = tf.image.decode_jpeg(image_raw_data) # 通過tf.image.resize_images函式調整影象的大小 # 這個函式第一個引數為原始影象,第二個和第三個引數為調整後圖像的大小 # method 引數給出了調整影象大小的演算法 resized = tf.image.resize_images(img_data, 300, 300, method=0) # 輸出調整後圖像的大小,此處的結果為(300, 300, ?)表示影象的大小為300*300 # 但是在影象的深度還沒有明確設定之前會是問號 print(img_data.get_shape)
下圖給出了 tf.image.resize_images 函式的 method 引數取值對應的影象大小調整演算法
例項程式碼如下:
#_*_coding:utf-8_*_ import matplotlib.pyplot as plt import tensorflow as tf import numpy as np # 讀取影象的原始資料 picture_path = 'kd.jpg' image_raw_data = tf.gfile.FastGFile(picture_path, 'rb').read() with tf.Session() as sess: # 將影象使用JPEG的格式解碼從而得到影象對應的三維矩陣 # TensorFlow提供了 tf.image.decode_png 函式對png格式的影象進行解碼 # 解碼之後的結果為一個張量,在使用它的取值之前需要明確呼叫執行的過程 img_data = tf.image.decode_jpeg(image_raw_data) img_data.set_shape([300, 300, 3]) print(img_data.get_shape()) # (300, 300, 3) # 重新調整圖片的大小 resized = tf.image.resize_images(img_data, [260, 260], method=0) # TensorFlow的函式處理圖片後儲存的資料是float32格式的, # 需要轉換成uint8才能正確列印圖片。 resized_photo = np.asarray(resized.eval(), dtype='uint8') # tf.image.convert_image_dtype(rgb_image, tf.float32) plt.imshow(resized_photo) plt.show()
結果如下:
當然,我們也可以進行雙三插值法,面積插值法,最近鄰插值法進行處理。不同演算法調整處理的結果會有細微差別,但不會相差太遠。
3,裁剪和填充
除了把整張影象資訊完整儲存,TensorFlow還提供了API對影象進行裁剪或者填充。TensorFlow提供了 tf.image.crop_to_bounding_box 函式 和 tf.image.pad_to_bounding_box 函式來剪裁或者填充給定區域的影象。這兩個函式都要求給出的尺寸滿足一定的要求,否則程式會報錯。比如在使用 tf.image.crop_to_bounding_box 函式時候,TensorFlow要求提供的影象尺寸要大於目標尺寸,也就是要求原始影象能夠裁剪出目標影象的大小。下面程式碼展示了通過 tf.image_resize_image_with_crop_or_pad 函式來調整影象大小的功能。
# 通過 tf.image_resize_image_with_crop_or_pad 函式調整影象的大小 # 這個函式的第一個引數為原始影象,後面兩個引數是調整後的模板影象大小 # 如果原始影象的尺寸大於目標影象,那麼這個函式會自動擷取影象原始影象中矩陣的部分 # 如果目標影象大於原始影象,這個函式會自動在原始影象的四周填充全0的背景 # 因為我這個圖片是500*468,所以第一個命令自動裁剪,第二個命名自動填充 croped = tf.image.resize_image_with_crop_or_pad(img_data, 300, 300) padded = tf.image.resize_image_with_crop_or_pad(img_data, 600, 600)
下面示例看一下圖片:
4,擷取中間50%的圖片
TensorFlow還支援通過比例調整影象大小,函式如下:
# 通過 tf.image.central_crop() 函式可以按比例裁剪影象 # 函式第一個引數為原始影象,第二個為調整比例 這個比例是需要在(0, 1] 的實數 # 下面意思是擷取中間百分之五十 central_cropped = tf.image.central_crop(img_data, 0.5)
擷取中間50%的結果展示如下:
5,翻轉圖片
TensorFlow提供了一些函式來支援對影象的翻轉,下面程式碼實現了將影象上下反轉,左右反轉,以及沿對角線翻轉的功能。
# 上下翻轉 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() # 以一定概率上下翻轉圖片。 #flipped = tf.image.random_flip_up_down(img_data) # 以一定概率左右翻轉圖片。 #flipped = tf.image.random_flip_left_right(img_data)
結果展示如下:
在很多影象識別問題中,影象的翻轉不會影響識別的結果。於是在訓練影象識別的神經網路模型時,可以隨機地翻轉訓練影象。這樣訓練得到的模型可以識別不同角度的實體。比如假設在訓練資料中所有的貓頭都是向右的,那麼訓練出來的模型就無法很好的識別貓頭向左向右的貓。雖然這個問題可以通過收集更多的訓練資料來解決,但是通過隨機翻轉訓練影象的方式可以在零成本的情況下很大的緩解該問題。所以隨機翻轉訓練影象是一種很常見的影象預處理方式。
6,影象色彩調整
和影象翻轉類似,調整影象的亮度,對比度,飽和度和色相在很多影象識別應用中都不會影響識別結果。所以在訓練神經網路模型時,可以隨機調整訓練影象的這些屬性,從而使得訓練得到的模型儘可能小的受到無關因素的影響。Tensorflow提供了調整這些色彩相關屬性的API,以下程式碼顯示瞭如何修改影象的亮度:
# 將圖片的亮度-0.5。 #adjusted = tf.image.adjust_brightness(img_data, -0.5) # 將圖片的亮度-0.5 #adjusted = tf.image.adjust_brightness(img_data, 0.5) # 在[-max_delta, max_delta)的範圍隨機調整圖片的亮度。 adjusted = tf.image.random_brightness(img_data, max_delta=0.5) # 將圖片的對比度-5 #adjusted = tf.image.adjust_contrast(img_data, -5) # 將圖片的對比度+5 #adjusted = tf.image.adjust_contrast(img_data, 5) # 在[lower, upper]的範圍隨機調整圖的對比度。 #adjusted = tf.image.random_contrast(img_data, lower, upper) plt.imshow(adjusted.eval()) plt.show()
結果展示如下:
7,影象色相調整
下面程式碼顯示瞭如何調整影象的色相:
# 下面四條命令分別將色相加0.1 0.3 0.6 0.9 adjusted = tf.image.adjust_hue(img_data, 0.1) #adjusted = tf.image.adjust_hue(img_data, 0.3) #adjusted = tf.image.adjust_hue(img_data, 0.6) #adjusted = tf.image.adjust_hue(img_data, 0.9) # 在[-max_delta, max_delta]的範圍隨機調整圖片的色相。max_delta的取值在[0, 0.5]之間。 #adjusted = tf.image.random_hue(image, max_delta) # 將圖片的飽和度-5。 #adjusted = tf.image.adjust_saturation(img_data, -5) # 將圖片的飽和度+5。 #adjusted = tf.image.adjust_saturation(img_data, 5) # 在[lower, upper]的範圍隨機調整圖的飽和度。 #adjusted = tf.image.random_saturation(img_data, lower, upper) # 將代表一張圖片的三維矩陣中的數字均值變為0,方差變為1。 #adjusted = tf.image.per_image_whitening(img_data) plt.imshow(adjusted.eval()) plt.show()
結果展示一個調整色相0.1的圖片:
8,影象標準化
影象標準化的過程,其實就是將影象上的亮度均值變為0, 方差變為1,下面程式碼實現了這個功能:
# 影象標準化 # 將代表一張影象的三維矩陣中的數字均值變為0, 方差變為1 adjusted = tf.image.per_image_standardization(img_data) plt.imshow(adjusted.eval()) plt.show()
結果如下:
9,處理標註框
在很多影象識別的資料集中,影象中需要關注的物體通常會被標註圈出來,TensorFlow提供了一些工具來處理標註框。下面程式碼展示瞭如何通過tf.image.draw_bounding_boxes函式在影象中加入標註框。
# 將影象縮小一些,這樣視覺化能讓標註框更加清楚 img_data = tf.image.resize_images(img_data, 180, 267, method=1) # tf.image.draw_bounding_boxes 函式要求影象矩陣中的數字為實數 # 所以需要先將影象矩陣轉化為實數型別,次函式輸入的是一個batch的資料 #也就是多張影象組成的四維矩陣,所以需要將解碼之後的影象矩陣加一維 batched = tf.expand_dims(tf.image.convert_image_dtype(img_data, tf.float32), 0) # 給出每一張影象的所有標註框,一個標註框有四個數字,分別代表[ymin, xmin, ymax, xmax] # 注意這裡給出的數字都是影象的相對位置,比如在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.eval()) plt.show()
和隨機翻轉影象,隨機調整顏色類似,隨機擷取影象上有資訊含量的部分也是一個提高模型健壯性(robustness)的一種方式。這樣可以使訓練得到的模型不受被識別物體大小的影響。下面程式展示瞭如何通過 tf.image.sample_distorted_bounding_box 函式來完成隨機擷取影象的過程。
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) # 擷取隨機出來的影象,因為演算法帶有隨機成分,所以每次得到的結果會有所不同 distorted_image = tf.slice(img_data, begin, size) plt.imshow(distorted_image.eval()) plt.show()
結果如下:
10,影象預處理完整樣例
上面學習了TensorFlow提供的主要影象處理函式,在解決真實的影象識別問題時,一般會同時使用多種處理方法,下面學習一個完整的樣例程式展示如何將不同的影象處理函式結合成一個完整的影象預處理流程。以下TensorFlow程式完成了從影象片段擷取,到影象大小調整再到影象翻轉及色彩調整的整個影象預處理過程。
#_*_coding:utf-8_*_ import tensorflow as tf import numpy as np import matplotlib.pyplot as plt def distort_color(image, color_ordering=0): ''' 隨機調整圖片的色彩,定義兩種順序 下面將給定一張影象,隨機調整影象的色彩,因為點贊亮度,對比度,飽和度和色相的 順序將會影響最後得到的結果,所以可以定義多種不同的順序,具體使用哪一種順序可以 在訓練資料預處理時隨機的選擇一種,這樣可以進一步降低無關元素對模型的影響 :param image: :param color_ordering: :return: ''' if color_ordering == 0: image = tf.image.random_brightness(image, max_delta=32./255.) image = tf.image.random_saturation(image, lower=0.5, upper=1.5) image = tf.image.random_hue(image, max_delta=0.2) image = tf.image.random_contrast(image, lower=0.5, upper=1.5) else: image = tf.image.random_saturation(image, lower=0.5, upper=1.5) image = tf.image.random_brightness(image, max_delta=32./255.) image = tf.image.random_contrast(image, lower=0.5, upper=1.5) image = tf.image.random_hue(image, max_delta=0.2) #還可以elif 去設定其他的排列,但是這裡就不再一一列出了。 return tf.clip_by_value(image, 0.0, 1.0) def preprocess_for_train(image, height, width, bbox): ''' 對圖片進行預處理,將圖片轉化成神經網路的輸入層資料 給定一張解碼後的影象,目標影象的尺寸以及影象上的標註框 次函式可以對給出的額影象進行預處理,這個函式的輸入影象是影象識別問題中 原始的訓練影象,而輸出則是神經網路模型的輸入層,注意這裡只處理模型的訓練資料 # 對於預測的資料,一般不需要使用隨機變換的步驟 :param image: :param height: :param width: :param bbox: :return: ''' # 檢視是否存在標註框。如果沒有提供標註框,則認為整個影象就是需要關注的部分 if bbox is None: bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4]) # 轉換影象張量的型別 if image.dtype != tf.float32: image = tf.image.convert_image_dtype(image, dtype=tf.float32) # 隨機的擷取圖片中一個塊。減少需要關注的物體大小對影象識別演算法ade影響 bbox_begin, bbox_size, _ = tf.image.sample_distorted_bounding_box( tf.shape(image), bounding_boxes=bbox) bbox_begin, bbox_size, _ = tf.image.sample_distorted_bounding_box( tf.shape(image), bounding_boxes=bbox) distorted_image = tf.slice(image, bbox_begin, bbox_size) # 將隨機擷取的圖片調整為神經網路輸入層的大小。大小調整的演算法是隨機選擇的 distorted_image = tf.image.resize_images(distorted_image, [height, width], method=np.random.randint(4)) # 隨機左右反轉影象 distorted_image = tf.image.random_flip_left_right(distorted_image) # 使用一種隨機的順序調整影象色彩 distorted_image = distort_color(distorted_image, np.random.randint(2)) return distorted_image # 讀取影象 image_raw_data = tf.gfile.FastGFile("kd.jpg", "rb").read() with tf.Session() as sess: img_data = tf.image.decode_jpeg(image_raw_data) boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]]) for i in range(9): result = preprocess_for_train(img_data, 299, 299, boxes) plt.imshow(result.eval()) plt.show()
執行上面的diam,可以得到下圖9張不同的影象,因為執行6次影象預處理。這樣就可以通過一張訓練影象衍生出很多訓練樣本,通過將訓練影象進行預處理,訓練得到的神經網路模型可以識別不同大小,方位,色彩等方面的例項。
原圖如下:
處理之後的圖片如下:
11,python glob.glob的使用
函式功能:匹配所有的符合條件的問卷,並將其以 list的形式返回
示例,當前資料夾中有如下檔案:
import glob list = glob.glob(‘*g’) print(list)
結果如下:
['dog.1012.jpg', 'dog.1013.jpg', 'dog.1014.jpg', 'dog.1015.jpg', 'dog.1016.jpg']
12,tensorflow tf.gfile的使用
tf.gfile()函式和python中os模組非常的相似,一般都可以使用os模組代替。
此函式的作用是讀寫檔案,控制代碼具有 .read() 方法
''' 本程式碼也是載入圖pb檔案並獲取節點張量控制代碼的標準流程, feed_dict輸入節點 & sess.run(輸出節點)就可以使用模型了 ''' # 使用tf.gfile.FastGFile()函式的方法 # with tf.gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f: with open(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f: # 使用open()函式的方法 graph_def = tf.GraphDef() # 生成圖 graph_def.ParseFromString(f.read()) # 圖載入模型 # 從圖上讀取張量,同時把圖設為預設圖 bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def( graph_def, return_elements=[BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME]) print(gfile.FastGFile(image_path, 'rb').read() == open(image_path, 'rb').read()) # True
13,python np.squeeze()的使用
函式的作用:從陣列的形狀中刪除單維條目,即把shape 中為1 的維度去掉。
#_*_coding:utf-8_*_ import numpy as np a = np.array([[1], [2], [3]]) print(a) print(a.shape) ''' 輸出結果如下: [[1] [2] [3]] (3, 1) ''' # 應用squeeze() 後 a1 = np.squeeze(a) print(a1) print(a1.shape) ''' 輸出結果如下: [1 2 3] (3,) '''
應用:在預測分析中用於處理預測陣列和真實陣列以方便計算預測值和真實值之間的誤差。
predictions = np.array(predictions).squeeze() labels = np.array(labels).squeeze() rmse = np.sqrt(((predictions - labels) ** 2).mean(axis=O))
14,tf.global_variables_initializer() 函式與tf.local_variables_initializer() 函式的區別
當我們訓練自己的神經網路的時候,無一例外的就是都會加上一句sess.run(tf.global_variables_initializer() ),這行程式碼的官方解釋是 初始化模型的引數。
tf.global_variables_initializer() 新增節點用於初始化全域性的變數(GraphKeys.VARIABLES)。返回一個初始化所有全域性變數的操作(Op)。在我們構建完整個模型並在會話中載入模型後執行這個節點,能夠將所有的變數一步到位的初始化,非常的方便。通過 feed_dict,我們也可以將指定的列表傳遞給它,只初始化列表中的變數。
示例程式碼如下:
sess.run(tf.global_variables_initializer(), feed_dict={x: val_x, y: val_y, keep_prob: 1.0}
tf.local_variables_initializer() 新增節點用於初始化區域性的變數(GraphKeys.LOCAL_VARIABLE), 返回一個初始化所有區域性變數的操作(Op)。GraphKeys.LOCAL_VARIABLE 中的變數指的是被新增如圖中,但是未被儲存的變數。示例程式碼與上面的類似。
注意:在使用區域性變數時必須使用 tf.local_variables_initializer() 初始化器,在使用全域性變數時必須使用 tf.global_variables_initializer() 初始化器,不然會報錯,報錯程式碼類似下面:
tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value matching_filenames [[Node: _retval_matching_filenames_0_0 = _Retval[T=DT_STRING, index=0, _device="/job:localhost/replica:0/task:0/cpu:0"](matching_filenames)]]
此文是自己的學習筆記總結,學習於《TensorFlow深度學習框架》,俗話說,好記性不如爛筆頭,寫寫總是好的,所以若侵權,請聯絡我,謝謝。