import random from typing import Tuple import sys from PIL import Image import numpy as np from fvcore.transforms import transform as T from detectron2.data.transforms import RandomCrop, StandardAugInput from detectron2.structures import BoxMode import torch from detectron2.data.transforms import Augmentation, PadTransform from fvcore.transforms.transform import Transform, NoOpTransform def gen_crop_transform_with_instance(crop_size, image_size, instances, crop_box=True): """ Generate a CropTransform so that the cropping region contains the center of the given instance. Args: crop_size (tuple): h, w in pixels image_size (tuple): h, w instance (dict): an annotation dict of one instance, in Detectron2's dataset format. """ bbox = random.choice(instances) crop_size = np.asarray(crop_size, dtype=np.int32) center_yx = (bbox[1] + bbox[3]) * 0.5, (bbox[0] + bbox[2]) * 0.5 assert ( image_size[0] >= center_yx[0] and image_size[1] >= center_yx[1] ), "The annotation bounding box is outside of the image!" assert ( image_size[0] >= crop_size[0] and image_size[1] >= crop_size[1] ), "Crop size is larger than image size!" min_yx = np.maximum(np.floor(center_yx).astype(np.int32) - crop_size, 0) max_yx = np.maximum(np.asarray(image_size, dtype=np.int32) - crop_size, 0) max_yx = np.minimum(max_yx, np.ceil(center_yx).astype(np.int32)) y0 = np.random.randint(min_yx[0], max_yx[0] + 1) x0 = np.random.randint(min_yx[1], max_yx[1] + 1) # if some instance is cropped extend the box if not crop_box: num_modifications = 0 modified = True # convert crop_size to float crop_size = crop_size.astype(np.float32) while modified: modified, x0, y0, crop_size = adjust_crop(x0, y0, crop_size, instances) num_modifications += 1 if num_modifications > 25: raise ValueError( "Cannot finished cropping adjustment within 25 tries (#instances {}).".format( len(instances) ) ) return T.CropTransform(0, 0, image_size[1], image_size[0]) return T.CropTransform(*map(int, (x0, y0, crop_size[1], crop_size[0]))) def adjust_crop(x0, y0, crop_size, instances, eps=1e-3): modified = False x1 = x0 + crop_size[1] y1 = y0 + crop_size[0] for bbox in instances: if bbox[0] < x0 - eps and bbox[2] > x0 + eps: crop_size[1] += x0 - bbox[0] x0 = bbox[0] modified = True if bbox[0] < x1 - eps and bbox[2] > x1 + eps: crop_size[1] += bbox[2] - x1 x1 = bbox[2] modified = True if bbox[1] < y0 - eps and bbox[3] > y0 + eps: crop_size[0] += y0 - bbox[1] y0 = bbox[1] modified = True if bbox[1] < y1 - eps and bbox[3] > y1 + eps: crop_size[0] += bbox[3] - y1 y1 = bbox[3] modified = True return modified, x0, y0, crop_size class RandomCropWithInstance(RandomCrop): """ Instance-aware cropping. """ def __init__(self, crop_type, crop_size, crop_instance=True): """ Args: crop_instance (bool): if False, extend cropping boxes to avoid cropping instances """ super().__init__(crop_type, crop_size) self.crop_instance = crop_instance # relative range self.input_args = ("image", "boxes") def get_transform(self, img, boxes): image_size = img.shape[:2] crop_size = self.get_crop_size(image_size) return gen_crop_transform_with_instance( crop_size, image_size, boxes, crop_box=self.crop_instance ) class Pad(Augmentation): def __init__(self, divisible_size = 32): super().__init__() self._init(locals()) def get_transform(self, img): ori_h, ori_w = img.shape[:2] # h, w if ori_h % 32 == 0: pad_h = 0 else: pad_h = 32 - ori_h % 32 if ori_w % 32 == 0: pad_w = 0 else: pad_w = 32 - ori_w % 32 # pad_h, pad_w = 32 - ori_h % 32, 32 - ori_w % 32 return PadTransform( 0, 0, pad_w, pad_h, pad_value=0 )