blob: d2ed3ad9afe79108d505bb69260d7d2680575a8e [file] [log] [blame]
import numpy as np
import math
class RandSampler(object):
"""
Random sampler base class, used for data augmentation
Parameters:
----------
max_trials : int
maximum trials, if exceed this number, give up anyway
max_sample : int
maximum random crop samples to be generated
"""
def __init__(self, max_trials, max_sample):
assert max_trials > 0
self.max_trials = int(max_trials)
assert max_sample >= 0
self.max_sample = int(max_sample)
def sample(self, label):
"""
Interface for calling sampling function
Parameters:
----------
label : numpy.array (n x 5 matrix)
ground-truths
Returns:
----------
list of (crop_box, label) tuples, if failed, return empty list []
"""
return NotImplementedError
class RandCropper(RandSampler):
"""
Random cropping original images with various settings
Parameters:
----------
min_scale : float
minimum crop scale, (0, 1]
max_scale : float
maximum crop scale, (0, 1], must larger than min_scale
min_aspect_ratio : float
minimum crop aspect ratio, (0, 1]
max_aspect_ratio : float
maximum crop aspect ratio, [1, inf)
min_overlap : float
hreshold of minimum overlap between a rand crop and any gt
max_trials : int
maximum trials, if exceed this number, give up anyway
max_sample : int
maximum random crop samples to be generated
"""
def __init__(self, min_scale=1., max_scale=1.,
min_aspect_ratio=1., max_aspect_ratio=1.,
min_overlap=0., max_trials=50, max_sample=1):
super(RandCropper, self).__init__(max_trials, max_sample)
assert min_scale <= max_scale, "min_scale must <= max_scale"
assert 0 < min_scale and min_scale <= 1, "min_scale must in (0, 1]"
assert 0 < max_scale and max_scale <= 1, "max_scale must in (0, 1]"
self.min_scale = min_scale
self.max_scale = max_scale
assert 0 < min_aspect_ratio and min_aspect_ratio <= 1, "min_ratio must in (0, 1]"
assert 1 <= max_aspect_ratio , "max_ratio must >= 1"
self.min_aspect_ratio = min_aspect_ratio
self.max_aspect_ratio = max_aspect_ratio
assert 0 <= min_overlap and min_overlap <= 1, "min_overlap must in [0,1]"
self.min_overlap = min_overlap
self.config = {'gt_constraint' : 'center'}
def sample(self, label):
"""
generate random cropping boxes according to parameters
if satifactory crops generated, apply to ground-truth as well
Parameters:
----------
label : numpy.array (n x 5 matrix)
ground-truths
Returns:
----------
list of (crop_box, label) tuples, if failed, return empty list []
"""
samples = []
count = 0
for trial in range(self.max_trials):
if count >= self.max_sample:
return samples
scale = np.random.uniform(self.min_scale, self.max_scale)
min_ratio = max(self.min_aspect_ratio, scale * scale)
max_ratio = min(self.max_aspect_ratio, 1. / scale / scale)
ratio = math.sqrt(np.random.uniform(min_ratio, max_ratio))
width = scale * ratio
height = scale / ratio
left = np.random.uniform(0., 1 - width)
top = np.random.uniform(0., 1 - height)
rand_box = (left, top, left + width, top + height)
valid_mask = np.where(label[:, 0] > -1)[0]
gt = label[valid_mask, :]
ious = self._check_satisfy(rand_box, gt)
if ious is not None:
# transform gt labels after crop, discard bad ones
l, t, r, b = rand_box
new_gt_boxes = []
new_width = r - l
new_height = b - t
for i in range(valid_mask.size):
if ious[i] > 0:
xmin = max(0., (gt[i, 1] - l) / new_width)
ymin = max(0., (gt[i, 2] - t) / new_height)
xmax = min(1., (gt[i, 3] - l) / new_width)
ymax = min(1., (gt[i, 4] - t) / new_height)
new_gt_boxes.append([gt[i, 0], xmin, ymin, xmax, ymax])
if not new_gt_boxes:
continue
new_gt_boxes = np.array(new_gt_boxes)
label = np.lib.pad(new_gt_boxes,
((0, label.shape[0]-new_gt_boxes.shape[0]), (0,0)), \
'constant', constant_values=(-1, -1))
samples.append((rand_box, label))
count += 1
return samples
def _check_satisfy(self, rand_box, gt_boxes):
"""
check if overlap with any gt box is larger than threshold
"""
l, t, r, b = rand_box
num_gt = gt_boxes.shape[0]
ls = np.ones(num_gt) * l
ts = np.ones(num_gt) * t
rs = np.ones(num_gt) * r
bs = np.ones(num_gt) * b
mask = np.where(ls < gt_boxes[:, 1])[0]
ls[mask] = gt_boxes[mask, 1]
mask = np.where(ts < gt_boxes[:, 2])[0]
ts[mask] = gt_boxes[mask, 2]
mask = np.where(rs > gt_boxes[:, 3])[0]
rs[mask] = gt_boxes[mask, 3]
mask = np.where(bs > gt_boxes[:, 4])[0]
bs[mask] = gt_boxes[mask, 4]
w = rs - ls
w[w < 0] = 0
h = bs - ts
h[h < 0] = 0
inter_area = h * w
union_area = np.ones(num_gt) * max(0, r - l) * max(0, b - t)
union_area += (gt_boxes[:, 3] - gt_boxes[:, 1]) * (gt_boxes[:, 4] - gt_boxes[:, 2])
union_area -= inter_area
ious = inter_area / union_area
ious[union_area <= 0] = 0
max_iou = np.amax(ious)
if max_iou < self.min_overlap:
return None
# check ground-truth constraint
if self.config['gt_constraint'] == 'center':
for i in range(ious.shape[0]):
if ious[i] > 0:
gt_x = (gt_boxes[i, 1] + gt_boxes[i, 3]) / 2.0
gt_y = (gt_boxes[i, 2] + gt_boxes[i, 4]) / 2.0
if gt_x < l or gt_x > r or gt_y < t or gt_y > b:
return None
elif self.config['gt_constraint'] == 'corner':
for i in range(ious.shape[0]):
if ious[i] > 0:
if gt_boxes[i, 1] < l or gt_boxes[i, 3] > r \
or gt_boxes[i, 2] < t or gt_boxes[i, 4] > b:
return None
return ious
class RandPadder(RandSampler):
"""
Random cropping original images with various settings
Parameters:
----------
min_scale : float
minimum crop scale, [1, inf)
max_scale : float
maximum crop scale, [1, inf), must larger than min_scale
min_aspect_ratio : float
minimum crop aspect ratio, (0, 1]
max_aspect_ratio : float
maximum crop aspect ratio, [1, inf)
min_gt_scale : float
minimum ground-truth scale to be satisfied after padding,
either width or height, [0, 1]
max_trials : int
maximum trials, if exceed this number, give up anyway
max_sample : int
maximum random crop samples to be generated
"""
def __init__(self, min_scale=1., max_scale=1., min_aspect_ratio=1., \
max_aspect_ratio=1., min_gt_scale=.01, max_trials=50,
max_sample=1):
super(RandPadder, self).__init__(max_trials, max_sample)
assert min_scale <= max_scale, "min_scale must <= max_scale"
assert min_scale >= 1, "min_scale must in (0, 1]"
self.min_scale = min_scale
self.max_scale = max_scale
assert 0 < min_aspect_ratio and min_aspect_ratio <= 1, "min_ratio must in (0, 1]"
assert 1 <= max_aspect_ratio , "max_ratio must >= 1"
self.min_aspect_ratio = min_aspect_ratio
self.max_aspect_ratio = max_aspect_ratio
assert 0 <= min_gt_scale and min_gt_scale <= 1, "min_gt_scale must in [0, 1]"
self.min_gt_scale = min_gt_scale
def sample(self, label):
"""
generate random padding boxes according to parameters
if satifactory padding generated, apply to ground-truth as well
Parameters:
----------
label : numpy.array (n x 5 matrix)
ground-truths
Returns:
----------
list of (crop_box, label) tuples, if failed, return empty list []
"""
samples = []
count = 0
for trial in range(self.max_trials):
if count >= self.max_sample:
return samples
scale = np.random.uniform(self.min_scale, self.max_scale)
min_ratio = max(self.min_aspect_ratio, scale * scale)
max_ratio = min(self.max_aspect_ratio, 1. / scale / scale)
ratio = math.sqrt(np.random.uniform(min_ratio, max_ratio))
width = scale * ratio
if width < 1:
continue
height = scale / ratio
if height < 1:
continue
left = np.random.uniform(0., 1 - width)
top = np.random.uniform(0., 1 - height)
right = left + width
bot = top + height
rand_box = (left, top, right, bot)
valid_mask = np.where(label[:, 0] > -1)[0]
gt = label[valid_mask, :]
new_gt_boxes = []
for i in range(gt.shape[0]):
xmin = (gt[i, 1] - left) / width
ymin = (gt[i, 2] - top) / height
xmax = (gt[i, 3] - left) / width
ymax = (gt[i, 4] - top) / height
new_size = min(xmax - xmin, ymax - ymin)
if new_size < self.min_gt_scale:
new_gt_boxes = []
break
new_gt_boxes.append([gt[i, 0], xmin, ymin, xmax, ymax])
if not new_gt_boxes:
continue
new_gt_boxes = np.array(new_gt_boxes)
label = np.lib.pad(new_gt_boxes,
((0, label.shape[0]-new_gt_boxes.shape[0]), (0,0)), \
'constant', constant_values=(-1, -1))
samples.append((rand_box, label))
count += 1
return samples