transforms.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. # Copyright (c) 2020 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. import random
  15. import cv2
  16. import numpy as np
  17. from PIL import Image
  18. from paddlex.paddleseg.cvlibs import manager
  19. from paddlex.paddleseg.transforms import functional
  20. @manager.TRANSFORMS.add_component
  21. class Compose:
  22. """
  23. Do transformation on input data with corresponding pre-processing and augmentation operations.
  24. The shape of input data to all operations is [height, width, channels].
  25. Args:
  26. transforms (list): A list contains data pre-processing or augmentation. Empty list means only reading images, no transformation.
  27. to_rgb (bool, optional): If converting image to RGB color space. Default: True.
  28. Raises:
  29. TypeError: When 'transforms' is not a list.
  30. ValueError: when the length of 'transforms' is less than 1.
  31. """
  32. def __init__(self, transforms, to_rgb=True):
  33. if not isinstance(transforms, list):
  34. raise TypeError('The transforms must be a list!')
  35. self.transforms = transforms
  36. self.to_rgb = to_rgb
  37. def __call__(self, im, label=None):
  38. """
  39. Args:
  40. im (str|np.ndarray): It is either image path or image object.
  41. label (str|np.ndarray): It is either label path or label ndarray.
  42. Returns:
  43. (tuple). A tuple including image, image info, and label after transformation.
  44. """
  45. if isinstance(im, str):
  46. im = cv2.imread(im).astype('float32')
  47. if isinstance(label, str):
  48. label = np.asarray(Image.open(label))
  49. if im is None:
  50. raise ValueError('Can\'t read The image file {}!'.format(im))
  51. if self.to_rgb:
  52. im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
  53. for op in self.transforms:
  54. outputs = op(im, label)
  55. im = outputs[0]
  56. if len(outputs) == 2:
  57. label = outputs[1]
  58. im = np.transpose(im, (2, 0, 1))
  59. return (im, label)
  60. @manager.TRANSFORMS.add_component
  61. class RandomHorizontalFlip:
  62. """
  63. Flip an image horizontally with a certain probability.
  64. Args:
  65. prob (float, optional): A probability of horizontally flipping. Default: 0.5.
  66. """
  67. def __init__(self, prob=0.5):
  68. self.prob = prob
  69. def __call__(self, im, label=None):
  70. if random.random() < self.prob:
  71. im = functional.horizontal_flip(im)
  72. if label is not None:
  73. label = functional.horizontal_flip(label)
  74. if label is None:
  75. return (im, )
  76. else:
  77. return (im, label)
  78. @manager.TRANSFORMS.add_component
  79. class RandomVerticalFlip:
  80. """
  81. Flip an image vertically with a certain probability.
  82. Args:
  83. prob (float, optional): A probability of vertical flipping. Default: 0.1.
  84. """
  85. def __init__(self, prob=0.1):
  86. self.prob = prob
  87. def __call__(self, im, label=None):
  88. if random.random() < self.prob:
  89. im = functional.vertical_flip(im)
  90. if label is not None:
  91. label = functional.vertical_flip(label)
  92. if label is None:
  93. return (im, )
  94. else:
  95. return (im, label)
  96. @manager.TRANSFORMS.add_component
  97. class Resize:
  98. """
  99. Resize an image.
  100. Args:
  101. target_size (list|tuple, optional): The target size of image. Default: (512, 512).
  102. interp (str, optional): The interpolation mode of resize is consistent with opencv.
  103. ['NEAREST', 'LINEAR', 'CUBIC', 'AREA', 'LANCZOS4', 'RANDOM']. Note that when it is
  104. 'RANDOM', a random interpolation mode would be specified. Default: "LINEAR".
  105. Raises:
  106. TypeError: When 'target_size' type is neither list nor tuple.
  107. ValueError: When "interp" is out of pre-defined methods ('NEAREST', 'LINEAR', 'CUBIC',
  108. 'AREA', 'LANCZOS4', 'RANDOM').
  109. """
  110. # The interpolation mode
  111. interp_dict = {
  112. 'NEAREST': cv2.INTER_NEAREST,
  113. 'LINEAR': cv2.INTER_LINEAR,
  114. 'CUBIC': cv2.INTER_CUBIC,
  115. 'AREA': cv2.INTER_AREA,
  116. 'LANCZOS4': cv2.INTER_LANCZOS4
  117. }
  118. def __init__(self, target_size=(512, 512), interp='LINEAR'):
  119. self.interp = interp
  120. if not (interp == "RANDOM" or interp in self.interp_dict):
  121. raise ValueError("`interp` should be one of {}".format(
  122. self.interp_dict.keys()))
  123. if isinstance(target_size, list) or isinstance(target_size, tuple):
  124. if len(target_size) != 2:
  125. raise ValueError(
  126. '`target_size` should include 2 elements, but it is {}'.
  127. format(target_size))
  128. else:
  129. raise TypeError(
  130. "Type of `target_size` is invalid. It should be list or tuple, but it is {}"
  131. .format(type(target_size)))
  132. self.target_size = target_size
  133. def __call__(self, im, label=None):
  134. """
  135. Args:
  136. im (np.ndarray): The Image data.
  137. label (np.ndarray, optional): The label data. Default: None.
  138. Returns:
  139. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label),
  140. Raises:
  141. TypeError: When the 'img' type is not numpy.
  142. ValueError: When the length of "im" shape is not 3.
  143. """
  144. if not isinstance(im, np.ndarray):
  145. raise TypeError("Resize: image type is not numpy.")
  146. if len(im.shape) != 3:
  147. raise ValueError('Resize: image is not 3-dimensional.')
  148. if self.interp == "RANDOM":
  149. interp = random.choice(list(self.interp_dict.keys()))
  150. else:
  151. interp = self.interp
  152. im = functional.resize(im, self.target_size, self.interp_dict[interp])
  153. if label is not None:
  154. label = functional.resize(label, self.target_size,
  155. cv2.INTER_NEAREST)
  156. if label is None:
  157. return (im, )
  158. else:
  159. return (im, label)
  160. @manager.TRANSFORMS.add_component
  161. class ResizeByLong:
  162. """
  163. Resize the long side of an image to given size, and then scale the other side proportionally.
  164. Args:
  165. long_size (int): The target size of long side.
  166. """
  167. def __init__(self, long_size):
  168. self.long_size = long_size
  169. def __call__(self, im, label=None):
  170. """
  171. Args:
  172. im (np.ndarray): The Image data.
  173. label (np.ndarray, optional): The label data. Default: None.
  174. Returns:
  175. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  176. """
  177. im = functional.resize_long(im, self.long_size)
  178. if label is not None:
  179. label = functional.resize_long(label, self.long_size,
  180. cv2.INTER_NEAREST)
  181. if label is None:
  182. return (im, )
  183. else:
  184. return (im, label)
  185. @manager.TRANSFORMS.add_component
  186. class LimitLong:
  187. """
  188. Limit the long edge of image.
  189. If the long edge is larger than max_long, resize the long edge
  190. to max_long, while scale the short edge proportionally.
  191. If the long edge is smaller than min_long, resize the long edge
  192. to min_long, while scale the short edge proportionally.
  193. Args:
  194. max_long (int, optional): If the long edge of image is larger than max_long,
  195. it will be resize to max_long. Default: None.
  196. min_long (int, optional): If the long edge of image is smaller than min_long,
  197. it will be resize to min_long. Default: None.
  198. """
  199. def __init__(self, max_long=None, min_long=None):
  200. if max_long is not None:
  201. if not isinstance(max_long, int):
  202. raise TypeError(
  203. "Type of `max_long` is invalid. It should be int, but it is {}"
  204. .format(type(max_long)))
  205. if min_long is not None:
  206. if not isinstance(min_long, int):
  207. raise TypeError(
  208. "Type of `min_long` is invalid. It should be int, but it is {}"
  209. .format(type(min_long)))
  210. if (max_long is not None) and (min_long is not None):
  211. if min_long > max_long:
  212. raise ValueError(
  213. '`max_long should not smaller than min_long, but they are {} and {}'
  214. .format(max_long, min_long))
  215. self.max_long = max_long
  216. self.min_long = min_long
  217. def __call__(self, im, label=None):
  218. """
  219. Args:
  220. im (np.ndarray): The Image data.
  221. label (np.ndarray, optional): The label data. Default: None.
  222. Returns:
  223. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  224. """
  225. h, w = im.shape[0], im.shape[1]
  226. long_edge = max(h, w)
  227. target = long_edge
  228. if (self.max_long is not None) and (long_edge > self.max_long):
  229. target = self.max_long
  230. elif (self.min_long is not None) and (long_edge < self.min_long):
  231. target = self.min_long
  232. if target != long_edge:
  233. im = functional.resize_long(im, target)
  234. if label is not None:
  235. label = functional.resize_long(label, target,
  236. cv2.INTER_NEAREST)
  237. if label is None:
  238. return (im, )
  239. else:
  240. return (im, label)
  241. @manager.TRANSFORMS.add_component
  242. class ResizeRangeScaling:
  243. """
  244. Resize the long side of an image into a range, and then scale the other side proportionally.
  245. Args:
  246. min_value (int, optional): The minimum value of long side after resize. Default: 400.
  247. max_value (int, optional): The maximum value of long side after resize. Default: 600.
  248. """
  249. def __init__(self, min_value=400, max_value=600):
  250. if min_value > max_value:
  251. raise ValueError('min_value must be less than max_value, '
  252. 'but they are {} and {}.'.format(min_value,
  253. max_value))
  254. self.min_value = min_value
  255. self.max_value = max_value
  256. def __call__(self, im, label=None):
  257. """
  258. Args:
  259. im (np.ndarray): The Image data.
  260. label (np.ndarray, optional): The label data. Default: None.
  261. Returns:
  262. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  263. """
  264. if self.min_value == self.max_value:
  265. random_size = self.max_value
  266. else:
  267. random_size = int(
  268. np.random.uniform(self.min_value, self.max_value) + 0.5)
  269. im = functional.resize_long(im, random_size, cv2.INTER_LINEAR)
  270. if label is not None:
  271. label = functional.resize_long(label, random_size,
  272. cv2.INTER_NEAREST)
  273. if label is None:
  274. return (im, )
  275. else:
  276. return (im, label)
  277. @manager.TRANSFORMS.add_component
  278. class ResizeStepScaling:
  279. """
  280. Scale an image proportionally within a range.
  281. Args:
  282. min_scale_factor (float, optional): The minimum scale. Default: 0.75.
  283. max_scale_factor (float, optional): The maximum scale. Default: 1.25.
  284. scale_step_size (float, optional): The scale interval. Default: 0.25.
  285. Raises:
  286. ValueError: When min_scale_factor is smaller than max_scale_factor.
  287. """
  288. def __init__(self,
  289. min_scale_factor=0.75,
  290. max_scale_factor=1.25,
  291. scale_step_size=0.25):
  292. if min_scale_factor > max_scale_factor:
  293. raise ValueError(
  294. 'min_scale_factor must be less than max_scale_factor, '
  295. 'but they are {} and {}.'.format(min_scale_factor,
  296. max_scale_factor))
  297. self.min_scale_factor = min_scale_factor
  298. self.max_scale_factor = max_scale_factor
  299. self.scale_step_size = scale_step_size
  300. def __call__(self, im, label=None):
  301. """
  302. Args:
  303. im (np.ndarray): The Image data.
  304. label (np.ndarray, optional): The label data. Default: None.
  305. Returns:
  306. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  307. """
  308. if self.min_scale_factor == self.max_scale_factor:
  309. scale_factor = self.min_scale_factor
  310. elif self.scale_step_size == 0:
  311. scale_factor = np.random.uniform(self.min_scale_factor,
  312. self.max_scale_factor)
  313. else:
  314. num_steps = int((self.max_scale_factor - self.min_scale_factor) /
  315. self.scale_step_size + 1)
  316. scale_factors = np.linspace(self.min_scale_factor,
  317. self.max_scale_factor,
  318. num_steps).tolist()
  319. np.random.shuffle(scale_factors)
  320. scale_factor = scale_factors[0]
  321. w = int(round(scale_factor * im.shape[1]))
  322. h = int(round(scale_factor * im.shape[0]))
  323. im = functional.resize(im, (w, h), cv2.INTER_LINEAR)
  324. if label is not None:
  325. label = functional.resize(label, (w, h), cv2.INTER_NEAREST)
  326. if label is None:
  327. return (im, )
  328. else:
  329. return (im, label)
  330. @manager.TRANSFORMS.add_component
  331. class Normalize:
  332. """
  333. Normalize an image.
  334. Args:
  335. mean (list, optional): The mean value of a data set. Default: [0.5, 0.5, 0.5].
  336. std (list, optional): The standard deviation of a data set. Default: [0.5, 0.5, 0.5].
  337. Raises:
  338. ValueError: When mean/std is not list or any value in std is 0.
  339. """
  340. def __init__(self, mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)):
  341. self.mean = mean
  342. self.std = std
  343. if not (isinstance(self.mean,
  344. (list, tuple)) and isinstance(self.std,
  345. (list, tuple))):
  346. raise ValueError(
  347. "{}: input type is invalid. It should be list or tuple".format(
  348. self))
  349. from functools import reduce
  350. if reduce(lambda x, y: x * y, self.std) == 0:
  351. raise ValueError('{}: std is invalid!'.format(self))
  352. def __call__(self, im, label=None):
  353. """
  354. Args:
  355. im (np.ndarray): The Image data.
  356. label (np.ndarray, optional): The label data. Default: None.
  357. Returns:
  358. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  359. """
  360. mean = np.array(self.mean)[np.newaxis, np.newaxis, :]
  361. std = np.array(self.std)[np.newaxis, np.newaxis, :]
  362. im = functional.normalize(im, mean, std)
  363. if label is None:
  364. return (im, )
  365. else:
  366. return (im, label)
  367. @manager.TRANSFORMS.add_component
  368. class Padding:
  369. """
  370. Add bottom-right padding to a raw image or annotation image.
  371. Args:
  372. target_size (list|tuple): The target size after padding.
  373. im_padding_value (list, optional): The padding value of raw image.
  374. Default: [127.5, 127.5, 127.5].
  375. label_padding_value (int, optional): The padding value of annotation image. Default: 255.
  376. Raises:
  377. TypeError: When target_size is neither list nor tuple.
  378. ValueError: When the length of target_size is not 2.
  379. """
  380. def __init__(self,
  381. target_size,
  382. im_padding_value=(127.5, 127.5, 127.5),
  383. label_padding_value=255):
  384. if isinstance(target_size, list) or isinstance(target_size, tuple):
  385. if len(target_size) != 2:
  386. raise ValueError(
  387. '`target_size` should include 2 elements, but it is {}'.
  388. format(target_size))
  389. else:
  390. raise TypeError(
  391. "Type of target_size is invalid. It should be list or tuple, now is {}"
  392. .format(type(target_size)))
  393. self.target_size = target_size
  394. self.im_padding_value = im_padding_value
  395. self.label_padding_value = label_padding_value
  396. def __call__(self, im, label=None):
  397. """
  398. Args:
  399. im (np.ndarray): The Image data.
  400. label (np.ndarray, optional): The label data. Default: None.
  401. Returns:
  402. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  403. """
  404. im_height, im_width = im.shape[0], im.shape[1]
  405. if isinstance(self.target_size, int):
  406. target_height = self.target_size
  407. target_width = self.target_size
  408. else:
  409. target_height = self.target_size[1]
  410. target_width = self.target_size[0]
  411. pad_height = target_height - im_height
  412. pad_width = target_width - im_width
  413. if pad_height < 0 or pad_width < 0:
  414. raise ValueError(
  415. 'The size of image should be less than `target_size`, but the size of image ({}, {}) is larger than `target_size` ({}, {})'
  416. .format(im_width, im_height, target_width, target_height))
  417. else:
  418. im = cv2.copyMakeBorder(
  419. im,
  420. 0,
  421. pad_height,
  422. 0,
  423. pad_width,
  424. cv2.BORDER_CONSTANT,
  425. value=self.im_padding_value)
  426. if label is not None:
  427. label = cv2.copyMakeBorder(
  428. label,
  429. 0,
  430. pad_height,
  431. 0,
  432. pad_width,
  433. cv2.BORDER_CONSTANT,
  434. value=self.label_padding_value)
  435. if label is None:
  436. return (im, )
  437. else:
  438. return (im, label)
  439. @manager.TRANSFORMS.add_component
  440. class PaddingByAspectRatio:
  441. """
  442. Args:
  443. aspect_ratio (int|float, optional): The aspect ratio = width / height. Default: 1.
  444. """
  445. def __init__(self,
  446. aspect_ratio=1,
  447. im_padding_value=(127.5, 127.5, 127.5),
  448. label_padding_value=255):
  449. self.aspect_ratio = aspect_ratio
  450. self.im_padding_value = im_padding_value
  451. self.label_padding_value = label_padding_value
  452. def __call__(self, im, label=None):
  453. """
  454. Args:
  455. im (np.ndarray): The Image data.
  456. label (np.ndarray, optional): The label data. Default: None.
  457. Returns:
  458. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  459. """
  460. img_height = im.shape[0]
  461. img_width = im.shape[1]
  462. ratio = img_width / img_height
  463. if ratio == self.aspect_ratio:
  464. if label is None:
  465. return (im, )
  466. else:
  467. return (im, label)
  468. elif ratio > self.aspect_ratio:
  469. img_height = int(img_width / self.aspect_ratio)
  470. else:
  471. img_width = int(img_height * self.aspect_ratio)
  472. padding = Padding(
  473. (img_width, img_height),
  474. im_padding_value=self.im_padding_value,
  475. label_padding_value=self.label_padding_value)
  476. return padding(im, label)
  477. @manager.TRANSFORMS.add_component
  478. class RandomPaddingCrop:
  479. """
  480. Crop a sub-image from a raw image and annotation image randomly. If the target cropping size
  481. is larger than original image, then the bottom-right padding will be added.
  482. Args:
  483. crop_size (tuple, optional): The target cropping size. Default: (512, 512).
  484. im_padding_value (list, optional): The padding value of raw image.
  485. Default: [127.5, 127.5, 127.5].
  486. label_padding_value (int, optional): The padding value of annotation image. Default: 255.
  487. Raises:
  488. TypeError: When crop_size is neither list nor tuple.
  489. ValueError: When the length of crop_size is not 2.
  490. """
  491. def __init__(self,
  492. crop_size=(512, 512),
  493. im_padding_value=(127.5, 127.5, 127.5),
  494. label_padding_value=255):
  495. if isinstance(crop_size, list) or isinstance(crop_size, tuple):
  496. if len(crop_size) != 2:
  497. raise ValueError(
  498. 'Type of `crop_size` is list or tuple. It should include 2 elements, but it is {}'
  499. .format(crop_size))
  500. else:
  501. raise TypeError(
  502. "The type of `crop_size` is invalid. It should be list or tuple, but it is {}"
  503. .format(type(crop_size)))
  504. self.crop_size = crop_size
  505. self.im_padding_value = im_padding_value
  506. self.label_padding_value = label_padding_value
  507. def __call__(self, im, label=None):
  508. """
  509. Args:
  510. im (np.ndarray): The Image data.
  511. label (np.ndarray, optional): The label data. Default: None.
  512. Returns:
  513. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  514. """
  515. if isinstance(self.crop_size, int):
  516. crop_width = self.crop_size
  517. crop_height = self.crop_size
  518. else:
  519. crop_width = self.crop_size[0]
  520. crop_height = self.crop_size[1]
  521. img_height = im.shape[0]
  522. img_width = im.shape[1]
  523. if img_height == crop_height and img_width == crop_width:
  524. if label is None:
  525. return (im, )
  526. else:
  527. return (im, label)
  528. else:
  529. pad_height = max(crop_height - img_height, 0)
  530. pad_width = max(crop_width - img_width, 0)
  531. if (pad_height > 0 or pad_width > 0):
  532. im = cv2.copyMakeBorder(
  533. im,
  534. 0,
  535. pad_height,
  536. 0,
  537. pad_width,
  538. cv2.BORDER_CONSTANT,
  539. value=self.im_padding_value)
  540. if label is not None:
  541. label = cv2.copyMakeBorder(
  542. label,
  543. 0,
  544. pad_height,
  545. 0,
  546. pad_width,
  547. cv2.BORDER_CONSTANT,
  548. value=self.label_padding_value)
  549. img_height = im.shape[0]
  550. img_width = im.shape[1]
  551. if crop_height > 0 and crop_width > 0:
  552. h_off = np.random.randint(img_height - crop_height + 1)
  553. w_off = np.random.randint(img_width - crop_width + 1)
  554. im = im[h_off:(crop_height + h_off), w_off:(w_off + crop_width
  555. ), :]
  556. if label is not None:
  557. label = label[h_off:(crop_height + h_off), w_off:(
  558. w_off + crop_width)]
  559. if label is None:
  560. return (im, )
  561. else:
  562. return (im, label)
  563. @manager.TRANSFORMS.add_component
  564. class RandomBlur:
  565. """
  566. Blurring an image by a Gaussian function with a certain probability.
  567. Args:
  568. prob (float, optional): A probability of blurring an image. Default: 0.1.
  569. """
  570. def __init__(self, prob=0.1):
  571. self.prob = prob
  572. def __call__(self, im, label=None):
  573. """
  574. Args:
  575. im (np.ndarray): The Image data.
  576. label (np.ndarray, optional): The label data. Default: None.
  577. Returns:
  578. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  579. """
  580. if self.prob <= 0:
  581. n = 0
  582. elif self.prob >= 1:
  583. n = 1
  584. else:
  585. n = int(1.0 / self.prob)
  586. if n > 0:
  587. if np.random.randint(0, n) == 0:
  588. radius = np.random.randint(3, 10)
  589. if radius % 2 != 1:
  590. radius = radius + 1
  591. if radius > 9:
  592. radius = 9
  593. im = cv2.GaussianBlur(im, (radius, radius), 0, 0)
  594. if label is None:
  595. return (im, )
  596. else:
  597. return (im, label)
  598. @manager.TRANSFORMS.add_component
  599. class RandomRotation:
  600. """
  601. Rotate an image randomly with padding.
  602. Args:
  603. max_rotation (float, optional): The maximum rotation degree. Default: 15.
  604. im_padding_value (list, optional): The padding value of raw image.
  605. Default: [127.5, 127.5, 127.5].
  606. label_padding_value (int, optional): The padding value of annotation image. Default: 255.
  607. """
  608. def __init__(self,
  609. max_rotation=15,
  610. im_padding_value=(127.5, 127.5, 127.5),
  611. label_padding_value=255):
  612. self.max_rotation = max_rotation
  613. self.im_padding_value = im_padding_value
  614. self.label_padding_value = label_padding_value
  615. def __call__(self, im, label=None):
  616. """
  617. Args:
  618. im (np.ndarray): The Image data.
  619. label (np.ndarray, optional): The label data. Default: None.
  620. Returns:
  621. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  622. """
  623. if self.max_rotation > 0:
  624. (h, w) = im.shape[:2]
  625. do_rotation = np.random.uniform(-self.max_rotation,
  626. self.max_rotation)
  627. pc = (w // 2, h // 2)
  628. r = cv2.getRotationMatrix2D(pc, do_rotation, 1.0)
  629. cos = np.abs(r[0, 0])
  630. sin = np.abs(r[0, 1])
  631. nw = int((h * sin) + (w * cos))
  632. nh = int((h * cos) + (w * sin))
  633. (cx, cy) = pc
  634. r[0, 2] += (nw / 2) - cx
  635. r[1, 2] += (nh / 2) - cy
  636. dsize = (nw, nh)
  637. im = cv2.warpAffine(
  638. im,
  639. r,
  640. dsize=dsize,
  641. flags=cv2.INTER_LINEAR,
  642. borderMode=cv2.BORDER_CONSTANT,
  643. borderValue=self.im_padding_value)
  644. if label is not None:
  645. label = cv2.warpAffine(
  646. label,
  647. r,
  648. dsize=dsize,
  649. flags=cv2.INTER_NEAREST,
  650. borderMode=cv2.BORDER_CONSTANT,
  651. borderValue=self.label_padding_value)
  652. if label is None:
  653. return (im, )
  654. else:
  655. return (im, label)
  656. @manager.TRANSFORMS.add_component
  657. class RandomScaleAspect:
  658. """
  659. Crop a sub-image from an original image with a range of area ratio and aspect and
  660. then scale the sub-image back to the size of the original image.
  661. Args:
  662. min_scale (float, optional): The minimum area ratio of cropped image to the original image. Default: 0.5.
  663. aspect_ratio (float, optional): The minimum aspect ratio. Default: 0.33.
  664. """
  665. def __init__(self, min_scale=0.5, aspect_ratio=0.33):
  666. self.min_scale = min_scale
  667. self.aspect_ratio = aspect_ratio
  668. def __call__(self, im, label=None):
  669. """
  670. Args:
  671. im (np.ndarray): The Image data.
  672. label (np.ndarray, optional): The label data. Default: None.
  673. Returns:
  674. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  675. """
  676. if self.min_scale != 0 and self.aspect_ratio != 0:
  677. img_height = im.shape[0]
  678. img_width = im.shape[1]
  679. for i in range(0, 10):
  680. area = img_height * img_width
  681. target_area = area * np.random.uniform(self.min_scale, 1.0)
  682. aspectRatio = np.random.uniform(self.aspect_ratio,
  683. 1.0 / self.aspect_ratio)
  684. dw = int(np.sqrt(target_area * 1.0 * aspectRatio))
  685. dh = int(np.sqrt(target_area * 1.0 / aspectRatio))
  686. if (np.random.randint(10) < 5):
  687. tmp = dw
  688. dw = dh
  689. dh = tmp
  690. if (dh < img_height and dw < img_width):
  691. h1 = np.random.randint(0, img_height - dh)
  692. w1 = np.random.randint(0, img_width - dw)
  693. im = im[h1:(h1 + dh), w1:(w1 + dw), :]
  694. im = cv2.resize(
  695. im, (img_width, img_height),
  696. interpolation=cv2.INTER_LINEAR)
  697. if label is not None:
  698. label = label[h1:(h1 + dh), w1:(w1 + dw)]
  699. label = cv2.resize(
  700. label, (img_width, img_height),
  701. interpolation=cv2.INTER_NEAREST)
  702. break
  703. if label is None:
  704. return (im, )
  705. else:
  706. return (im, label)
  707. @manager.TRANSFORMS.add_component
  708. class RandomDistort:
  709. """
  710. Distort an image with random configurations.
  711. Args:
  712. brightness_range (float, optional): A range of brightness. Default: 0.5.
  713. brightness_prob (float, optional): A probability of adjusting brightness. Default: 0.5.
  714. contrast_range (float, optional): A range of contrast. Default: 0.5.
  715. contrast_prob (float, optional): A probability of adjusting contrast. Default: 0.5.
  716. saturation_range (float, optional): A range of saturation. Default: 0.5.
  717. saturation_prob (float, optional): A probability of adjusting saturation. Default: 0.5.
  718. hue_range (int, optional): A range of hue. Default: 18.
  719. hue_prob (float, optional): A probability of adjusting hue. Default: 0.5.
  720. """
  721. def __init__(self,
  722. brightness_range=0.5,
  723. brightness_prob=0.5,
  724. contrast_range=0.5,
  725. contrast_prob=0.5,
  726. saturation_range=0.5,
  727. saturation_prob=0.5,
  728. hue_range=18,
  729. hue_prob=0.5):
  730. self.brightness_range = brightness_range
  731. self.brightness_prob = brightness_prob
  732. self.contrast_range = contrast_range
  733. self.contrast_prob = contrast_prob
  734. self.saturation_range = saturation_range
  735. self.saturation_prob = saturation_prob
  736. self.hue_range = hue_range
  737. self.hue_prob = hue_prob
  738. def __call__(self, im, label=None):
  739. """
  740. Args:
  741. im (np.ndarray): The Image data.
  742. label (np.ndarray, optional): The label data. Default: None.
  743. Returns:
  744. (tuple). When label is None, it returns (im, ), otherwise it returns (im, label).
  745. """
  746. brightness_lower = 1 - self.brightness_range
  747. brightness_upper = 1 + self.brightness_range
  748. contrast_lower = 1 - self.contrast_range
  749. contrast_upper = 1 + self.contrast_range
  750. saturation_lower = 1 - self.saturation_range
  751. saturation_upper = 1 + self.saturation_range
  752. hue_lower = -self.hue_range
  753. hue_upper = self.hue_range
  754. ops = [
  755. functional.brightness, functional.contrast, functional.saturation,
  756. functional.hue
  757. ]
  758. random.shuffle(ops)
  759. params_dict = {
  760. 'brightness': {
  761. 'brightness_lower': brightness_lower,
  762. 'brightness_upper': brightness_upper
  763. },
  764. 'contrast': {
  765. 'contrast_lower': contrast_lower,
  766. 'contrast_upper': contrast_upper
  767. },
  768. 'saturation': {
  769. 'saturation_lower': saturation_lower,
  770. 'saturation_upper': saturation_upper
  771. },
  772. 'hue': {
  773. 'hue_lower': hue_lower,
  774. 'hue_upper': hue_upper
  775. }
  776. }
  777. prob_dict = {
  778. 'brightness': self.brightness_prob,
  779. 'contrast': self.contrast_prob,
  780. 'saturation': self.saturation_prob,
  781. 'hue': self.hue_prob
  782. }
  783. im = im.astype('uint8')
  784. im = Image.fromarray(im)
  785. for id in range(len(ops)):
  786. params = params_dict[ops[id].__name__]
  787. prob = prob_dict[ops[id].__name__]
  788. params['im'] = im
  789. if np.random.uniform(0, 1) < prob:
  790. im = ops[id](**params)
  791. im = np.asarray(im).astype('float32')
  792. if label is None:
  793. return (im, )
  794. else:
  795. return (im, label)