语义分割中的数据增强方法
为什么要使用数据增强?
在实际生产项目中,我们通常都难以拥有充足的数据来完成任务,为了充分利用有限的数据,我们需要进行数据增强,使得少量数据产生的价值等价于更多数据的价值。在这篇博文中,将会简单介绍一些语义分割的数据增强方法并贴出源码。
随机翻转
主要是三种选择,分别是水平、垂直和水平垂直翻转。
其效果如下图所示:
class RandomFlip:
def __init__(self, prob=1.0):
self.prob = prob
def __call__(self, img, mask=None):
if random.random() < self.prob:
d = random.randint(-1, 1)
img = cv2.flip(img, d)
if mask is not None:
mask = cv2.flip(mask, d)
return img, mask
随机旋转n*90°
N取值范围在0到4之间。
其效果如下:
class RandomRotate90:
def __init__(self, prob=1.0):
self.prob = prob
def __call__(self, img, mask=None):
if random.random() < self.prob:
factor = random.randint(0, 4)
img = np.rot90(img, factor)
if mask is not None:
mask = np.rot90(mask, factor)
return img.copy(), mask.copy()
中心旋转
该方法以图片中心为旋转点,旋转一定的角度。
其效果如下图所示:
class Rotate:
def __init__(self, limit=90, prob=1.0):
self.prob = prob
self.limit = limit
def __call__(self, img, mask=None):
if random.random() < self.prob:
angle = random.uniform(-self.limit, self.limit)
height, width = img.shape[0:2]
mat = cv2.getRotationMatrix2D((width/2, height/2), angle, 1.0)
img = cv2.warpAffine(img, mat, (height, width),
flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_REFLECT_101)
if mask is not None:
mask = cv2.warpAffine(mask, mat, (height, width),
flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_REFLECT_101)
return img, mask
平移
在垂直和水平方向平移一段距离。
其效果如下:
class Shift:
def __init__(self, limit=50, prob=1.0):
self.limit = limit
self.prob = prob
def __call__(self, img, mask=None):
if random.random() < self.prob:
limit = self.limit
dx = round(random.uniform(-limit, limit))
dy = round(random.uniform(-limit, limit))
height, width, channel = img.shape
y1 = limit + 1 + dy
y2 = y1 + height
x1 = limit + 1 + dx
x2 = x1 + width
img1 = cv2.copyMakeBorder(img, limit+1, limit + 1, limit + 1, limit +1,
borderType=cv2.BORDER_REFLECT_101)
img = img1[y1:y2, x1:x2, :]
if mask is not None:
mask1 = cv2.copyMakeBorder(mask, limit+1, limit + 1, limit + 1, limit +1,
borderType=cv2.BORDER_REFLECT_101)
mask = mask1[y1:y2, x1:x2, :]
return img, mask
Cutout
这篇方法的出发点除了解决遮挡问题外,还有从dropout上得到启发(所以也称为Cutout)。众所周知,Dropout随机隐藏一些神经元,最后的网络模型相当于多个模型的集成。类似于dropout的思路,这篇文章将drop用在了输入图片上,并且drop掉连续的区域——即矩形区域。
class Cutout:
def __init__(self, num_holes=8, max_h_size=8, max_w_size=8, fill_value=0, prob=1.):
self.num_holes = num_holes
self.max_h_size = max_h_size
self.max_w_size = max_w_size
self.fill_value = fill_value
self.prob = prob
def __call__(self, img, mask=None):
if random.random() < self.prob:
h = img.shape[0]
w = img.shape[1]
# c = img.shape[2]
# img2 = np.ones([h, w], np.float32)
for _ in range(self.num_holes):
y = np.random.randint(h)
x = np.random.randint(w)
y1 = np.clip(max(0, y - self.max_h_size // 2), 0, h)
y2 = np.clip(max(0, y + self.max_h_size // 2), 0, h)
x1 = np.clip(max(0, x - self.max_w_size // 2), 0, w)
x2 = np.clip(max(0, x + self.max_w_size // 2), 0, w)
img[y1: y2, x1: x2, :] = self.fill_value
if mask is not None:
mask[y1: y2, x1: x2, :] = self.fill_value
return img, mask
随机缩放
若缩放的尺寸大于原尺寸,则进行随机裁剪至原尺寸;若缩放的尺寸小于原尺寸,则进行镜像延边,延长至原尺寸。
class Rescale(object):
def __init__(self, output_size, prob=0.75):
self.prob = prob
assert isinstance(output_size, (int,tuple))
self.output_size = output_size
def __call__(self, image, label):
if random.random() < self.prob:
raw_h, raw_w = image.shape[:2]
img = cv.resize(image, (self.output_size, self.output_size))
lbl = cv.resize(label, (self.output_size, self.output_size))
h, w = img.shape[:2]
if h > raw_w:
i = random.randint(0, h - raw_h)
j = random.randint(0, w - raw_h)
img = img[i:i + raw_h, j:j + raw_h]
lbl = lbl[i:i + raw_h, j:j + raw_h]
else:
res_h = raw_w - h
img = cv.copyMakeBorder(img, res_h, 0, res_h, 0, borderType=cv.BORDER_REFLECT)
lbl = cv.copyMakeBorder(lbl, res_h, 0, res_h, 0, borderType=cv.BORDER_REFLECT)
return img, lbl
else:
return image, label
怎么同时使用?
class DualCompose:
def __init__(self, transforms):
self.transforms = transforms
def __call__(self, x, mask=None):
for t in self.transforms:
x, mask = t(x, mask)
return x, mask
class OneOf:
def __init__(self, transforms, prob=0.5):
self.transforms = transforms
self.prob = prob
def __call__(self, x, mask=None):
if random.random() < self.prob:
t = random.choice(self.transforms)
t.prob = 1.
x, mask = t(x, mask)
return x, mask
class OneOrOther:
def __init__(self, first, second, prob=0.5):
self.first = first
first.prob = 1.
self.second = second
second.prob = 1.
self.prob = prob
def __call__(self, x, mask=None):
if random.random() < self.prob:
x, mask = self.first(x, mask)
else:
x, mask = self.second(x, mask)
return x, mask
以DualCompose为例子,调用上述介绍的方法。
transform = DualCompose([
RandomFlip(),
RandomRotate90(),
Rotate(),
Shift(),
])
image, mask = transform(img, msk)