cls_transforms.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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. class RandomCrop(Transform):
  25. """对图像进行随机剪裁,模型训练时的数据增强操作。
  26. 1. 根据lower_scale、lower_ratio、upper_ratio计算随机剪裁的高、宽。
  27. 2. 根据随机剪裁的高、宽随机选取剪裁的起始点。
  28. 3. 剪裁图像。
  29. 4. 调整剪裁后的图像的大小到crop_size*crop_size。
  30. Args:
  31. crop_size (int): 随机裁剪后重新调整的目标边长。默认为224。
  32. lower_scale (float): 裁剪面积相对原面积比例的最小限制。默认为0.08。
  33. lower_ratio (float): 宽变换比例的最小限制。默认为3. / 4。
  34. upper_ratio (float): 宽变换比例的最大限制。默认为4. / 3。
  35. """
  36. def __init__(self,
  37. crop_size=224,
  38. lower_scale=0.08,
  39. lower_ratio=3. / 4,
  40. upper_ratio=4. / 3):
  41. super(RandomCrop, self).__init__()
  42. self.crop_size = crop_size
  43. self.lower_scale = lower_scale
  44. self.lower_ratio = lower_ratio
  45. self.upper_ratio = upper_ratio
  46. def apply_im(self, image):
  47. scale = [self.lower_scale, 1.0]
  48. ratio = [self.lower_ratio, self.upper_ratio]
  49. aspect_ratio = math.sqrt(np.random.uniform(*ratio))
  50. w = 1. * aspect_ratio
  51. h = 1. / aspect_ratio
  52. bound = min((float(image.shape[0]) / image.shape[1]) / (h**2),
  53. (float(image.shape[1]) / image.shape[0]) / (w**2))
  54. scale_max = min(scale[1], bound)
  55. scale_min = min(scale[0], bound)
  56. target_area = image.shape[0] * image.shape[1] * np.random.uniform(
  57. scale_min, scale_max)
  58. target_size = math.sqrt(target_area)
  59. w = int(target_size * w)
  60. h = int(target_size * h)
  61. i = np.random.randint(0, image.shape[0] - h + 1)
  62. j = np.random.randint(0, image.shape[1] - w + 1)
  63. image = image[i:i + h, j:j + w, :]
  64. image = cv2.resize(image, (self.crop_size, self.crop_size))
  65. return image
  66. def apply(self, sample):
  67. sample['image'] = self.apply_im(sample['image'])
  68. return sample
  69. class RandomRotate(Transform):
  70. def __init__(self, rotate_range=30, prob=.5):
  71. """
  72. Randomly rotate image(s) by an arbitrary angle between -rotate_range and rotate_range.
  73. Args:
  74. rotate_range(int, optional): Range of the rotation angle. Defaults to 30.
  75. prob(float, optional): Probability of operating rotation. Defaults to .5.
  76. """
  77. self.rotate_range = rotate_range
  78. self.prob = prob
  79. def apply_im(self, image, angle):
  80. image = image.astype('uint8')
  81. image = Image.fromarray(image)
  82. image = image.rotate(angle)
  83. image = np.asarray(image).astype('float32')
  84. return image
  85. def apply(self, sample):
  86. rotate_lower = -self.rotate_range
  87. rotate_upper = self.rotate_range
  88. if np.random.uniform(0, 1) < self.prob:
  89. angle = np.random.uniform(rotate_lower, rotate_upper)
  90. sample['image'] = self.apply_im(sample['image'], angle)
  91. return sample
  92. class ComposedClsTransforms(Compose):
  93. """ 分类模型的基础Transforms流程,具体如下
  94. 训练阶段:
  95. 1. 随机从图像中crop一块子图,并resize成crop_size大小
  96. 2. 将1的输出按0.5的概率随机进行水平翻转
  97. 3. 将图像进行归一化
  98. 验证/预测阶段:
  99. 1. 将图像按比例Resize,使得最小边长度为crop_size[0] * 1.14
  100. 2. 从图像中心crop出一个大小为crop_size的图像
  101. 3. 将图像进行归一化
  102. Args:
  103. mode(str): 图像处理流程所处阶段,训练/验证/预测,分别对应'train', 'eval', 'test'
  104. crop_size(int|list): 输入模型里的图像大小
  105. mean(list): 图像均值
  106. std(list): 图像方差
  107. random_horizontal_flip(bool): 是否以0.5的概率使用随机水平翻转增强,该仅在mode为`train`时生效,默认为True
  108. """
  109. def __init__(self,
  110. mode,
  111. crop_size=[224, 224],
  112. mean=[0.485, 0.456, 0.406],
  113. std=[0.229, 0.224, 0.225],
  114. random_horizontal_flip=True):
  115. width = crop_size
  116. if isinstance(crop_size, list):
  117. if crop_size[0] != crop_size[1]:
  118. raise Exception(
  119. "In classifier model, width and height should be equal, please modify your parameter `crop_size`"
  120. )
  121. width = crop_size[0]
  122. if width % 32 != 0:
  123. raise Exception(
  124. "In classifier model, width and height should be multiple of 32, e.g 224、256、320...., please modify your parameter `crop_size`"
  125. )
  126. if mode == 'train':
  127. # 训练时的transforms,包含数据增强
  128. transforms = [
  129. RandomCrop(crop_size=width), Normalize(
  130. mean=mean, std=std)
  131. ]
  132. if random_horizontal_flip:
  133. transforms.insert(0, RandomHorizontalFlip())
  134. else:
  135. # 验证/预测时的transforms
  136. transforms = [
  137. ResizeByShort(short_size=int(width * 1.14)),
  138. CenterCrop(crop_size=width), Normalize(
  139. mean=mean, std=std)
  140. ]
  141. super(ComposedClsTransforms, self).__init__(transforms)