在CNN中使用Tensorflow進行數據增強


開始之前,需要思考一些基本問題

1、為什么需要大量數據

      當您訓練機器學習模型時,您真正在做的是調整其參數,以便它可以將特定輸入(例如,圖像)映射到某個輸出(標簽)。我們的優化目標是追逐我們模型損失較低的最佳位置,這種情況發生在您的參數以正確的方式調整時。

     現在的神經網絡通常具有數百萬的參數,因此,你需要向您的機器學習模型喂入一定比例的示例,以獲得良好的性能。此外,您需要的參數數量與模型送執行的任務的復雜程度成正比。

2、如果我沒有“更多數據”,如何獲得更多數據?

    您無需尋找可添加到數據集中的新穎圖像。為什么?因為,神經網絡開始時並不聰明。例如,訓練不良的神經網絡會認為下面顯示的這三個網球是截然不同的獨特圖像。

    因此,為了獲得更多數據,我們只需要對現有數據集進行微小改動。輕微更改,例如翻轉或翻譯或輪換。無論如何,我們的神經網絡會認為這些是不同的圖像。

   
    卷積神經網絡即使放置在不同的方向,也可以對對象進行穩健的分類,我們稱之為稱為不變性。更具體地,CNN可以對平移,視點,大小或照明(或上述的組合)也不變。
 
   這基本上是 數據增加的前提。在現實世界場景中,我們可能會在一組有限的條件下獲取圖像數據集。但是,我們的目標應用可能存在於各種條件下,例如不同的方向,位置,比例,亮度等。我們通過使用額外的合成修改數據訓練我們的神經網絡來解釋這些情況。

3、即使有我有大量的數據,擴充也可以提供幫助嗎

    是。它有助於增加數據集中的相關數據量。 這與神經網絡學習的方式有關。讓我用一個例子來說明它。
我們假設數據集中的兩個類。左邊的那個代表品牌A(福特),右邊的代表品牌B(雪佛蘭)。
 (我們假設數據集中的兩個類。左邊的那個代表品牌A(福特),右邊的代表品牌B(雪佛蘭))
    
    想象一下,您有一個由兩個品牌的汽車組成的數據集,如上所示。讓我們假設品牌A的所有車輛都與左側的車輛完全對齊(即所有車輛都朝向左側)。同樣,品牌B的所有汽車都與右側的圖片完全對齊(即面向右側)。現在,您將此數據集提供給“最先進的”神經網絡,並且您希望在訓練后獲得令人印象深刻的結果。
福特汽車(品牌A),但面對正確。
    
 
    現在,你提供一張品牌A的圖片,但是你的神經網絡輸出它是B品牌車。為什么會這樣?這是因為大多數機器學習算法都是如此。它找到了區分一個類與另一個類的最明顯的特征。在這里,特色是所有品牌A的汽車都面向左側,而品牌B的所有汽車都面向右側。
    我們如何防止這種情況發生?我們必須減少數據集中不相關的功能。對於我們上面的汽車模型分類器,一個簡單的解決方案是添加兩個類別的汽車圖片,面向我們原始數據集的另一個方向。更好的是,您可以水平翻轉現有數據集中的圖像,使其面向另一側!現在,在這個新數據集上訓練神經網絡時,您將獲得您想要獲得的性能。
 

流行的增強技術

     讓我們探討幾種最常用的圖像增強技術,包括代碼示例和增強后的圖像可視化。從這里開始,數據將被稱為圖像。我們將在所有示例中使用用Python編寫的Tensorflow或OpenCV。以下是我們將在文章中使用的技術索引:

在任何技術之前:圖像大小調整

    從互聯網收集的圖像將具有不同的大小。由於在大多數神經網絡中存在完全連接的層,所以饋送到網絡的圖像將需要固定大小(除非您在傳遞到密集層之前使用空間金字塔池)。因此,在圖像增強發生之前,讓我們將圖像預處理到我們網絡所需的大小。使用固定大小的圖像,我們可以獲得批量處理它們的好處。

 1 import tensorflow as tf
 2 import matplotlib.image as mpimg
 3 import numpy as np
 4 
 5 IMAGE_SIZE = 224
 6 
 7 def tf_resize_images(X_img_file_paths):
 8     X_data = []
 9     tf.reset_default_graph()
