cls_transforms.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. function:
  16. transforms for classification in PaddleX<2.0
  17. """
  18. import math
  19. import numpy as np
  20. import cv2
  21. from PIL import Image
  22. from .operators import Transform, Compose, RandomHorizontalFlip, RandomVerticalFlip, Normalize, \
  23. ResizeByShort, CenterCrop, RandomDistort, ArrangeClassifier
  24. __all__ = [
  25. 'Compose', 'RandomHorizontalFlip', 'RandomVerticalFlip', 'Normalize',
  26. 'ResizeByShort', 'CenterCrop', 'RandomDistort', 'ArrangeClassifier',
  27. 'RandomCrop', 'RandomRotate', 'ComposedClsTransforms'
  28. ]
  29. class RandomCrop(Transform):
  30. """对图像进行随机剪裁,模型训练时的数据增强操作。
  31. 1. 根据lower_scale、lower_ratio、upper_ratio计算随机剪裁的高、宽。
  32. 2. 根据随机剪裁的高、宽随机选取剪裁的起始点。
  33. 3. 剪裁图像。
  34. 4. 调整剪裁后的图像的大小到crop_size*crop_size。
  35. Args:
  36. crop_size (int): 随机裁剪后重新调整的目标边长。默认为224。
  37. lower_scale (float): 裁剪面积相对原面积比例的最小限制。默认为0.08。
  38. lower_ratio (float): 宽变换比例的最小限制。默认为3. / 4。
  39. upper_ratio (float): 宽变换比例的最大限制。默认为4. / 3。
  40. """
  41. def __init__(self,
  42. crop_size=224,
  43. lower_scale=0.08,
  44. lower_ratio=3. / 4,
  45. upper_ratio=4. / 3):
  46. super(RandomCrop, self).__init__()
  47. self.crop_size = crop_size
  48. self.lower_scale = lower_scale
  49. self.lower_ratio = lower_ratio
  50. self.upper_ratio = upper_ratio
  51. def apply_im(self, image):
  52. scale = [self.lower_scale, 1.0]
  53. ratio = [self.lower_ratio, self.upper_ratio]
  54. aspect_ratio = math.sqrt(np.random.uniform(*ratio))
  55. w = 1. * aspect_ratio
  56. h = 1. / aspect_ratio
  57. bound = min((float(image.shape[0]) / image.shape[1]) / (h**2),
  58. (float(image.shape[1]) / image.shape[0]) / (w**2))
  59. scale_max = min(scale[1], bound)
  60. scale_min = min(scale[0], bound)
  61. target_area = image.shape[0] * image.shape[1] * np.random.uniform(
  62. scale_min, scale_max)
  63. target_size = math.sqrt(target_area)
  64. w = int(target_size * w)
  65. h = int(target_size * h)
  66. i = np.random.randint(0, image.shape[0] - h + 1)
  67. j = np.random.randint(0, image.shape[1] - w + 1)
  68. image = image[i:i + h, j:j + w, :]
  69. image = cv2.resize(image, (self.crop_size, self.crop_size))
  70. return image
  71. def apply(self, sample):
  72. sample['image'] = self.apply_im(sample['image'])
  73. return sample
  74. class RandomRotate(Transform):
  75. def __init__(self, rotate_range=30, prob=.5):
  76. """
  77. Randomly rotate image(s) by an arbitrary angle between -rotate_range and rotate_range.
  78. Args:
  79. rotate_range(int, optional): Range of the rotation angle. Defaults to 30.
  80. prob(float, optional): Probability of operating rotation. Defaults to .5.
  81. """
  82. self.rotate_range = rotate_range
  83. self.prob = prob
  84. def apply_im(self, image, angle):
  85. image = image.astype('uint8')
  86. image = Image.fromarray(image)
  87. image = image.rotate(angle)
  88. image = np.asarray(image).astype('float32')
  89. return image
  90. def apply(self, sample):
  91. rotate_lower = -self.rotate_range
  92. rotate_upper = self.rotate_range
  93. if np.random.uniform(0, 1) < self.prob:
  94. angle = np.random.uniform(rotate_lower, rotate_upper)
  95. sample['image'] = self.apply_im(sample['image'], angle)
  96. return sample
  97. class ComposedClsTransforms(Compose):
  98. """ 分类模型的基础Transforms流程,具体如下
  99. 训练阶段:
  100. 1. 随机从图像中crop一块子图,并resize成crop_size大小
  101. 2. 将1的输出按0.5的概率随机进行水平翻转
  102. 3. 将图像进行归一化
  103. 验证/预测阶段:
  104. 1. 将图像按比例Resize,使得最小边长度为crop_size[0] * 1.14
  105. 2. 从图像中心crop出一个大小为crop_size的图像
  106. 3. 将图像进行归一化
  107. Args:
  108. mode(str): 图像处理流程所处阶段,训练/验证/预测,分别对应'train', 'eval', 'test'
  109. crop_size(int|list): 输入模型里的图像大小
  110. mean(list): 图像均值
  111. std(list): 图像方差
  112. random_horizontal_flip(bool): 是否以0.5的概率使用随机水平翻转增强,该仅在mode为`train`时生效,默认为True
  113. """
  114. def __init__(self,
  115. mode,
  116. crop_size=[224, 224],
  117. mean=[0.485, 0.456, 0.406],
  118. std=[0.229, 0.224, 0.225],
  119. random_horizontal_flip=True):
  120. width = crop_size
  121. if isinstance(crop_size, list):
  122. if crop_size[0] != crop_size[1]:
  123. raise Exception(
  124. "In classifier model, width and height should be equal, please modify your parameter `crop_size`"
  125. )
  126. width = crop_size[0]
  127. if width % 32 != 0:
  128. raise Exception(
  129. "In classifier model, width and height should be multiple of 32, e.g 224、256、320...., please modify your parameter `crop_size`"
  130. )
  131. if mode == 'train':
  132. # 训练时的transforms,包含数据增强
  133. transforms = [
  134. RandomCrop(crop_size=width), Normalize(
  135. mean=mean, std=std)
  136. ]
  137. if random_horizontal_flip:
  138. transforms.insert(0, RandomHorizontalFlip())
  139. else:
  140. # 验证/预测时的transforms
  141. transforms = [
  142. ResizeByShort(short_size=int(width * 1.14)),
  143. CenterCrop(crop_size=width), Normalize(
  144. mean=mean, std=std)
  145. ]
  146. super(ComposedClsTransforms, self).__init__(transforms)