机器学习尤其是深度学习中,为了防止模型过拟合,数据增强也是一种非常有效的方法,好多牛逼的模型除了网络结构精妙意外,在数据(比较吃数据的有监督深度学习)上也做了不可忽视的工作,才有state of the art的效果。来看一张图:
C10和C100是没有经过数据增强的训练效果,C10+和C100+则是经过数据增强的效果,提升非常明显……,这个是分类的错误率。在目标检测领域,好的算法都采用了精妙的数据增强,如YOLOv2&v3、SSD、Faster-RCNN和MASK-RCNN等。
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+dis0:
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