10     X = tf.placeholder(tf.float32, (None, None, 3))
11     tf_img = tf.image.resize_images(X, (IMAGE_SIZE, IMAGE_SIZE), 
12                                     tf.image.ResizeMethod.NEAREST_NEIGHBOR)
13     with tf.Session() as sess:
14         sess.run(tf.global_variables_initializer())
15         
16         # Each image is resized individually as different image may be of different size.
17         for index, file_path in enumerate(X_img_file_paths):
18             img = mpimg.imread(file_path)[:, :, :3] # Do not read alpha channel.
19             resized_img = sess.run(tf_img, feed_dict = {X: img})
20             X_data.append(resized_img)
21 
22     X_data = np.array(X_data, dtype = np.float32) # Convert to numpy
23     return X_data
Image Reszing

縮放

    在圖像中具有不同縮放的感興趣對象是圖像多樣性的最重要方面。當您的網絡掌握在真實用戶手中時,圖像中的對象可能很小或很大。此外,有時,物體可以覆蓋整個圖像,但不會完全存在於圖像中(即在物體的邊緣處被裁剪)。

def central_scale_images(X_imgs, scales):
    # Various settings needed for Tensorflow operation
    boxes = np.zeros((len(scales), 4), dtype = np.float32)
    for index, scale in enumerate(scales):
        x1 = y1 = 0.5 - 0.5 * scale # To scale centrally
        x2 = y2 = 0.5 + 0.5 * scale
        boxes[index] = np.array([y1, x1, y2, x2], dtype = np.float32)
    box_ind = np.zeros((len(scales)), dtype = np.int32)
    crop_size = np.array([IMAGE_SIZE, IMAGE_SIZE], dtype = np.int32)
    
    X_scale_data = []
    tf.reset_default_graph()
    X = tf.placeholder(tf.float32, shape = (1, IMAGE_SIZE, IMAGE_SIZE, 3))
    # Define Tensorflow operation for all scales but only one base image at a time
    tf_img = tf.image.crop_and_resize(X, boxes, box_ind, crop_size)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        
        for img_data in X_imgs:
            batch_img = np.expand_dims(img_data, axis = 0)
            scaled_imgs = sess.run(tf_img, feed_dict = {X: batch_img})
            X_scale_data.extend(scaled_imgs)
    
    X_scale_data = np.array(X_scale_data, dtype = np.float32)
    return X_scale_data
    
# Produce each image at scaling of 90%, 75% and 60% of original image.
scaled_imgs = central_scale_images(X_imgs, [0.90, 0.75, 0.60])
Scaling

翻譯

      我們希望我們的網絡能夠識別圖像任何部分中存在的對象。此外,對象可以部分地存在於圖像的角落或邊緣中。因此,我們將對象移動到圖像的各個部分。這也可能導致背景噪聲的增加。代碼段顯示在四邊翻譯圖像,保留80%的基本圖像。
from math import ceil, floor

def get_translate_parameters(index):
    if index == 0: # Translate left 20 percent
        offset = np.array([0.0, 0.2], dtype = np.float32)
        size = np.array([IMAGE_SIZE, ceil(0.8 * IMAGE_SIZE)], dtype = np.int32)
        w_start = 0
        w_end = int(ceil(0.8 * IMAGE_SIZE))
        h_start = 0
        h_end = IMAGE_SIZE
    elif index == 1: # Translate right 20 percent
        offset = np.array([0.0, -0.2], dtype = np.float32)
        size = np.array([IMAGE_SIZE, ceil(0.8 * IMAGE_SIZE)], dtype = np.int32)
        w_start = int(floor((1 - 0.8) * IMAGE_SIZE))
        w_end = IMAGE_SIZE
        h_start = 0
        h_end = IMAGE_SIZE
    elif index == 2: # Translate top 20 percent
        offset = np.array([0.2, 0.0], dtype = np.float32)
        size = np.array([ceil(0.8 * IMAGE_SIZE), IMAGE_SIZE], dtype = np.int32)
        w_start = 0
        w_end = IMAGE_SIZE
        h_start = 0
        h_end = int(ceil(0.8 * IMAGE_SIZE)) 
    else: # Translate bottom 20 percent
        offset = np.array([-0.2, 0.0], dtype = np.float32)
        size = np.array([ceil(0.8 * IMAGE_SIZE), IMAGE_SIZE], dtype = np.int32)
        w_start = 0
        w_end = IMAGE_SIZE
        h_start = int(floor((1 - 0.8) * IMAGE_SIZE))
        h_end = IMAGE_SIZE 
        
    return offset, size, w_start, w_end, h_start, h_end

