剪辑自:https://zhuanlan.zhihu.com/p/43665254

引言

数据增广是深度学习中常用的技巧之一,主要用于增加训练数据集,让数据集尽可能的多样化,使得训练的模型具有更强的泛化能力.现有的各大深度学习框架都已经自带了数据增广,但是平时在用的使用只是直接调用了对应的接口函数,而没有进行详细的分析.在实际应用中,并非所有的增广方式都适用当前的训练数据,你需要根据自己的数据集特征来确定应该使用哪几种数据增广方式.这篇文章的目的是为了更好地理解各种增广方式及其背后的真正原理.
目前数据增广主要包括:水平/垂直翻转,旋转,缩放,裁剪,剪切,平移,对比度,色彩抖动,噪声等,这里因为时间问题,有部分还每有完成,后续会进行更新.

数据增广

所有的数据增广在操作的时候默认是以图像中心点进行的.从数学角度来看,任何操作都可以分成以下几个步骤:1). 首先将旋转点移动到原点处 ;2). 执行如2所描述的绕原点的旋转;3). 再将旋转点移回到原来的位置;这里为了更好地理解,给出一个示例:

数据增广之详细理解 - 图1

假设图像的原始坐标为数据增广之详细理解 - 图2,平移后的坐标为 数据增广之详细理解 - 图3 ,则平移前和平移后的坐标关系为:

数据增广之详细理解 - 图4

图像平移

平移是指所有的像素在x和y方向各平移和,平移变换对应的数学矩阵为

数据增广之详细理解 - 图5

这里给出平移后的具体实例(这里平移后我采用的是倒映填充):

数据增广之详细理解 - 图6

图像翻转(图像镜像)

图像翻转包括水平翻转和垂直翻转.水平翻转的变换矩阵为:

数据增广之详细理解 - 图7

垂直翻转的变换矩阵为:

数据增广之详细理解 - 图8

这里给出翻转后的具体实例(这里平移后我采用的是倒映填充):
数据增广之详细理解 - 图9

图像旋转

图像旋转是指以某个点(默认为图像中心点)为中心进行任意角度的旋转,其变换矩阵为:

数据增广之详细理解 - 图10

这里给出旋转后的具体实例(这里平移后我采用的是倒映填充):
数据增广之详细理解 - 图11

图像缩放

图像缩放是指对当前图像进行任意尺度的缩放,其变换矩阵为:

数据增广之详细理解 - 图12

这里给出缩放后的具体实例(这里平移后我采用的是倒映填充):
数据增广之详细理解 - 图13

图像错切

图像,其变换矩阵为:

数据增广之详细理解 - 图14

这里给出错切后的具体实例(这里平移后我采用的是倒映填充):
数据增广之详细理解 - 图15

图像裁剪

深度学习的裁剪的常用做法是将图片缩放到原图的1.1倍,然后在缩放后的图像上进行裁剪操作,具体的裁剪实例如下:
数据增广之详细理解 - 图16

组合变换

在深度学习中的数据增广一般会采用多种增广方式的组合,这里就会涉及到矩阵乘法运算,根据其运算的规则,可以知道不同的组合顺序结果是不一样的,即线性代数中的 数据增广之详细理解 - 图17 ,当然特例除外.
为了更好地解释,假设给定平移变换矩阵 数据增广之详细理解 - 图18 ,旋转矩阵 数据增广之详细理解 - 图19 ,缩放矩阵 数据增广之详细理解 - 图20 ,为了说明这里我给出两个不同的组合变换.对于组合变换一,其组合后的矩阵如下: 数据增广之详细理解 - 图21 ;对于组合变换二,其组合后的矩阵如下: 数据增广之详细理解 - 图22 ,对于两种不同的组合其结果如下:

在深度学习中的数据增广一般会采用多种增广方式的组合,这里就会涉及到矩阵乘法运算,根据其运算的规则,可以知道不同的组合顺序结果是不一样的,即线性代数中的 数据增广之详细理解 - 图23,当然特例除外.

为了更好地解释,假设给定平移变换矩阵 数据增广之详细理解 - 图24,旋转矩阵 数据增广之详细理解 - 图25,缩放矩阵 数据增广之详细理解 - 图26,为了说明这里我给出两个不同的组合变换.对于组合变换一,其组合后的矩阵如下: 数据增广之详细理解 - 图27;对于组合变换二,其组合后的矩阵如下: 数据增广之详细理解 - 图28,对于两种不同的组合其结果如下:
数据增广之详细理解 - 图29

数据增广之源码实现

