在計算機視覺方面,計算機視覺的主要問題是沒有辦法得到充足的數據。對大多數機器學習應用,這不是問題,但是對計算機視覺,數據就遠遠不夠。所以這就意味着當你訓練計算機視覺模型的時候,數據增強會有所幫助,這是可行的,無論你是使用遷移學習,使用別人的預訓練模型開始,或者從源代碼開始訓練模型。
下面就詳細介紹數據增強方式以及代碼實現講解。
數據增強
數據增強可以使得我們的圖片變得更加多樣,比如你的數據集中只有左邊的這樣的一張圖片,假如我們分別對其進行色調和翻轉等操作,就可以得到四張新的不同含有貓的照片,這樣你的數據集就擴充了,貓的識別效果會更好,因為數據集樣本更加的多樣性,放入神經網絡訓練可以提高網絡的魯棒性,降低各方面的額外因素對識別的影響,比如:亮度和形態,你的數據集已經包括不同形態貓的圖片。

通過改變亮度,圖像扭曲等方式使得圖像變得更加多種多樣,如上圖所示,盡管亮度,形態發生了細微改變,但本質上圖片上的目標還是一只貓。
目標檢測中的數據增強
如果你只是想簡單的進行目標分類任務,讓你的網絡模型遍歷圖片,判斷這是不是一只貓,像上面那樣進行數據擴充之后保存圖片到數據集中再喂入到網絡中訓練即可,但是在目標檢測中不僅需要進行目標分類還要進行目標定位,這就涉及到目標框的調整了。
一般的數據增強方式:
- 對圖像進行縮放並進行長和寬的扭曲
- 對圖像進行翻轉
- 對圖像進行色域扭曲
在目標檢測中,並不是直接增強圖片就好了,我們要考慮扭曲以及翻轉之后框的位置,也就是說框的位置要隨着圖片的改變而改變。

