import numpy as np
import math
import cv2
import numpy.random as random
class Compose(object):
"""Composes several augmentations together.
Args:
transforms (List[Transform]): list of transforms to compose.
Example:
>>> augmentations.Compose([
>>> transforms.CenterCrop(10),
>>> transforms.ToTensor(),
>>> ])
"""
def __init__(self, transforms):
self.transforms = transforms
def __call__(self, img, pts=None):
for t in self.transforms:
img, pts = t(img, pts)
return img, pts
class RandomMirror(object):
def __init__(self):
pass
def __call__(self, image, polygons=None):
if np.random.randint(2):
image = np.ascontiguousarray(image[:, ::-1])
_, width, _ = image.shape
for polygon in polygons:
polygon.points[:, 0] = width - polygon.points[:, 0]
return image, polygons
class AugmentColor(object):
def __init__(self):
self.U = np.array([[-0.56543481, 0.71983482, 0.40240142],
[-0.5989477, -0.02304967, -0.80036049],
[-0.56694071, -0.6935729, 0.44423429]], dtype=np.float32)
self.EV = np.array([1.65513492, 0.48450358, 0.1565086], dtype=np.float32)
self.sigma = 0.1
self.color_vec = None
def __call__(self, img, polygons=None):
color_vec = self.color_vec
if self.color_vec is None:
if not self.sigma > 0.0:
color_vec = np.zeros(3, dtype=np.float32)
else:
color_vec = np.random.normal(0.0, self.sigma, 3)
alpha = color_vec.astype(np.float32) * self.EV
noise = np.dot(self.U, alpha.T) * 255
return np.clip(img + noise[np.newaxis, np.newaxis, :], 0, 255), polygons
class RandomContrast(object):
def __init__(self, lower=0.5, upper=1.5):
self.lower = lower
self.upper = upper
assert self.upper >= self.lower, "contrast upper must be >= lower."
assert self.lower >= 0, "contrast lower must be non-negative."
def __call__(self, image, polygons=None):
if random.randint(2):
alpha = random.uniform(self.lower, self.upper)
image *= alpha
return np.clip(image, 0, 255), polygons
class RandomBrightness(object):
def __init__(self, delta=32):
assert delta >= 0.0
assert delta <= 255.0
self.delta = delta
def __call__(self, image, polygons=None):
image = image.astype(np.float32)
if random.randint(2):
delta = random.uniform(-self.delta, self.delta)
image += delta
return np.clip(image, 0, 255), polygons
class Rotate(object):
def __init__(self, up=30):
self.up = up
def rotate(self, center, pt, theta):
xr, yr = center
yr = -yr
x, y = pt[:, 0], pt[:, 1]
y = -y
theta = theta / 360 * 2 * math.pi
cos = math.cos(theta)
sin = math.sin(theta)
_x = xr + (x - xr) * cos - (y - yr) * sin
_y = yr + (x - xr) * sin + (y - yr) * cos
return _x, -_y
def __call__(self, img, polygons=None):
if np.random.randint(2):
return img, polygons
angle = np.random.uniform(-self.up, self.up)
rows, cols = img.shape[0:2]
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1.0)
img = cv2.warpAffine(img, M, (cols, rows), borderValue=[0, 0, 0])
center = cols / 2.0, rows / 2.0
if polygons is not None:
for polygon in polygons:
x, y = self.rotate(center, polygon.points, angle)
pts = np.vstack([x, y]).T
polygon.points = pts
return img, polygons
class SquarePadding(object):
def __call__(self, image, pts=None):
H, W, _ = image.shape
if H == W:
return image, pts
padding_size = max(H, W)
expand_image = np.zeros((padding_size, padding_size, 3), dtype=image.dtype)
if H > W:
y0, x0 = 0, (H - W) // 2
else:
y0, x0 = (W - H) // 2, 0
if pts is not None:
pts[:, 0] += x0
pts[:, 1] += y0
expand_image[y0:y0+H, x0:x0+W] = image
image = expand_image
return image, pts
class Padding(object):
def __init__(self, fill=0):
self.fill = fill
def __call__(self, image, polygons=None):
if np.random.randint(2):
return image, polygons
height, width, depth = image.shape
ratio = np.random.uniform(1, 2)
left = np.random.uniform(0, width * ratio - width)
top = np.random.uniform(0, height * ratio - height)
expand_image = np.zeros(
(int(height * ratio), int(width * ratio), depth),
dtype=image.dtype)
expand_image[:, :, :] = self.fill
expand_image[int(top):int(top + height),
int(left):int(left + width)] = image
image = expand_image
if polygons is not None:
for polygon in polygons:
polygon.points[:, 0] = polygon.points[:, 0] + left
polygon.points[:, 1] = polygon.points[:, 1] + top
return image, polygons
class RandomResizedCrop(object):
def __init__(self, size, scale=(0.3, 1.0), ratio=(3. / 4., 4. / 3.)):
self.size = (size, size)
self.scale = scale
self.ratio = ratio
@staticmethod
def get_params(img, scale, ratio):
"""Get parameters for ``crop`` for a random sized crop.
Args:
img (PIL Image): Image to be cropped.
scale (tuple): range of size of the origin size cropped
ratio (tuple): range of aspect ratio of the origin aspect ratio cropped
Returns:
tuple: params (i, j, h, w) to be passed to ``crop`` for a random
sized crop.
"""
for attempt in range(10):
area = img.shape[0] * img.shape[1]
target_area = np.random.uniform(*scale) * area
aspect_ratio = np.random.uniform(*ratio)
w = int(round(math.sqrt(target_area * aspect_ratio)))
h = int(round(math.sqrt(target_area / aspect_ratio)))
if np.random.random() < 0.5:
w, h = h, w
if h < img.shape[0] and w < img.shape[1]:
j = np.random.randint(0, img.shape[1] - w)
i = np.random.randint(0, img.shape[0] - h)
return i, j, h, w
w = min(img.shape[0], img.shape[1])
i = (img.shape[0] - w) // 2
j = (img.shape[1] - w) // 2
return i, j, w, w
def __call__(self, image, pts=None):
i, j, h, w = self.get_params(image, self.scale, self.ratio)
cropped = image[i:i + h, j:j + w, :]
pts = pts.copy()
mask = (pts[:, 1] >= i) * (pts[:, 0] >= j) * (pts[:, 1] < (i+h)) * (pts[:, 0] < (j+w))
pts[~mask, 2] = -1
scales = np.array([self.size[0]/w, self.size[1]/h])
pts[:, :2] -= np.array([j, i])
pts[:, :2] = (pts[:, :2] * scales)
img = cv2.resize(cropped, self.size)
return img, pts
class RandomResizedLimitCrop(object):
def __init__(self, size, scale=(0.3, 1.0), ratio=(3. / 4., 4. / 3.)):
self.size = (size, size)
self.scale = scale
self.ratio = ratio
@staticmethod
def get_params(img, scale, ratio):
for attempt in range(10):
area = img.shape[0] * img.shape[1]
target_area = np.random.uniform(*scale) * area
aspect_ratio = np.random.uniform(*ratio)
w = int(round(math.sqrt(target_area * aspect_ratio)))
h = int(round(math.sqrt(target_area / aspect_ratio)))
if np.random.random() < 0.5:
w, h = h, w
if h < img.shape[0] and w < img.shape[1]:
j = np.random.randint(0, img.shape[1] - w)
i = np.random.randint(0, img.shape[0] - h)
return i, j, h, w
w = min(img.shape[0], img.shape[1])
i = (img.shape[0] - w) // 2
j = (img.shape[1] - w) // 2
return i, j, w, w
def __call__(self, image, polygons=None):
i, j, h, w = self.get_params(image, self.scale, self.ratio)
cropped = image[i:i + h, j:j + w, :]
scales = np.array([self.size[0] / w, self.size[1] / h])
if polygons is not None:
for polygon in polygons:
polygon.points[:, 0] = (polygon.points[:, 0] - j) * scales[0]
polygon.points[:, 1] = (polygon.points[:, 1] - i) * scales[1]
img = cv2.resize(cropped, self.size)
return img, polygons
class Normalize(object):
def __init__(self, mean, std):
self.mean = np.array(mean)
self.std = np.array(std)
def __call__(self, image, polygons=None):
image = image.astype(np.float32)
image /= 255.0
image -= self.mean
image /= self.std
return image, polygons
class Resize(object):
def __init__(self, size=256):
self.size = size
def __call__(self, image, polygons=None):
h, w, _ = image.shape
image = cv2.resize(image, (self.size,
self.size))
scales = np.array([self.size / w, self.size / h])
if polygons is not None:
for polygon in polygons:
polygon.points = polygon.points * scales
return image, polygons
class Augmentation(object):
def __init__(self, size, mean, std):
self.size = size
self.mean = mean
self.std = std
self.augmentation = Compose([
Padding(),
RandomResizedLimitCrop(size=size, scale=(0.24, 1.0), ratio=(0.33, 3)),
RandomMirror(),
Rotate(),
Normalize(mean, std)
])
def __call__(self, image, polygons=None):
return self.augmentation(image, polygons)
class BaseTransform(object):
def __init__(self, size, mean, std):
self.size = size
self.mean = mean
self.std = std
self.augmentation = Compose([
Resize(size),
Normalize(mean, std)
])
def __call__(self, image, polygons=None):
return self.augmentation(image, polygons)