上文我们讲了在图像分类问题的数据增强的几种方法,其中很重要的方式为图像的随机裁剪,我们可以轻松地调用Pytorch框架的API加以实现,但是在图像检测问题中,如何实现数据增强的效果呢?由于我们对图像进行了随机裁剪,那么GT boxes的位置信息也会发生变化,可能会被裁剪为一半或者什么的,所以对于检测问题,直接使用Pytorch的API是不可以的,那么我们就从Numpy的底层来实现一个数据增强的程序!
分类问题的数据增强:TeddyZhang:图像分类:数据增强(Pytorch版)zhuanlan.zhihu.com
需要工程源码的请点赞!!!哈哈哈~
数据格式
已知我们的图像为3通道R/G/B,而且一幅图像标记的Boxes为[N, 4],其中N为一幅图像的真实框数,那么我们就来看看在检测问题中数据增强怎么做的?4维数据分别是[x1, y1, w, h],假设我们的模型的输入图片定义为1024x1024
随机裁剪 (对标记的Boxes进行处理)从图像中随机裁剪一个正方形,边长范围为[0.3*min, max], min为长与宽的最小值
计算真实框的中心点,裁剪区域一定要包含这些中心点
将原始图的GT Boxes变换成裁剪图的GT boxes
去掉那些过小的GT boxes, 不参与训练
# 3. 随机进行裁剪
def random_crop(self, im, boxes, labels):
imh, imw, _ = im.shape
short_size = min(imh, imw)
while True:
# 选择任意一个crop pitch
mode = random.randint(0, 4)
for _ in range(10):
if mode == 0:
w = short_size
else:
w = random.randrange(int(0.3*short_size), short_size) # 从0.3倍的最小边界开始
h = w
x = random.randint(0, imw - w)
y = random.randint(0, imh - h) # 随机选择正方形区域
roi = torch.Tensor([x, y, x+w, y+h])
center = (boxes[:, :2]+boxes[:, 2:]) / 2 # (N,2)维, 中心点
roi2 = roi.expand(len(center), 4) # 把(1,4)expand到(N,4)维
# 1. 选择包含box的roi
mask = (center > roi2[:, :2]) & (center < roi2[:, 2:]) # crop pitch里面包含那个中心点(N,2)
mask = mask[:, 0] & mask[:, 1] # (N, 1)
# any对每个元素进行或运算,对每个元素进行与运算
if not mask.any(): # 如果全为零,舍弃这个crop patch
im, boxes, labels = self.random_getim()
imh, imw, _ = im.shape
short_size = min(imh, imw)
continue
selected_boxes = boxes.index_select(0, mask.nonzero().squeeze(1)) # mask变为(N,)
img = im[y:y+h, x:x+w] # 裁剪区域
selected_boxes[:,0].add_(-x).clamp_(min=0, max=w) # clamp 夹并在x,y之间
selected_boxes[:,1].add_(-y).clamp_(min=0, max=h)
selected_boxes[:,2].add_(-x).clamp_(min=0, max=w)
selected_boxes[:,3].add_(-y).clamp_(min=0, max=h)
# expand_as(x) 表示扩展成x的尺寸
boxes_uniform = selected_boxes / torch.Tensor([w,h,w,h]).expand_as(selected_boxes)
boxeswh = boxes_uniform[:, 2:] - boxes_uniform[:, :2]
# 2. 选择去掉box太小的
mask = (boxeswh[:,0] > self.small_threshold) & (boxeswh[:,1] > self.small_threshold)
if not mask.any(): # 若全部为零,则舍弃
im, boxes, labels = self.random_getim()
imh, imw, _ = im.shape
short_size = min(imh, imw)
continue
selected_boxes_selected = selected_boxes[mask.nonzero().squeeze(1)] # mask变为(N,)
selected_labels = labels.index_select(0, mask.nonzero().squeeze(1))
return img, selected_boxes_selected, selected_labels
其中:tensor.index_select(dim, mask) 在dim维选择mask对应为正的元素
tensor.clamp_(min, max) 把数据夹在(min, max)之间
random.random() 产生0到1的随机数
random.randrange(0, N) 在0到N之间产生随机整数
np.nonzero() 选择不为零的元素
np.squeeze(1) 压缩维度,去掉维度1
tensor.expand(a, b) 把a扩展 b次,和传播机制类似
tensor.expand_as(B) 也是扩展,扩展后的维度和B一致
随机变换亮度
就是把图像加减某个值
# 4. 随机变换亮度 (概率:0.5)
def random_bright(self, im, delta=32):
if random.random() < 0.5:
delta = random.uniform(-delta, delta)
im += delta
im = im.clip(min=0, max=255)
return im
其中:random.uniform(-a, a) 随机在-a到a之间生成数字
np.clip(min,max) 和tensor.clamp效果一样
随机变换通道
# 5. 随机变换通道
def random_swap(self, im):
perms = ((0, 1, 2), (0, 2, 1),
(1, 0, 2), (1, 2, 0),
(2, 0, 1), (2, 1, 0))
if random.random() < 0.5:
swap = perms[random.randrange(0, len(perms))]
im = im[:, :, swap]
return im
随机变换对比度
图片像素点随机乘以某个值,(0.5,1.5)
# 6. 随机变换对比度
def random_contrast(self, im, lower=0.5, upper=1.5):
if random.random() < 0.5:
alpha = random.uniform(lower, upper)
im *= alpha
im = im.clip(min=0, max=255)
return im
随机变换饱和度
就是中间的色彩通道乘以某个值
# 7. 随机变换饱和度
def random_saturation(self, im, lower=0.5, upper=1.5):
if random.random() < 0.5:
im[:, :, 1] *= random.uniform(lower, upper)
return im
随机色度变换
首先把图像转换到HSV空间,然后再加上某个值
# 8. 随机变换色度(HSV空间下(-180, 180))
def random_hue(self, im, delta=18.0):
if random.random() < 0.5:
im[:, :, 0] += random.uniform(-delta, delta)
im[:, :, 0][im[:, :, 0] > 360.0] -= 360.0
im[:, :, 0][im[:, :, 0] < 0.0] += 360.0
return im
随机饱和度和色度变换
变换HSV空间,然后变回到OpenCV的BGR空间
# 9. 扭曲
def for_distort(self, im):
im = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
self.random_saturation(im)
self.random_hue(im)
im = cv2.cvtColor(im, cv2.COLOR_HSV2BGR)
return im
实验结果:
原始图片(图片源自:源自云从科技人头检测竞赛)
对一张图片进行裁剪,,然后随机进行色彩和对比度等的变化,这样就达到了数据增强的目的,使得模型训练的泛化能力更强~~,可能这个例子不明显,由于随机变换,所以可能色彩没有进行变换,可以看到裁剪后,对应的框的位置没有改变,这就是random_crop所实现的东西!( 图片源自:云从科技人头检测竞赛)