右圖是我用VOC2007數據集中的一張圖片,包含人和狗兩個目標以及相應的框框,左圖是我們進行數據增強之后得到的一張新的圖片,我們可以看到我們的原圖的寬高被扭曲、尺寸被縮小了、圖片被翻轉、圖片的色域也發生了變化,但是目標以及框住目標的框框依然存在。
實現代碼
使用以下代碼,即可實現對VOC2007的數據集進行數據增強,代碼的講解已經詳細注釋好了。
from PIL import Image, ImageDraw
import numpy as np
from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
"""
數據增強的方式:
數據增強其實就是讓圖片變得更加多樣,數據增強是非常重要的提高目標檢測算法魯棒性的手段。
可以通過改變亮度,圖像扭曲等方式使得圖像變得更加多種多樣,改變后的圖片放入神經網絡進行訓練可以提高網絡的魯棒性,降低各方面額外因素對識別的影響.
"""
def rand(a=0, b=1): # 生成一個取值范圍為[a,b)的隨機數
return np.random.rand() * (b - a) + a
# get_random_data數據增強
def get_random_data(annotation_line, input_shape, random=True, max_boxes=20, jitter=.5, hue=.1, sat=1.5, val=1.5, proc_img=True):
"""
實時數據增強的隨機預處理
random preprocessing for real-time data augmentation
:param annotation_line: 數據集中的某一行對應的圖片
:param input_shape: yolo網絡輸入圖片的大小416*416
:param random:
:param max_boxes:
:param jitter:控制圖片的寬高的扭曲比率,jitter=.5表示在0.5到1.5之間進行扭曲
:param hue: 代表hsv色域中三個通道中的色調進行扭曲,色調(H)=.1
:param sat: 代表hsv色域中三個通道中的飽和度進行扭曲,飽和度(S)=1.5
:param val: 代表hsv色域中三個通道中的明度進行扭曲,明度(V)=1.5
:param proc_img:
:return:
"""
line = annotation_line.split()
image = Image.open(line[0])
iw, ih = image.size # 原圖片大小
h, w = input_shape # 模型輸入圖片的大小
box = np.array([np.array(list(map(int, box.split(',')))) for box in line[1:]]) # 對該行的圖片中的目標框進行一個划分
# 對圖像進行縮放並且進行長和寬的扭曲
# 扭曲后的圖片大小可能會大於416*416的大小,但是在加灰條的時候會修正為416*416
new_ar = w/h * rand(1-jitter, 1+jitter)/rand(1-jitter, 1+jitter) # 表原圖片的寬高的扭曲比率,jitter=0,則原圖的寬高的比率不變,否則對圖片的寬和高進行一定的扭曲
scale = rand(.25, 2) # scale控制對原圖片的縮放比率,rand(.25, 2)表示在0.25到2之間縮放,圖片可能會放大可能會縮小,rand(.25, 1)會把原始的圖片進行縮小,圖片的邊緣加上灰條,可以訓練網絡對我們小目標的檢測能力。rand(1,2)則是一定放大圖像
if new_ar < 1:
nh = int(scale * h)
nw = int(nh * new_ar)
else:
nw = int(scale * w)
nh = int(nw / new_ar)
image = image.resize((nw, nh), Image.BICUBIC)
# print(nw,nh) # 扭曲后的圖片的寬和高
# 將圖像多余的部分加上灰條,一定保證圖片的大小為w,h = 416,416
dx = int(rand(0, w - nw))
dy = int(rand(0, h - nh))
new_image = Image.new('RGB', (w, h), (128, 128, 128)) # (128, 128, 128)代表灰色
new_image.paste(image, (dx, dy))
image = new_image
# 是否翻轉圖像
flip = rand() < .5 # 有50%的幾率發生翻轉
if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT) # 左右翻轉
# 色域扭曲
# 色域扭曲是發生在這個hsv這樣的色域上,hsv色域是有色調H、飽和度S、明度V三者控制,調整這3個值調整色域扭曲的比率
hue = rand(-hue, hue)
sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
val = rand(1, val) if rand()<.5 else 1/rand(1, val)
x = rgb_to_hsv(np.array(image) / 255.) # 將圖片從RGB圖像調整到hsv色域上之后,再對其色域進行扭曲
x[..., 0] += hue
x[..., 0][x[..., 0] > 1] -= 1
x[..., 0][x[..., 0] < 0] += 1
x[..., 1] *= sat
x[..., 2] *= val
x[x > 1] = 1
x[x < 0] = 0
image_data = hsv_to_rgb(x) # numpy array, 0 to 1
# 將box進行調整
# 對原圖片進項扭曲后,也要對原圖片中的框框也進行相應的調整
box_data = np.zeros((max_boxes, 5))
if len(box) > 0:
np.random.shuffle(box)
# 扭曲調整
box[:, [0, 2]] = box[:, [0, 2]] * nw / iw + dx
box[:, [1, 3]] = box[:, [1, 3]] * nh / ih + dy
# 旋轉調整
if flip: box[:, [0, 2]] = w - box[:, [2, 0]]
# 因為調整后不再圖像中的目標框的調整
box[:, 0:2][box[:, 0:2] < 0] = 0
box[:, 2][box[:, 2] > w] = w
box[:, 3][box[:, 3] > h] = h
box_w = box[:, 2] - box[:, 0]
box_h = box[:, 3] - box[:, 1]
box = box[np.logical_and(box_w > 1, box_h > 1)] # discard invalid box
if len(box) > max_boxes: box = box[:max_boxes]
box_data[:len(box)] = box
return image_data, box_data
# 原圖片繪制展示
def normal_(annotation_line, input_shape):
"""
random preprocessing for real-time data augmentation
:param annotation_line: 選取的數據集第a行所對應的圖片進行數據增強
:param input_shape: 輸入的大小
:return:
"""
line = annotation_line.split() # 以空格進行分割
# 獲取該行對應的圖片
image = Image.open(line[0])
# 獲取該圖片上的每一個目標框
box = np.array([np.array(list(map(int, box.split(',')))) for box in line[1:]])
return image, box
if __name__ == "__main__":
with open("2007_train.txt") as f:
lines = f.readlines()
a = np.random.randint(0, len(lines))
line = lines[a] # 選取的數據集第a行所對應的圖片進行數據增強
image_data, box_data = normal_(line, [416, 416])
img = image_data
# 原圖片繪制展示
#數據集的第a行圖片的展現
for j in range(len(box_data)):
thickness = 3
left, top, right, bottom = box_data[j][0:4]
draw = ImageDraw.Draw(img)
for i in range(thickness):
draw.rectangle([left + i, top + i, right - i, bottom - i], outline=(255, 255, 255))
img.show()
#對圖片進行數據增強后的展示
image_data, box_data = get_random_data(line, [416, 416])
print(box_data)
img = Image.fromarray((image_data * 255).astype(np.uint8))
for j in range(len(box_data)):
thickness = 3
left, top, right, bottom = box_data[j][0:4]
#創建繪制對象
draw = ImageDraw.Draw(img)
for i in range(thickness):
draw.rectangle([left + i, top + i, right - i, bottom - i], outline=(255, 255, 255))
img.show()
從代碼中我們可以看到的主要步驟是:讀取數據集圖像->對圖像做數據做數據增強->調整圖像中的目標框。
所有實現代碼以及完整注釋,關注我下載使用,更多有關python、深度學習和計算機編程和電腦知識的精彩內容,可以關注微信公眾號:碼農的后花園