def translate_images(X_imgs):
    offsets = np.zeros((len(X_imgs), 2), dtype = np.float32)
    n_translations = 4
    X_translated_arr = []
    
    tf.reset_default_graph()
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for i in range(n_translations):
            X_translated = np.zeros((len(X_imgs), IMAGE_SIZE, IMAGE_SIZE, 3), 
                    dtype = np.float32)
            X_translated.fill(1.0) # Filling background color
            base_offset, size, w_start, w_end, h_start, h_end = get_translate_parameters(i)
            offsets[:, :] = base_offset 
            glimpses = tf.image.extract_glimpse(X_imgs, size, offsets)
            
            glimpses = sess.run(glimpses)
            X_translated[:, h_start: h_start + size[0], \
             w_start: w_start + size[1], :] = glimpses
            X_translated_arr.extend(X_translated)
    X_translated_arr = np.array(X_translated_arr, dtype = np.float32)
    return X_translated_arr
    
translated_imgs = translate_images(X_imgs)
Translation

 

 旋轉(90度)

      網絡必須識別任何方向上存在的對象。假設圖像是方形的,將圖像旋轉90度將不會在圖像中添加任何背景噪聲。
def rotate_images(X_imgs):
    X_rotate = []
    tf.reset_default_graph()
    X = tf.placeholder(tf.float32, shape = (IMAGE_SIZE, IMAGE_SIZE, 3))
    k = tf.placeholder(tf.int32)
    tf_img = tf.image.rot90(X, k = k)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for img in X_imgs:
            for i in range(3):  # Rotation at 90, 180 and 270 degrees
                rotated_img = sess.run(tf_img, feed_dict = {X: img, k: i + 1})
                X_rotate.append(rotated_img)
        
    X_rotate = np.array(X_rotate, dtype = np.float32)
    return X_rotate
    
rotated_imgs = rotate_images(X_imgs)
    
Rotate90

旋轉(更精細的角度)

      根據上面的需求,它可能是必要的對於各種角度。如果這圖片的背景是一種固定的顏色,新加的顏色需要與背景融合,否則,神經網絡不會將它作為一種特征來學習,而這種特征是不必要的。

from math import pi

def rotate_images(X_imgs, start_angle, end_angle, n_images):
    X_rotate = []
    iterate_at = (end_angle - start_angle) / (n_images - 1)
    
    tf.reset_default_graph()
    X = tf.placeholder(tf.float32, shape = (None, IMAGE_SIZE, IMAGE_SIZE, 3))
    radian = tf.placeholder(tf.float32, shape = (len(X_imgs)))
    tf_img = tf.contrib.image.rotate(X, radian)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
    
        for index in range(n_images):
            degrees_angle = start_angle + index * iterate_at
            radian_value = degrees_angle * pi / 180  # Convert to radian
            radian_arr = [radian_value] * len(X_imgs)
            rotated_imgs = sess.run(tf_img, feed_dict = {X: X_imgs, radian: radian_arr})
            X_rotate.extend(rotated_imgs)

    X_rotate = np.array(X_rotate, dtype = np.float32)
    return X_rotate
    
# Start rotation at -90 degrees, end at 90 degrees and produce totally 14 images
rotated_imgs = rotate_images(X_imgs, -90, 90, 14)
rotate

翻轉

      這種情況對於網絡來說更重要的是消除假設對象的某些特征僅在特定方面可用的偏差。考慮圖像示例中顯示的情況。您不希望網絡知道香蕉的傾斜僅發生在基本圖像中觀察到的右側。

def flip_images(X_imgs):
    X_flip = []
    tf.reset_default_graph()
    X = tf.placeholder(tf.float32, shape = (IMAGE_SIZE, IMAGE_SIZE, 3))
    tf_img1 = tf.image.flip_left_right(X)
    tf_img2 = tf.image.flip_up_down(X)
    tf_img3 = tf.image.transpose_image(X)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for img in X_imgs:
            flipped_imgs = sess.run([tf_img1, tf_img2, tf_img3], feed_dict = {X: img})
            X_flip.extend(flipped_imgs)
    X_flip = np.array(X_flip, dtype = np.float32)
    return X_flip
    