这里使用python和opencv来实现上述中的各种变换,具体的源码如下:

  1. #coding=utf-8
  2. ################################################
  3. # 数据增广,包括
  4. # 2018.09.02 add
  5. ################################################
  6. import numpy as np
  7. import os
  8. import cv2
  9. import copy
  10. class DataAugment:
  11. def __init__(self,debug=False):
  12. self.debug=debug
  13. print("Data augment...")
  14. def basic_matrix(self,translation):
  15. """基础变换矩阵"""
  16. return np.array([[1,0,translation[0]],[0,1,translation[1]],[0,0,1]])
  17. def adjust_transform_for_image(self,img,trans_matrix):
  18. """根据图像调整当前变换矩阵"""
  19. transform_matrix=copy.deepcopy(trans_matrix)
  20. height, width, channels = img.shape
  21. transform_matrix[0:2, 2] *= [width, height]
  22. center = np.array((0.5 * width, 0.5 * height))
  23. transform_matrix = np.linalg.multi_dot([self.basic_matrix(center), transform_matrix, self.basic_matrix(-center)])
  24. return transform_matrix
  25. def apply_transform(self,img,transform):
  26. """仿射变换"""
  27. output = cv2.warpAffine(img, transform[:2, :], dsize=(img.shape[1], img.shape[0]),
  28. flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT, borderValue=0,) #cv2.BORDER_REPLICATE,cv2.BORDER_TRANSPARENT
  29. return output
  30. def apply(self,img,trans_matrix):
  31. """应用变换"""
  32. tmp_matrix=self.adjust_transform_for_image(img, trans_matrix)
  33. out_img=self.apply_transform(img, tmp_matrix)
  34. if self.debug:
  35. self.show(out_img)
  36. return out_img
  37. def random_vector(self,min,max):
  38. """生成范围矩阵"""
  39. min=np.array(min)
  40. max=np.array(max)
  41. print(min.shape,max.shape)
  42. assert min.shape==max.shape
  43. assert len(min.shape) == 1
  44. return np.random.uniform(min, max)
  45. def show(self,img):
  46. """可视化"""
  47. cv2.imshow("outimg",img)
  48. cv2.waitKey()
  49. def random_transform(self,img,min_translation,max_translation):
  50. """平移变换"""
  51. factor=self.random_vector(min_translation,max_translation)
  52. trans_matrix=np.array([[1, 0, factor[0]],[0, 1, factor[1]],[0, 0, 1]])
  53. out_img=self.apply(img,trans_matrix)
  54. return trans_matrix, out_img
  55. def random_flip(self,img,factor):
  56. """水平或垂直翻转"""
  57. flip_matrix = np.array([[factor[0], 0, 0],[0, factor[1], 0],[0, 0, 1]])
  58. out_img=self.apply(img,flip_matrix)
  59. return flip_matrix, out_img
  60. def random_rotate(self,img,factor):
  61. """随机旋转"""
  62. angle=np.random.uniform(factor[0],factor[1])
  63. print("angle:{}".format(angle))
  64. rotate_matrix=np.array([[np.cos(angle), -np.sin(angle), 0],[np.sin(angle), np.cos(angle), 0],[0, 0, 1]])
  65. out_img=self.apply(img,rotate_matrix)
  66. return rotate_matrix, out_img
  67. def random_scale(self,img,min_translation,max_translation):
  68. """随机缩放"""
  69. factor=self.random_vector(min_translation, max_translation)
  70. scale_matrix = np.array([[factor[0], 0, 0],[0, factor[1], 0],[0, 0, 1]])
  71. out_img=self.apply(img,scale_matrix)
  72. return scale_matrix, out_img
  73. def random_shear(self,img,factor):
  74. """随机剪切,包括横向和众向剪切"""
  75. angle = np.random.uniform(factor[0], factor[1])
  76. print("fc:{}".format(angle))
  77. crop_matrix = np.array([[1, factor[0], 0], [factor[1], 1, 0], [0, 0, 1]])
  78. out_img=self.apply(img,crop_matrix)
  79. return crop_matrix, out_img
  80. if __name__=="__main__":
  81. demo=DataAugment(debug=True)
  82. img=cv2.imread("/pathto/dataArgu/wr.jpg")
  83. # 平移测试
  84. _,outimg=demo.random_transform(img,(0.1,0.1),(0.2,0.2)) #(-0.3,-0.3),(0.3,0.3)
  85. # 垂直变换测试
  86. _, outimg =demo.random_flip(img,(1.0,-1.0))
  87. # 水平变换测试
  88. _, outimg =demo.random_flip(img, (-1.0, 1.0))
  89. # 旋转变换测试
  90. _, outimg =demo.random_rotate(img,(0.5,0.8))
  91. # # 缩放变换测试
  92. _, outimg =demo.random_scale(img,(1.2, 1.2),(1.3,1.3))
  93. # 随机裁剪测试
  94. _, outimg =demo.random_shear(img,(0.2,0.3))
  95. # 组合变换
  96. t1,_=demo.random_transform(img,(-0.3,-0.3),(0.3,0.3))
  97. t2,_=demo.random_rotate(img,(0.5,0.8))
  98. t3,_=demo.random_scale(img,(1.5,1.5),(1.7,1.7))
  99. tmp=np.linalg.multi_dot([t1,t2,t3])
  100. print("tmp:{}".format(tmp))
  101. out=demo.apply(img,tmp)