# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ function: transforms for classification in PaddleX<2.0 """ import math import numpy as np import cv2 from PIL import Image from .operators import Transform, Compose, RandomHorizontalFlip, RandomVerticalFlip, Normalize, \ ResizeByShort, CenterCrop, RandomDistort, ArrangeClassifier __all__ = [ 'Compose', 'RandomHorizontalFlip', 'RandomVerticalFlip', 'Normalize', 'ResizeByShort', 'CenterCrop', 'RandomDistort', 'ArrangeClassifier', 'RandomCrop', 'RandomRotate', 'ComposedClsTransforms' ] class RandomCrop(Transform): """对图像进行随机剪裁,模型训练时的数据增强操作。 1. 根据lower_scale、lower_ratio、upper_ratio计算随机剪裁的高、宽。 2. 根据随机剪裁的高、宽随机选取剪裁的起始点。 3. 剪裁图像。 4. 调整剪裁后的图像的大小到crop_size*crop_size。 Args: crop_size (int): 随机裁剪后重新调整的目标边长。默认为224。 lower_scale (float): 裁剪面积相对原面积比例的最小限制。默认为0.08。 lower_ratio (float): 宽变换比例的最小限制。默认为3. / 4。 upper_ratio (float): 宽变换比例的最大限制。默认为4. / 3。 """ def __init__(self, crop_size=224, lower_scale=0.08, lower_ratio=3. / 4, upper_ratio=4. / 3): super(RandomCrop, self).__init__() self.crop_size = crop_size self.lower_scale = lower_scale self.lower_ratio = lower_ratio self.upper_ratio = upper_ratio def apply_im(self, image): scale = [self.lower_scale, 1.0] ratio = [self.lower_ratio, self.upper_ratio] aspect_ratio = math.sqrt(np.random.uniform(*ratio)) w = 1. * aspect_ratio h = 1. / aspect_ratio bound = min((float(image.shape[0]) / image.shape[1]) / (h**2), (float(image.shape[1]) / image.shape[0]) / (w**2)) scale_max = min(scale[1], bound) scale_min = min(scale[0], bound) target_area = image.shape[0] * image.shape[1] * np.random.uniform( scale_min, scale_max) target_size = math.sqrt(target_area) w = int(target_size * w) h = int(target_size * h) i = np.random.randint(0, image.shape[0] - h + 1) j = np.random.randint(0, image.shape[1] - w + 1) image = image[i:i + h, j:j + w, :] image = cv2.resize(image, (self.crop_size, self.crop_size)) return image def apply(self, sample): sample['image'] = self.apply_im(sample['image']) return sample class RandomRotate(Transform): def __init__(self, rotate_range=30, prob=.5): """ Randomly rotate image(s) by an arbitrary angle between -rotate_range and rotate_range. Args: rotate_range(int, optional): Range of the rotation angle. Defaults to 30. prob(float, optional): Probability of operating rotation. Defaults to .5. """ self.rotate_range = rotate_range self.prob = prob def apply_im(self, image, angle): image = image.astype('uint8') image = Image.fromarray(image) image = image.rotate(angle) image = np.asarray(image).astype('float32') return image def apply(self, sample): rotate_lower = -self.rotate_range rotate_upper = self.rotate_range if np.random.uniform(0, 1) < self.prob: angle = np.random.uniform(rotate_lower, rotate_upper) sample['image'] = self.apply_im(sample['image'], angle) return sample class ComposedClsTransforms(Compose): """ 分类模型的基础Transforms流程,具体如下 训练阶段: 1. 随机从图像中crop一块子图,并resize成crop_size大小 2. 将1的输出按0.5的概率随机进行水平翻转 3. 将图像进行归一化 验证/预测阶段: 1. 将图像按比例Resize,使得最小边长度为crop_size[0] * 1.14 2. 从图像中心crop出一个大小为crop_size的图像 3. 将图像进行归一化 Args: mode(str): 图像处理流程所处阶段,训练/验证/预测,分别对应'train', 'eval', 'test' crop_size(int|list): 输入模型里的图像大小 mean(list): 图像均值 std(list): 图像方差 random_horizontal_flip(bool): 是否以0.5的概率使用随机水平翻转增强,该仅在mode为`train`时生效,默认为True """ def __init__(self, mode, crop_size=[224, 224], mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], random_horizontal_flip=True): width = crop_size if isinstance(crop_size, list): if crop_size[0] != crop_size[1]: raise Exception( "In classifier model, width and height should be equal, please modify your parameter `crop_size`" ) width = crop_size[0] if width % 32 != 0: raise Exception( "In classifier model, width and height should be multiple of 32, e.g 224、256、320...., please modify your parameter `crop_size`" ) if mode == 'train': # 训练时的transforms,包含数据增强 transforms = [ RandomCrop(crop_size=width), Normalize( mean=mean, std=std) ] if random_horizontal_flip: transforms.insert(0, RandomHorizontalFlip()) else: # 验证/预测时的transforms transforms = [ ResizeByShort(short_size=int(width * 1.14)), CenterCrop(crop_size=width), Normalize( mean=mean, std=std) ] super(ComposedClsTransforms, self).__init__(transforms)