通用數據增強方法(Data Augmentation)
1、前言
機器學習尤其是深度學習中,爲了防止模型過擬合,數據增強也是一種非常有效的方法,好多牛逼的模型除了網絡結構精妙意外,在數據(比較喫數據的有監督深度學習)上也做了不可忽視的工作,纔有state of the art的效果。來看一張圖:
C10和C100是沒有經過數據增強的訓練效果,C10+和C100+則是經過數據增強的效果,提升非常明顯……,這個是分類的錯誤率。在目標檢測領域,好的算法都採用了精妙的數據增強,如YOLOv2&v3、SSD、Faster-RCNN和MASK-RCNN等。
2、數據增強
2.1 隨機裁切(random crop)
隨機裁切幾乎是所有深度學習框架訓練都具有的數據增強方法,在很多有名的深度學習網絡(VGG,AlexNet,GoogleNet,ResNet……)的訓練中,對輸入256*256的圖像,通常會以224或227的窗口隨機獲得子圖像作爲訓練,而在測試時則是以圖像中心的子塊(Patch)。最終使用則直接將圖像resize到訓練時用的crop size大小。實現也是比較簡單,在紅色區域內隨機取點作爲滑窗左上角頂點。
Python實現:
import numpy.random as npr
def randomCrop(img,size_h,size_w):
rows,cols=img.shape[:2]
left_h=npr.randint(0,rows-size_h)
left_w=npr.randint(0,cols-size_w)
crop_img=img[left_h:left_h+size_h,left_w:left_w+size_w]
return crop_img
2.2 翻轉(左右上下)
左右翻轉也叫做水平翻轉或鏡像(mirror),將圖像的左右部分以圖像垂直中軸線爲中心進行鏡像對換。假設原圖像高度爲h,寬度爲w,原圖像某一像素點P(x0,y0), 經過水平鏡像變換後爲P(w-1-x0,y0),矩陣表示:
Python代碼(左右鏡像):
#水平翻轉
def horizontalFlip(img):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h): # 元素循環
for j in range(w):
iLR[i, w - 1 - j] = img[i, j]
return iLR
#垂直翻轉
def verticalFlip(img):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h): # 元素循環
for j in range(w):
iLR[ h- 1 - i,j] = img[i, j]
return iLR
2.3 顏色抖動(color jitter)
顏色抖動是指對圖像的曝光度(exposure)、飽和度(saturation)和色調(hue)進行隨機變化形成不同光照及顏色下的圖片,達到數據增強的目的,儘可能使得模型能夠使用不同光照條件小的情形,提高模型泛化能力。
YOLOv2-v3的目標檢測就用到了這一數據增強方法,YOLO在訓練的每一Batch中對訓練數據進行在線數據增強(包括顏色抖動) ,實現上首先將圖像變換到HSV顏色空間,然後對圖像在HSV顏色空間中隨機的改變圖像的曝光度、飽和度和色調,然後再將變換後的圖像轉到RGB空間。具體實現代碼可以參考YOLO源碼(image.c),
void random_distort_image(image im, float hue, float saturation, float exposure)
{
float dhue = rand_uniform_strong(-hue, hue);
float dsat = rand_scale(saturation);
float dexp = rand_scale(exposure);
distort_image(im, dhue, dsat, dexp);
}
void distort_image(image im, float hue, float sat, float val)
{
rgb_to_hsv(im);
scale_image_channel(im, 1, sat);//改變S通道,該通道的像素值乘上隨機sat值
scale_image_channel(im, 2, val);//改變V通道,該通道的像素值乘上隨機val值
int i;
for(i = 0; i < im.w*im.h; ++i){
im.data[i] = im.data[i] + hue;//改變H通道,該通道像素值加隨機hue值
if (im.data[i] > 1) im.data[i] -= 1;
if (im.data[i] < 0) im.data[i] += 1;
}
hsv_to_rgb(im);
constrain_image(im);
}
對於部分任務也可以借鑑該方法,對樣本進行離線或者在線的數據增強。
2.4 噪聲(高斯噪聲)
一般對圖像加入高斯噪聲可以使得圖像變得模糊,從而模擬模糊情況。Python實現
from skimage import util
def gaussNoise(img):
noise_img=util.random_noise(img,mode='gaussian')
result=util.img_as_ubyte(noise_img)
return result
2.5 旋轉
圖像旋轉一般是以圖像中心爲旋轉中心進行隨機旋轉(一般有一個正負角度約束),而。Python實現
from skimage.transform import rotate
def img_rotate(img,angle):
return rotate(img,angle)
rotate_limit=(-30, 30)
theta = np.pi / 180 * np.random.uniform(rotate_limit[0], rotate_limit[1]) #逆
img_rot = img_rotate(img, theta)
#opencv實現
def cv_rotate(img,center,angle,scale):
rows,cols=img.shape[:2]
M = cv2.getRotationMatrix2D(center, angle, scale)
dst = cv2.warpAffine(img, M, (cols, rows))
return dst
2.6 平移
1)圖像沿着X軸或Y軸的平移有以下四種情況:
def move_right(img,dis):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h):
for j in range(w):
if j-dis>0:
iLR[i,j]=img[i,j-dis]
else:
iLR[i, j] =0
return iLR
def move_left(img,dis):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h):
for j in range(w):
if j+dis<w-1:
iLR[i,j]=img[i,j+dis]
else:
iLR[i, j] =0
return iLR
def move_up(img,dis):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h):
for j in range(w):
if i+dis<h-1:
iLR[i,j]=img[i+dis,j]
else:
iLR[i, j] =0
return iLR
def move_down(img,dis):
size = img.shape # 獲得圖像的形狀
iLR = img.copy() # 獲得一個和原始圖像相同的圖像,注意這裏要使用深度複製
h = size[0]
w = size[1]
for i in range(h):
for j in range(w):
if i-dis>0:
iLR[i,j]=img[i-dis,j]
else:
iLR[i, j] =0
return iLR
2)圖像沿着任意向量平移
參考:https://blog.csdn.net/sty945/article/details/79387054
def translate(image, x, y): #3
M = np.float32([[1, 0, x], [0, 1, y]]) #4 //X軸移動x, Y中移動y
shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0])) #5
return shifted #6
2.7 縮放
全卷積網絡對於尺度沒有嚴格要求,如目標檢測算法yolov2-v3在訓練過程中會對樣本進行隨機尺度變換(每隔10步變換一次),實現多尺度訓練,進而跨尺度特徵融合。同時也使得模型對於不同尺度的圖像有更強的適應性。
from skimage.transform import rescale
def image_resize(img,scale):
result=rescale(img,scale)
return result
2.8 PCA jitter
PCA jitter是實際上對RGB顏色空間添加擾動,從而達到對RGB顏色添加噪聲的目的,具體爲對RGB空間做PCA,然後對主成分做一個(0, 0.1)的高斯擾動。最早使用是在2012年的AlexNet,從論文實驗中可以看出,PCA jitter對於分類的性能提升比較顯著。
def PCA_Jittering (img):
img = np.asanyarray(img, dtype='float32')
img = img / 255.0
img_size = img.size // 3 # 轉換爲單通道
img1 = img.reshape(img_size, 3)
img1 = np.transpose(img1) # 轉置
img_cov = np.cov([img1[0], img1[1], img1[2]]) # 協方差矩陣
lamda, p = np.linalg.eig(img_cov) # 得到上述協方差矩陣的特徵向量和特徵值
# p是協方差矩陣的特徵向量
p = np.transpose(p) # 轉置回去
# 生成高斯隨機數********可以修改
alpha1 = random.gauss(0, 1)
alpha2 = random.gauss(0, 1)
alpha3 = random.gauss(0, 1)
# lamda是協方差矩陣的特徵值
v = np.transpose((alpha1 * lamda[0], alpha2 * lamda[1], alpha3 * lamda[2])) # 轉置
# 得到主成分
add_num = np.dot(p, v)
# 在原圖像的基礎上加上主成分
img2 = np.array([img[:, :, 0] + add_num[0], img[:, :, 1] + add_num[1], img[:, :, 2] + add_num[2]])
# 現在是BGR,要轉成RBG再進行保存
img2 = np.swapaxes(img2, 0, 2)
img2 = np.swapaxes(img2, 0, 1)
return img2