CoolFace
Apppublic

captchaboy/dfff4444

sourceHugging Faceupdated 4y agoView on Hugging Face
0likes
transforms.py330 linesDownload Raw Back to root
1import math2import numbers3import random4 5import cv26import numpy as np7from PIL import Image8from torchvision import transforms9from torchvision.transforms import Compose10 11 12def sample_asym(magnitude, size=None):13    return np.random.beta(1, 4, size) * magnitude14 15def sample_sym(magnitude, size=None):16    return (np.random.beta(4, 4, size=size) - 0.5) * 2 * magnitude17 18def sample_uniform(low, high, size=None):19    return np.random.uniform(low, high, size=size)20 21def get_interpolation(type='random'):22    if type == 'random':23        choice = [cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA]24        interpolation = choice[random.randint(0, len(choice)-1)]25    elif type == 'nearest': interpolation = cv2.INTER_NEAREST26    elif type == 'linear': interpolation = cv2.INTER_LINEAR27    elif type == 'cubic': interpolation = cv2.INTER_CUBIC28    elif type == 'area': interpolation = cv2.INTER_AREA29    else: raise TypeError('Interpolation types only nearest, linear, cubic, area are supported!')30    return interpolation31 32class CVRandomRotation(object):33    def __init__(self, degrees=15):34        assert isinstance(degrees, numbers.Number), "degree should be a single number."35        assert degrees >= 0, "degree must be positive."36        self.degrees = degrees37 38    @staticmethod39    def get_params(degrees):40        return sample_sym(degrees)41    42    def __call__(self, img):43        angle = self.get_params(self.degrees)44        src_h, src_w = img.shape[:2]45        M = cv2.getRotationMatrix2D(center=(src_w/2, src_h/2), angle=angle, scale=1.0)46        abs_cos, abs_sin = abs(M[0,0]), abs(M[0,1])47        dst_w = int(src_h * abs_sin + src_w * abs_cos)48        dst_h = int(src_h * abs_cos + src_w * abs_sin)49        M[0, 2] += (dst_w - src_w)/250        M[1, 2] += (dst_h - src_h)/251        52        flags = get_interpolation()53        return cv2.warpAffine(img, M, (dst_w, dst_h), flags=flags, borderMode=cv2.BORDER_REPLICATE)54 55class CVRandomAffine(object):56    def __init__(self, degrees, translate=None, scale=None, shear=None):57        assert isinstance(degrees, numbers.Number), "degree should be a single number."58        assert degrees >= 0, "degree must be positive."59        self.degrees = degrees60 61        if translate is not None:62            assert isinstance(translate, (tuple, list)) and len(translate) == 2, \63                "translate should be a list or tuple and it must be of length 2."64            for t in translate:65                if not (0.0 <= t <= 1.0):66                    raise ValueError("translation values should be between 0 and 1")67        self.translate = translate68 69        if scale is not None:70            assert isinstance(scale, (tuple, list)) and len(scale) == 2, \71                "scale should be a list or tuple and it must be of length 2."72            for s in scale:73                if s <= 0:74                    raise ValueError("scale values should be positive")75        self.scale = scale76 77        if shear is not None:78            if isinstance(shear, numbers.Number):79                if shear < 0:80                    raise ValueError("If shear is a single number, it must be positive.")81                self.shear = [shear]82            else:83                assert isinstance(shear, (tuple, list)) and (len(shear) == 2), \84                    "shear should be a list or tuple and it must be of length 2."85                self.shear = shear86        else:87            self.shear = shear88            89    def _get_inverse_affine_matrix(self, center, angle, translate, scale, shear):90        # https://github.com/pytorch/vision/blob/v0.4.0/torchvision/transforms/functional.py#L71791        from numpy import sin, cos, tan92        93        if isinstance(shear, numbers.Number):94            shear = [shear, 0]95 96        if not isinstance(shear, (tuple, list)) and len(shear) == 2:97            raise ValueError(98                "Shear should be a single value or a tuple/list containing " +99                "two values. Got {}".format(shear))100 101        rot = math.radians(angle)102        sx, sy = [math.radians(s) for s in shear]103 104        cx, cy = center105        tx, ty = translate106 107        # RSS without scaling108        a = cos(rot - sy) / cos(sy)109        b = -cos(rot - sy) * tan(sx) / cos(sy) - sin(rot)110        c = sin(rot - sy) / cos(sy)111        d = -sin(rot - sy) * tan(sx) / cos(sy) + cos(rot)112 113        # Inverted rotation matrix with scale and shear114        # det([[a, b], [c, d]]) == 1, since det(rotation) = 1 and det(shear) = 1115        M = [d, -b, 0,116             -c, a, 0]117        M = [x / scale for x in M]118 119        # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1120        M[2] += M[0] * (-cx - tx) + M[1] * (-cy - ty)121        M[5] += M[3] * (-cx - tx) + M[4] * (-cy - ty)122 123        # Apply center translation: C * RSS^-1 * C^-1 * T^-1124        M[2] += cx125        M[5] += cy126        return M127 128    @staticmethod129    def get_params(degrees, translate, scale_ranges, shears, height):        130        angle = sample_sym(degrees)131        if translate is not None:132            max_dx = translate[0] * height133            max_dy = translate[1] * height134            translations = (np.round(sample_sym(max_dx)), np.round(sample_sym(max_dy)))135        else:136            translations = (0, 0)137 138        if scale_ranges is not None:139            scale = sample_uniform(scale_ranges[0], scale_ranges[1])140        else:141            scale = 1.0142 143        if shears is not None:144            if len(shears) == 1:145                shear = [sample_sym(shears[0]), 0.]146            elif len(shears) == 2:147                shear = [sample_sym(shears[0]), sample_sym(shears[1])]148        else:149            shear = 0.0150 151        return angle, translations, scale, shear152    153    154    def __call__(self, img):155        src_h, src_w = img.shape[:2]156        angle, translate, scale, shear = self.get_params(157                self.degrees, self.translate, self.scale, self.shear, src_h)158 159        M = self._get_inverse_affine_matrix((src_w/2, src_h/2), angle, (0, 0), scale, shear)160        M = np.array(M).reshape(2,3)161        162        startpoints = [(0, 0), (src_w - 1, 0), (src_w - 1, src_h - 1), (0, src_h - 1)]163        project = lambda x, y, a, b, c: int(a*x + b*y + c)164        endpoints = [(project(x, y, *M[0]), project(x, y, *M[1])) for x, y in startpoints]165        166        rect = cv2.minAreaRect(np.array(endpoints))167        bbox = cv2.boxPoints(rect).astype(dtype=np.int)168        max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max()169        min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min()170 171        dst_w = int(max_x - min_x)172        dst_h = int(max_y - min_y)173        M[0, 2] += (dst_w - src_w) / 2174        M[1, 2] += (dst_h - src_h) / 2175        176        # add translate177        dst_w += int(abs(translate[0]))178        dst_h += int(abs(translate[1]))179        if translate[0] < 0: M[0, 2] += abs(translate[0])180        if translate[1] < 0: M[1, 2] += abs(translate[1])181            182        flags = get_interpolation()183        return cv2.warpAffine(img, M, (dst_w , dst_h), flags=flags, borderMode=cv2.BORDER_REPLICATE)184 185class CVRandomPerspective(object):186    def __init__(self, distortion=0.5):187        self.distortion = distortion188 189    def get_params(self, width, height, distortion):190        offset_h = sample_asym(distortion * height / 2, size=4).astype(dtype=np.int)191        offset_w = sample_asym(distortion * width / 2, size=4).astype(dtype=np.int)192        topleft  = (            offset_w[0],              offset_h[0])193        topright = (width - 1 - offset_w[1],              offset_h[1])194        botright = (width - 1 - offset_w[2], height - 1 - offset_h[2])195        botleft  = (            offset_w[3], height - 1 - offset_h[3])196 197        startpoints = [(0, 0), (width - 1, 0), (width - 1, height - 1), (0, height - 1)]198        endpoints = [topleft, topright, botright, botleft]199        return np.array(startpoints, dtype=np.float32), np.array(endpoints, dtype=np.float32)200    201    def __call__(self, img):202        height, width = img.shape[:2]203        startpoints, endpoints = self.get_params(width, height, self.distortion)204        M = cv2.getPerspectiveTransform(startpoints, endpoints)205 206        # TODO: more robust way to crop image207        rect = cv2.minAreaRect(endpoints)208        bbox = cv2.boxPoints(rect).astype(dtype=np.int)209        max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max()210        min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min()211        min_x, min_y = max(min_x, 0), max(min_y, 0)212 213        flags = get_interpolation() 214        img = cv2.warpPerspective(img, M, (max_x, max_y), flags=flags, borderMode=cv2.BORDER_REPLICATE)215        img = img[min_y:, min_x:]216        return img217 218class CVRescale(object):219    220    def __init__(self, factor=4, base_size=(128, 512)):221        """ Define image scales using gaussian pyramid and rescale image to target scale.222        223        Args:224            factor: the decayed factor from base size, factor=4 keeps target scale by default.225            base_size: base size the build the bottom layer of pyramid226        """227        if isinstance(factor, numbers.Number):228            self.factor = round(sample_uniform(0, factor))229        elif isinstance(factor, (tuple, list)) and len(factor) == 2:230            self.factor = round(sample_uniform(factor[0], factor[1]))231        else:232            raise Exception('factor must be number or list with length 2')233        # assert factor is valid234        self.base_h, self.base_w = base_size[:2]235 236    def __call__(self, img):237        if self.factor == 0: return img 238        src_h, src_w = img.shape[:2]239        cur_w, cur_h = self.base_w, self.base_h        240        scale_img = cv2.resize(img, (cur_w, cur_h), interpolation=get_interpolation())241        for _ in range(self.factor): 242            scale_img = cv2.pyrDown(scale_img)243        scale_img = cv2.resize(scale_img, (src_w, src_h), interpolation=get_interpolation())244        return scale_img245 246class CVGaussianNoise(object):247    def __init__(self, mean=0, var=20):248        self.mean = mean249        if isinstance(var, numbers.Number):250            self.var = max(int(sample_asym(var)), 1)251        elif isinstance(var, (tuple, list)) and len(var) == 2:252            self.var = int(sample_uniform(var[0], var[1]))253        else:254            raise Exception('degree must be number or list with length 2')255        256    def __call__(self, img):257        noise = np.random.normal(self.mean, self.var**0.5, img.shape)258        img = np.clip(img + noise, 0, 255).astype(np.uint8)259        return img260 261class CVMotionBlur(object):262    def __init__(self, degrees=12, angle=90):263        if isinstance(degrees, numbers.Number):264            self.degree = max(int(sample_asym(degrees)), 1)265        elif isinstance(degrees, (tuple, list)) and len(degrees) == 2:266            self.degree = int(sample_uniform(degrees[0], degrees[1]))267        else:268            raise Exception('degree must be number or list with length 2')269        self.angle = sample_uniform(-angle, angle)270 271    def __call__(self, img):272        M = cv2.getRotationMatrix2D((self.degree // 2, self.degree // 2), self.angle, 1)273        motion_blur_kernel = np.zeros((self.degree, self.degree))274        motion_blur_kernel[self.degree // 2, :] = 1275        motion_blur_kernel = cv2.warpAffine(motion_blur_kernel, M, (self.degree, self.degree))276        motion_blur_kernel = motion_blur_kernel / self.degree277        img = cv2.filter2D(img, -1, motion_blur_kernel)278        img = np.clip(img, 0, 255).astype(np.uint8)279        return img280 281class CVGeometry(object):282    def __init__(self, degrees=15, translate=(0.3, 0.3), scale=(0.5, 2.), 283                       shear=(45, 15), distortion=0.5, p=0.5):284        self.p = p285        type_p = random.random()286        if type_p < 0.33:287            self.transforms = CVRandomRotation(degrees=degrees)288        elif type_p < 0.66:289            self.transforms = CVRandomAffine(degrees=degrees, translate=translate, scale=scale, shear=shear)290        else:291            self.transforms = CVRandomPerspective(distortion=distortion)292 293    def __call__(self, img):294        if random.random() < self.p:295            img = np.array(img)296            return Image.fromarray(self.transforms(img))297        else: return img298 299class CVDeterioration(object):300    def __init__(self, var, degrees, factor, p=0.5):301        self.p = p302        transforms = []303        if var is not None:304            transforms.append(CVGaussianNoise(var=var))305        if degrees is not None:306            transforms.append(CVMotionBlur(degrees=degrees))307        if factor is not None:308            transforms.append(CVRescale(factor=factor))309            310        random.shuffle(transforms)311        transforms = Compose(transforms)312        self.transforms =  transforms313        314    def __call__(self, img):315        if random.random() < self.p:316            img = np.array(img)317            return Image.fromarray(self.transforms(img))318        else: return img319 320 321class CVColorJitter(object):322    def __init__(self, brightness=0.5, contrast=0.5, saturation=0.5, hue=0.1, p=0.5):323        self.p = p324        self.transforms = transforms.ColorJitter(brightness=brightness, contrast=contrast, 325                                                 saturation=saturation, hue=hue)326 327    def __call__(self, img):328        if random.random() < self.p: return self.transforms(img)329        else: return img330