| 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 |