flipped_images = flip_images(X_imgs)
flip image

添加椒鹽噪聲

      鹽和胡椒噪音是指在圖像中添加白點和黑點。雖然這似乎沒有必要,但重要的是要記住,將圖像輸入網絡的普通用戶可能不是專業攝影師。他的相機可以產生模糊的圖像,有許多白點和黑點。這種增強有助於上述用戶。
def add_salt_pepper_noise(X_imgs):
    # Need to produce a copy as to not modify the original image
    X_imgs_copy = X_imgs.copy()
    row, col, _ = X_imgs_copy[0].shape
    salt_vs_pepper = 0.2
    amount = 0.004
    num_salt = np.ceil(amount * X_imgs_copy[0].size * salt_vs_pepper)
    num_pepper = np.ceil(amount * X_imgs_copy[0].size * (1.0 - salt_vs_pepper))
    
    for X_img in X_imgs_copy:
        # Add Salt noise
        coords = [np.random.randint(0, i - 1, int(num_salt)) for i in X_img.shape]
        X_img[coords[0], coords[1], :] = 1

        # Add Pepper noise
        coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in X_img.shape]
        X_img[coords[0], coords[1], :] = 0
    return X_imgs_copy
  
salt_pepper_noise_imgs = add_salt_pepper_noise(X_imgs)
salt_pepper_noise

光照條件

      這是圖像數據集中所需的非常重要的多樣性類型,不僅網絡能夠正確地學習感興趣的對象,而且還模擬用戶拍攝的圖像的實際場景。通過在圖像中添加高斯噪聲來改變圖像的照明條件。
import cv2

def add_gaussian_noise(X_imgs):
    gaussian_noise_imgs = []
    row, col, _ = X_imgs[0].shape
    # Gaussian distribution parameters
    mean = 0
    var = 0.1
    sigma = var ** 0.5
    
    for X_img in X_imgs:
        gaussian = np.random.random((row, col, 1)).astype(np.float32)
        gaussian = np.concatenate((gaussian, gaussian, gaussian), axis = 2)
        gaussian_img = cv2.addWeighted(X_img, 0.75, 0.25 * gaussian, 0.25, 0)
        gaussian_noise_imgs.append(gaussian_img)
    gaussian_noise_imgs = np.array(gaussian_noise_imgs, dtype = np.float32)
    return gaussian_noise_imgs
  
gaussian_noise_imgs = add_gaussian_noise(X_imgs)
gaussian_noise

透視變換

      在透視變換中,我們嘗試從不同的角度投影圖像。為此,應事先知道物體的位置。僅僅在不知道對象位置的情況下計算透視變換可能導致數據集的劣化。因此,必須有選擇地執行這種類型的增強。這種增強的最大優點是它可以強調網絡需要學習的圖像中的對象部分。
def get_mask_coord(imshape):
    vertices = np.array([[(0.09 * imshape[1], 0.99 * imshape[0]), 
                          (0.43 * imshape[1], 0.32 * imshape[0]), 
                          (0.56 * imshape[1], 0.32 * imshape[0]),
                          (0.85 * imshape[1], 0.99 * imshape[0])]], dtype = np.int32)
    return vertices

def get_perspective_matrices(X_img):
    offset = 15
    img_size = (X_img.shape[1], X_img.shape[0])

    # Estimate the coordinates of object of interest inside the image.
    src = np.float32(get_mask_coord(X_img.shape))
    dst = np.float32([[offset, img_size[1]], [offset, 0], [img_size[0] - offset, 0], 
                      [img_size[0] - offset, img_size[1]]])
    
    perspective_matrix = cv2.getPerspectiveTransform(src, dst)
    return perspective_matrix

def perspective_transform(X_img):
    # Doing only for one type of example
    perspective_matrix = get_perspective_matrices(X_img)
    warped_img = cv2.warpPerspective(X_img, perspective_matrix,
                                     (X_img.shape[1], X_img.shape[0]),
                                     flags = cv2.INTER_LINEAR)
    return warped_img
  
perspective_img = perspective_transform(X_img)
perspective_transform

總結

     盡管上面的圖像增強方法列表並非詳盡無遺,但是包含了許多廣泛使用的方法,您可以組合的使用這些擴充來生成更多的圖像。您可以在Github中查看本文使用的代碼。

 

 
 
參考鏈接:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM