seg_transforms.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. # coding: utf8
  2. # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from .ops import *
  16. import random
  17. import os.path as osp
  18. import numpy as np
  19. from PIL import Image
  20. import cv2
  21. from collections import OrderedDict
  22. class SegTransform:
  23. """ 分割transform基类
  24. """
  25. def __init__(self):
  26. pass
  27. class Compose(SegTransform):
  28. """根据数据预处理/增强算子对输入数据进行操作。
  29. 所有操作的输入图像流形状均是[H, W, C],其中H为图像高,W为图像宽,C为图像通道数。
  30. Args:
  31. transforms (list): 数据预处理/增强算子。
  32. Raises:
  33. TypeError: transforms不是list对象
  34. ValueError: transforms元素个数小于1。
  35. """
  36. def __init__(self, transforms):
  37. if not isinstance(transforms, list):
  38. raise TypeError('The transforms must be a list!')
  39. if len(transforms) < 1:
  40. raise ValueError('The length of transforms ' + \
  41. 'must be equal or larger than 1!')
  42. self.transforms = transforms
  43. self.to_rgb = False
  44. def __call__(self, im, im_info=None, label=None):
  45. """
  46. Args:
  47. im (str/np.ndarray): 图像路径/图像np.ndarray数据。
  48. im_info (list): 存储图像reisze或padding前的shape信息,如
  49. [('resize', [200, 300]), ('padding', [400, 600])]表示
  50. 图像在过resize前shape为(200, 300), 过padding前shape为
  51. (400, 600)
  52. label (str/np.ndarray): 标注图像路径/标注图像np.ndarray数据。
  53. Returns:
  54. tuple: 根据网络所需字段所组成的tuple;字段由transforms中的最后一个数据预处理操作决定。
  55. """
  56. if im_info is None:
  57. im_info = list()
  58. if isinstance(im, np.ndarray):
  59. if len(im.shape) != 3:
  60. raise Exception(
  61. "im should be 3-dimensions, but now is {}-dimensions".
  62. format(len(im.shape)))
  63. else:
  64. try:
  65. im = cv2.imread(im).astype('float32')
  66. except:
  67. raise ValueError('Can\'t read The image file {}!'.format(im))
  68. if self.to_rgb:
  69. im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
  70. if label is not None:
  71. if not isinstance(label, np.ndarray):
  72. label = np.asarray(Image.open(label))
  73. for op in self.transforms:
  74. if isinstance(op, SegTransform):
  75. outputs = op(im, im_info, label)
  76. im = outputs[0]
  77. if len(outputs) >= 2:
  78. im_info = outputs[1]
  79. if len(outputs) == 3:
  80. label = outputs[2]
  81. else:
  82. im = execute_imgaug(op, im)
  83. if label is not None:
  84. outputs = (im, im_info, label)
  85. else:
  86. outputs = (im, im_info)
  87. return outputs
  88. def add_augmenters(self, augmenters):
  89. if not isinstance(augmenters, list):
  90. raise Exception(
  91. "augmenters should be list type in func add_augmenters()")
  92. transform_names = [type(x).__name__ for x in self.transforms]
  93. for aug in augmenters:
  94. if type(aug).__name__ in transform_names:
  95. print(
  96. "{} is already in ComposedTransforms, need to remove it from add_augmenters().".
  97. format(type(aug).__name__))
  98. self.transforms = augmenters + self.transforms
  99. class RandomHorizontalFlip(SegTransform):
  100. """以一定的概率对图像进行水平翻转。当存在标注图像时,则同步进行翻转。
  101. Args:
  102. prob (float): 随机水平翻转的概率。默认值为0.5。
  103. """
  104. def __init__(self, prob=0.5):
  105. self.prob = prob
  106. def __call__(self, im, im_info=None, label=None):
  107. """
  108. Args:
  109. im (np.ndarray): 图像np.ndarray数据。
  110. im_info (list): 存储图像reisze或padding前的shape信息,如
  111. [('resize', [200, 300]), ('padding', [400, 600])]表示
  112. 图像在过resize前shape为(200, 300), 过padding前shape为
  113. (400, 600)
  114. label (np.ndarray): 标注图像np.ndarray数据。
  115. Returns:
  116. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  117. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  118. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  119. """
  120. if random.random() < self.prob:
  121. im = horizontal_flip(im)
  122. if label is not None:
  123. label = horizontal_flip(label)
  124. if label is None:
  125. return (im, im_info)
  126. else:
  127. return (im, im_info, label)
  128. class RandomVerticalFlip(SegTransform):
  129. """以一定的概率对图像进行垂直翻转。当存在标注图像时,则同步进行翻转。
  130. Args:
  131. prob (float): 随机垂直翻转的概率。默认值为0.1。
  132. """
  133. def __init__(self, prob=0.1):
  134. self.prob = prob
  135. def __call__(self, im, im_info=None, label=None):
  136. """
  137. Args:
  138. im (np.ndarray): 图像np.ndarray数据。
  139. im_info (list): 存储图像reisze或padding前的shape信息,如
  140. [('resize', [200, 300]), ('padding', [400, 600])]表示
  141. 图像在过resize前shape为(200, 300), 过padding前shape为
  142. (400, 600)
  143. label (np.ndarray): 标注图像np.ndarray数据。
  144. Returns:
  145. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  146. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  147. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  148. """
  149. if random.random() < self.prob:
  150. im = vertical_flip(im)
  151. if label is not None:
  152. label = vertical_flip(label)
  153. if label is None:
  154. return (im, im_info)
  155. else:
  156. return (im, im_info, label)
  157. class Resize(SegTransform):
  158. """调整图像大小(resize),当存在标注图像时,则同步进行处理。
  159. - 当目标大小(target_size)类型为int时,根据插值方式,
  160. 将图像resize为[target_size, target_size]。
  161. - 当目标大小(target_size)类型为list或tuple时,根据插值方式,
  162. 将图像resize为target_size, target_size的输入应为[w, h]或(w, h)。
  163. Args:
  164. target_size (int|list|tuple): 目标大小。
  165. interp (str): resize的插值方式,与opencv的插值方式对应,
  166. 可选的值为['NEAREST', 'LINEAR', 'CUBIC', 'AREA', 'LANCZOS4'],默认为"LINEAR"。
  167. Raises:
  168. TypeError: target_size不是int/list/tuple。
  169. ValueError: target_size为list/tuple时元素个数不等于2。
  170. AssertionError: interp的取值不在['NEAREST', 'LINEAR', 'CUBIC', 'AREA', 'LANCZOS4']之内。
  171. """
  172. # The interpolation mode
  173. interp_dict = {
  174. 'NEAREST': cv2.INTER_NEAREST,
  175. 'LINEAR': cv2.INTER_LINEAR,
  176. 'CUBIC': cv2.INTER_CUBIC,
  177. 'AREA': cv2.INTER_AREA,
  178. 'LANCZOS4': cv2.INTER_LANCZOS4
  179. }
  180. def __init__(self, target_size, interp='LINEAR'):
  181. self.interp = interp
  182. assert interp in self.interp_dict, "interp should be one of {}".format(
  183. interp_dict.keys())
  184. if isinstance(target_size, list) or isinstance(target_size, tuple):
  185. if len(target_size) != 2:
  186. raise ValueError(
  187. 'when target is list or tuple, it should include 2 elements, but it is {}'
  188. .format(target_size))
  189. elif not isinstance(target_size, int):
  190. raise TypeError(
  191. "Type of target_size is invalid. Must be Integer or List or tuple, now is {}"
  192. .format(type(target_size)))
  193. self.target_size = target_size
  194. def __call__(self, im, im_info=None, label=None):
  195. """
  196. Args:
  197. im (np.ndarray): 图像np.ndarray数据。
  198. im_info (list): 存储图像reisze或padding前的shape信息,如
  199. [('resize', [200, 300]), ('padding', [400, 600])]表示
  200. 图像在过resize前shape为(200, 300), 过padding前shape为
  201. (400, 600)
  202. label (np.ndarray): 标注图像np.ndarray数据。
  203. Returns:
  204. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  205. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  206. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  207. 其中,im_info跟新字段为:
  208. -shape_before_resize (tuple): 保存resize之前图像的形状(h, w)。
  209. Raises:
  210. ZeroDivisionError: im的短边为0。
  211. TypeError: im不是np.ndarray数据。
  212. ValueError: im不是3维nd.ndarray。
  213. """
  214. if im_info is None:
  215. im_info = OrderedDict()
  216. im_info.append(('resize', im.shape[:2]))
  217. if not isinstance(im, np.ndarray):
  218. raise TypeError("ResizeImage: image type is not np.ndarray.")
  219. if len(im.shape) != 3:
  220. raise ValueError('ResizeImage: image is not 3-dimensional.')
  221. im_shape = im.shape
  222. im_size_min = np.min(im_shape[0:2])
  223. im_size_max = np.max(im_shape[0:2])
  224. if float(im_size_min) == 0:
  225. raise ZeroDivisionError('ResizeImage: min size of image is 0')
  226. if isinstance(self.target_size, int):
  227. resize_w = self.target_size
  228. resize_h = self.target_size
  229. else:
  230. resize_w = self.target_size[0]
  231. resize_h = self.target_size[1]
  232. im_scale_x = float(resize_w) / float(im_shape[1])
  233. im_scale_y = float(resize_h) / float(im_shape[0])
  234. im = cv2.resize(
  235. im,
  236. None,
  237. None,
  238. fx=im_scale_x,
  239. fy=im_scale_y,
  240. interpolation=self.interp_dict[self.interp])
  241. if label is not None:
  242. label = cv2.resize(
  243. label,
  244. None,
  245. None,
  246. fx=im_scale_x,
  247. fy=im_scale_y,
  248. interpolation=self.interp_dict['NEAREST'])
  249. if label is None:
  250. return (im, im_info)
  251. else:
  252. return (im, im_info, label)
  253. class ResizeByLong(SegTransform):
  254. """对图像长边resize到固定值,短边按比例进行缩放。当存在标注图像时,则同步进行处理。
  255. Args:
  256. long_size (int): resize后图像的长边大小。
  257. """
  258. def __init__(self, long_size):
  259. self.long_size = long_size
  260. def __call__(self, im, im_info=None, label=None):
  261. """
  262. Args:
  263. im (np.ndarray): 图像np.ndarray数据。
  264. im_info (list): 存储图像reisze或padding前的shape信息,如
  265. [('resize', [200, 300]), ('padding', [400, 600])]表示
  266. 图像在过resize前shape为(200, 300), 过padding前shape为
  267. (400, 600)
  268. label (np.ndarray): 标注图像np.ndarray数据。
  269. Returns:
  270. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  271. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  272. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  273. 其中,im_info新增字段为:
  274. -shape_before_resize (tuple): 保存resize之前图像的形状(h, w)。
  275. """
  276. if im_info is None:
  277. im_info = OrderedDict()
  278. im_info.append(('resize', im.shape[:2]))
  279. im = resize_long(im, self.long_size)
  280. if label is not None:
  281. label = resize_long(label, self.long_size, cv2.INTER_NEAREST)
  282. if label is None:
  283. return (im, im_info)
  284. else:
  285. return (im, im_info, label)
  286. class ResizeByShort(SegTransform):
  287. """根据图像的短边调整图像大小(resize)。
  288. 1. 获取图像的长边和短边长度。
  289. 2. 根据短边与short_size的比例,计算长边的目标长度,
  290. 此时高、宽的resize比例为short_size/原图短边长度。
  291. 3. 如果max_size>0,调整resize比例:
  292. 如果长边的目标长度>max_size,则高、宽的resize比例为max_size/原图长边长度。
  293. 4. 根据调整大小的比例对图像进行resize。
  294. Args:
  295. target_size (int): 短边目标长度。默认为800。
  296. max_size (int): 长边目标长度的最大限制。默认为1333。
  297. Raises:
  298. TypeError: 形参数据类型不满足需求。
  299. """
  300. def __init__(self, short_size=800, max_size=1333):
  301. self.max_size = int(max_size)
  302. if not isinstance(short_size, int):
  303. raise TypeError(
  304. "Type of short_size is invalid. Must be Integer, now is {}".
  305. format(type(short_size)))
  306. self.short_size = short_size
  307. if not (isinstance(self.max_size, int)):
  308. raise TypeError("max_size: input type is invalid.")
  309. def __call__(self, im, im_info=None, label=None):
  310. """
  311. Args:
  312. im (numnp.ndarraypy): 图像np.ndarray数据。
  313. im_info (list): 存储图像reisze或padding前的shape信息,如
  314. [('resize', [200, 300]), ('padding', [400, 600])]表示
  315. 图像在过resize前shape为(200, 300), 过padding前shape为
  316. (400, 600)
  317. label (np.ndarray): 标注图像np.ndarray数据。
  318. Returns:
  319. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  320. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  321. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  322. 其中,im_info更新字段为:
  323. -shape_before_resize (tuple): 保存resize之前图像的形状(h, w)。
  324. Raises:
  325. TypeError: 形参数据类型不满足需求。
  326. ValueError: 数据长度不匹配。
  327. """
  328. if im_info is None:
  329. im_info = OrderedDict()
  330. if not isinstance(im, np.ndarray):
  331. raise TypeError("ResizeByShort: image type is not numpy.")
  332. if len(im.shape) != 3:
  333. raise ValueError('ResizeByShort: image is not 3-dimensional.')
  334. im_info.append(('resize', im.shape[:2]))
  335. im_short_size = min(im.shape[0], im.shape[1])
  336. im_long_size = max(im.shape[0], im.shape[1])
  337. scale = float(self.short_size) / im_short_size
  338. if self.max_size > 0 and np.round(scale *
  339. im_long_size) > self.max_size:
  340. scale = float(self.max_size) / float(im_long_size)
  341. resized_width = int(round(im.shape[1] * scale))
  342. resized_height = int(round(im.shape[0] * scale))
  343. im = cv2.resize(
  344. im, (resized_width, resized_height),
  345. interpolation=cv2.INTER_NEAREST)
  346. if label is not None:
  347. im = cv2.resize(
  348. label, (resized_width, resized_height),
  349. interpolation=cv2.INTER_NEAREST)
  350. if label is None:
  351. return (im, im_info)
  352. else:
  353. return (im, im_info, label)
  354. class ResizeRangeScaling(SegTransform):
  355. """对图像长边随机resize到指定范围内,短边按比例进行缩放。当存在标注图像时,则同步进行处理。
  356. Args:
  357. min_value (int): 图像长边resize后的最小值。默认值400。
  358. max_value (int): 图像长边resize后的最大值。默认值600。
  359. Raises:
  360. ValueError: min_value大于max_value
  361. """
  362. def __init__(self, min_value=400, max_value=600):
  363. if min_value > max_value:
  364. raise ValueError('min_value must be less than max_value, '
  365. 'but they are {} and {}.'.format(min_value,
  366. max_value))
  367. self.min_value = min_value
  368. self.max_value = max_value
  369. def __call__(self, im, im_info=None, label=None):
  370. """
  371. Args:
  372. im (np.ndarray): 图像np.ndarray数据。
  373. im_info (list): 存储图像reisze或padding前的shape信息,如
  374. [('resize', [200, 300]), ('padding', [400, 600])]表示
  375. 图像在过resize前shape为(200, 300), 过padding前shape为
  376. (400, 600)
  377. label (np.ndarray): 标注图像np.ndarray数据。
  378. Returns:
  379. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  380. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  381. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  382. """
  383. if self.min_value == self.max_value:
  384. random_size = self.max_value
  385. else:
  386. random_size = int(
  387. np.random.uniform(self.min_value, self.max_value) + 0.5)
  388. im = resize_long(im, random_size, cv2.INTER_LINEAR)
  389. if label is not None:
  390. label = resize_long(label, random_size, cv2.INTER_NEAREST)
  391. if label is None:
  392. return (im, im_info)
  393. else:
  394. return (im, im_info, label)
  395. class ResizeStepScaling(SegTransform):
  396. """对图像按照某一个比例resize,这个比例以scale_step_size为步长
  397. 在[min_scale_factor, max_scale_factor]随机变动。当存在标注图像时,则同步进行处理。
  398. Args:
  399. min_scale_factor(float), resize最小尺度。默认值0.75。
  400. max_scale_factor (float), resize最大尺度。默认值1.25。
  401. scale_step_size (float), resize尺度范围间隔。默认值0.25。
  402. Raises:
  403. ValueError: min_scale_factor大于max_scale_factor
  404. """
  405. def __init__(self,
  406. min_scale_factor=0.75,
  407. max_scale_factor=1.25,
  408. scale_step_size=0.25):
  409. if min_scale_factor > max_scale_factor:
  410. raise ValueError(
  411. 'min_scale_factor must be less than max_scale_factor, '
  412. 'but they are {} and {}.'.format(min_scale_factor,
  413. max_scale_factor))
  414. self.min_scale_factor = min_scale_factor
  415. self.max_scale_factor = max_scale_factor
  416. self.scale_step_size = scale_step_size
  417. def __call__(self, im, im_info=None, label=None):
  418. """
  419. Args:
  420. im (np.ndarray): 图像np.ndarray数据。
  421. im_info (list): 存储图像reisze或padding前的shape信息,如
  422. [('resize', [200, 300]), ('padding', [400, 600])]表示
  423. 图像在过resize前shape为(200, 300), 过padding前shape为
  424. (400, 600)
  425. label (np.ndarray): 标注图像np.ndarray数据。
  426. Returns:
  427. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  428. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  429. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  430. """
  431. if self.min_scale_factor == self.max_scale_factor:
  432. scale_factor = self.min_scale_factor
  433. elif self.scale_step_size == 0:
  434. scale_factor = np.random.uniform(self.min_scale_factor,
  435. self.max_scale_factor)
  436. else:
  437. num_steps = int((self.max_scale_factor - self.min_scale_factor) /
  438. self.scale_step_size + 1)
  439. scale_factors = np.linspace(self.min_scale_factor,
  440. self.max_scale_factor,
  441. num_steps).tolist()
  442. np.random.shuffle(scale_factors)
  443. scale_factor = scale_factors[0]
  444. im = cv2.resize(
  445. im, (0, 0),
  446. fx=scale_factor,
  447. fy=scale_factor,
  448. interpolation=cv2.INTER_LINEAR)
  449. if label is not None:
  450. label = cv2.resize(
  451. label, (0, 0),
  452. fx=scale_factor,
  453. fy=scale_factor,
  454. interpolation=cv2.INTER_NEAREST)
  455. if label is None:
  456. return (im, im_info)
  457. else:
  458. return (im, im_info, label)
  459. class Normalize(SegTransform):
  460. """对图像进行标准化。
  461. 1.尺度缩放到 [0,1]。
  462. 2.对图像进行减均值除以标准差操作。
  463. Args:
  464. mean (list): 图像数据集的均值。默认值[0.5, 0.5, 0.5]。
  465. std (list): 图像数据集的标准差。默认值[0.5, 0.5, 0.5]。
  466. Raises:
  467. ValueError: mean或std不是list对象。std包含0。
  468. """
  469. def __init__(self, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]):
  470. self.mean = mean
  471. self.std = std
  472. if not (isinstance(self.mean, list) and isinstance(self.std, list)):
  473. raise ValueError("{}: input type is invalid.".format(self))
  474. from functools import reduce
  475. if reduce(lambda x, y: x * y, self.std) == 0:
  476. raise ValueError('{}: std is invalid!'.format(self))
  477. def __call__(self, im, im_info=None, label=None):
  478. """
  479. Args:
  480. im (np.ndarray): 图像np.ndarray数据。
  481. im_info (list): 存储图像reisze或padding前的shape信息,如
  482. [('resize', [200, 300]), ('padding', [400, 600])]表示
  483. 图像在过resize前shape为(200, 300), 过padding前shape为
  484. (400, 600)
  485. label (np.ndarray): 标注图像np.ndarray数据。
  486. Returns:
  487. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  488. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  489. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  490. """
  491. mean = np.array(self.mean)[np.newaxis, np.newaxis, :]
  492. std = np.array(self.std)[np.newaxis, np.newaxis, :]
  493. im = normalize(im, mean, std)
  494. if label is None:
  495. return (im, im_info)
  496. else:
  497. return (im, im_info, label)
  498. class Padding(SegTransform):
  499. """对图像或标注图像进行padding,padding方向为右和下。
  500. 根据提供的值对图像或标注图像进行padding操作。
  501. Args:
  502. target_size (int|list|tuple): padding后图像的大小。
  503. im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
  504. label_padding_value (int): 标注图像padding的值。默认值为255。
  505. Raises:
  506. TypeError: target_size不是int|list|tuple。
  507. ValueError: target_size为list|tuple时元素个数不等于2。
  508. """
  509. def __init__(self,
  510. target_size,
  511. im_padding_value=[127.5, 127.5, 127.5],
  512. label_padding_value=255):
  513. if isinstance(target_size, list) or isinstance(target_size, tuple):
  514. if len(target_size) != 2:
  515. raise ValueError(
  516. 'when target is list or tuple, it should include 2 elements, but it is {}'
  517. .format(target_size))
  518. elif not isinstance(target_size, int):
  519. raise TypeError(
  520. "Type of target_size is invalid. Must be Integer or List or tuple, now is {}"
  521. .format(type(target_size)))
  522. self.target_size = target_size
  523. self.im_padding_value = im_padding_value
  524. self.label_padding_value = label_padding_value
  525. def __call__(self, im, im_info=None, label=None):
  526. """
  527. Args:
  528. im (np.ndarray): 图像np.ndarray数据。
  529. im_info (list): 存储图像reisze或padding前的shape信息,如
  530. [('resize', [200, 300]), ('padding', [400, 600])]表示
  531. 图像在过resize前shape为(200, 300), 过padding前shape为
  532. (400, 600)
  533. label (np.ndarray): 标注图像np.ndarray数据。
  534. Returns:
  535. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  536. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  537. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  538. 其中,im_info新增字段为:
  539. -shape_before_padding (tuple): 保存padding之前图像的形状(h, w)。
  540. Raises:
  541. ValueError: 输入图像im或label的形状大于目标值
  542. """
  543. if im_info is None:
  544. im_info = OrderedDict()
  545. im_info.append(('padding', im.shape[:2]))
  546. im_height, im_width = im.shape[0], im.shape[1]
  547. if isinstance(self.target_size, int):
  548. target_height = self.target_size
  549. target_width = self.target_size
  550. else:
  551. target_height = self.target_size[1]
  552. target_width = self.target_size[0]
  553. pad_height = target_height - im_height
  554. pad_width = target_width - im_width
  555. if pad_height < 0 or pad_width < 0:
  556. raise ValueError(
  557. 'the size of image should be less than target_size, but the size of image ({}, {}), is larger than target_size ({}, {})'
  558. .format(im_width, im_height, target_width, target_height))
  559. else:
  560. im = cv2.copyMakeBorder(
  561. im,
  562. 0,
  563. pad_height,
  564. 0,
  565. pad_width,
  566. cv2.BORDER_CONSTANT,
  567. value=self.im_padding_value)
  568. if label is not None:
  569. label = cv2.copyMakeBorder(
  570. label,
  571. 0,
  572. pad_height,
  573. 0,
  574. pad_width,
  575. cv2.BORDER_CONSTANT,
  576. value=self.label_padding_value)
  577. if label is None:
  578. return (im, im_info)
  579. else:
  580. return (im, im_info, label)
  581. class RandomPaddingCrop(SegTransform):
  582. """对图像和标注图进行随机裁剪,当所需要的裁剪尺寸大于原图时,则进行padding操作。
  583. Args:
  584. crop_size (int|list|tuple): 裁剪图像大小。默认为512。
  585. im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
  586. label_padding_value (int): 标注图像padding的值。默认值为255。
  587. Raises:
  588. TypeError: crop_size不是int/list/tuple。
  589. ValueError: target_size为list/tuple时元素个数不等于2。
  590. """
  591. def __init__(self,
  592. crop_size=512,
  593. im_padding_value=[127.5, 127.5, 127.5],
  594. label_padding_value=255):
  595. if isinstance(crop_size, list) or isinstance(crop_size, tuple):
  596. if len(crop_size) != 2:
  597. raise ValueError(
  598. 'when crop_size is list or tuple, it should include 2 elements, but it is {}'
  599. .format(crop_size))
  600. elif not isinstance(crop_size, int):
  601. raise TypeError(
  602. "Type of crop_size is invalid. Must be Integer or List or tuple, now is {}"
  603. .format(type(crop_size)))
  604. self.crop_size = crop_size
  605. self.im_padding_value = im_padding_value
  606. self.label_padding_value = label_padding_value
  607. def __call__(self, im, im_info=None, label=None):
  608. """
  609. Args:
  610. im (np.ndarray): 图像np.ndarray数据。
  611. im_info (list): 存储图像reisze或padding前的shape信息,如
  612. [('resize', [200, 300]), ('padding', [400, 600])]表示
  613. 图像在过resize前shape为(200, 300), 过padding前shape为
  614. (400, 600)
  615. label (np.ndarray): 标注图像np.ndarray数据。
  616. Returns:
  617. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  618. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  619. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  620. """
  621. if isinstance(self.crop_size, int):
  622. crop_width = self.crop_size
  623. crop_height = self.crop_size
  624. else:
  625. crop_width = self.crop_size[0]
  626. crop_height = self.crop_size[1]
  627. img_height = im.shape[0]
  628. img_width = im.shape[1]
  629. if img_height == crop_height and img_width == crop_width:
  630. if label is None:
  631. return (im, im_info)
  632. else:
  633. return (im, im_info, label)
  634. else:
  635. pad_height = max(crop_height - img_height, 0)
  636. pad_width = max(crop_width - img_width, 0)
  637. if (pad_height > 0 or pad_width > 0):
  638. im = cv2.copyMakeBorder(
  639. im,
  640. 0,
  641. pad_height,
  642. 0,
  643. pad_width,
  644. cv2.BORDER_CONSTANT,
  645. value=self.im_padding_value)
  646. if label is not None:
  647. label = cv2.copyMakeBorder(
  648. label,
  649. 0,
  650. pad_height,
  651. 0,
  652. pad_width,
  653. cv2.BORDER_CONSTANT,
  654. value=self.label_padding_value)
  655. img_height = im.shape[0]
  656. img_width = im.shape[1]
  657. if crop_height > 0 and crop_width > 0:
  658. h_off = np.random.randint(img_height - crop_height + 1)
  659. w_off = np.random.randint(img_width - crop_width + 1)
  660. im = im[h_off:(crop_height + h_off), w_off:(w_off + crop_width
  661. ), :]
  662. if label is not None:
  663. label = label[h_off:(crop_height + h_off), w_off:(
  664. w_off + crop_width)]
  665. if label is None:
  666. return (im, im_info)
  667. else:
  668. return (im, im_info, label)
  669. class RandomBlur(SegTransform):
  670. """以一定的概率对图像进行高斯模糊。
  671. Args:
  672. prob (float): 图像模糊概率。默认为0.1。
  673. """
  674. def __init__(self, prob=0.1):
  675. self.prob = prob
  676. def __call__(self, im, im_info=None, label=None):
  677. """
  678. Args:
  679. im (np.ndarray): 图像np.ndarray数据。
  680. im_info (list): 存储图像reisze或padding前的shape信息,如
  681. [('resize', [200, 300]), ('padding', [400, 600])]表示
  682. 图像在过resize前shape为(200, 300), 过padding前shape为
  683. (400, 600)
  684. label (np.ndarray): 标注图像np.ndarray数据。
  685. Returns:
  686. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  687. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  688. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  689. """
  690. if self.prob <= 0:
  691. n = 0
  692. elif self.prob >= 1:
  693. n = 1
  694. else:
  695. n = int(1.0 / self.prob)
  696. if n > 0:
  697. if np.random.randint(0, n) == 0:
  698. radius = np.random.randint(3, 10)
  699. if radius % 2 != 1:
  700. radius = radius + 1
  701. if radius > 9:
  702. radius = 9
  703. im = cv2.GaussianBlur(im, (radius, radius), 0, 0)
  704. if label is None:
  705. return (im, im_info)
  706. else:
  707. return (im, im_info, label)
  708. class RandomScaleAspect(SegTransform):
  709. """裁剪并resize回原始尺寸的图像和标注图像。
  710. 按照一定的面积比和宽高比对图像进行裁剪,并reszie回原始图像的图像,当存在标注图时,同步进行。
  711. Args:
  712. min_scale (float):裁取图像占原始图像的面积比,取值[0,1],为0时则返回原图。默认为0.5。
  713. aspect_ratio (float): 裁取图像的宽高比范围,非负值,为0时返回原图。默认为0.33。
  714. """
  715. def __init__(self, min_scale=0.5, aspect_ratio=0.33):
  716. self.min_scale = min_scale
  717. self.aspect_ratio = aspect_ratio
  718. def __call__(self, im, im_info=None, label=None):
  719. """
  720. Args:
  721. im (np.ndarray): 图像np.ndarray数据。
  722. im_info (list): 存储图像reisze或padding前的shape信息,如
  723. [('resize', [200, 300]), ('padding', [400, 600])]表示
  724. 图像在过resize前shape为(200, 300), 过padding前shape为
  725. (400, 600)
  726. label (np.ndarray): 标注图像np.ndarray数据。
  727. Returns:
  728. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  729. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  730. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  731. """
  732. if self.min_scale != 0 and self.aspect_ratio != 0:
  733. img_height = im.shape[0]
  734. img_width = im.shape[1]
  735. for i in range(0, 10):
  736. area = img_height * img_width
  737. target_area = area * np.random.uniform(self.min_scale, 1.0)
  738. aspectRatio = np.random.uniform(self.aspect_ratio,
  739. 1.0 / self.aspect_ratio)
  740. dw = int(np.sqrt(target_area * 1.0 * aspectRatio))
  741. dh = int(np.sqrt(target_area * 1.0 / aspectRatio))
  742. if (np.random.randint(10) < 5):
  743. tmp = dw
  744. dw = dh
  745. dh = tmp
  746. if (dh < img_height and dw < img_width):
  747. h1 = np.random.randint(0, img_height - dh)
  748. w1 = np.random.randint(0, img_width - dw)
  749. im = im[h1:(h1 + dh), w1:(w1 + dw), :]
  750. label = label[h1:(h1 + dh), w1:(w1 + dw)]
  751. im = cv2.resize(
  752. im, (img_width, img_height),
  753. interpolation=cv2.INTER_LINEAR)
  754. label = cv2.resize(
  755. label, (img_width, img_height),
  756. interpolation=cv2.INTER_NEAREST)
  757. break
  758. if label is None:
  759. return (im, im_info)
  760. else:
  761. return (im, im_info, label)
  762. class RandomDistort(SegTransform):
  763. """对图像进行随机失真。
  764. 1. 对变换的操作顺序进行随机化操作。
  765. 2. 按照1中的顺序以一定的概率对图像进行随机像素内容变换。
  766. Args:
  767. brightness_range (float): 明亮度因子的范围。默认为0.5。
  768. brightness_prob (float): 随机调整明亮度的概率。默认为0.5。
  769. contrast_range (float): 对比度因子的范围。默认为0.5。
  770. contrast_prob (float): 随机调整对比度的概率。默认为0.5。
  771. saturation_range (float): 饱和度因子的范围。默认为0.5。
  772. saturation_prob (float): 随机调整饱和度的概率。默认为0.5。
  773. hue_range (int): 色调因子的范围。默认为18。
  774. hue_prob (float): 随机调整色调的概率。默认为0.5。
  775. """
  776. def __init__(self,
  777. brightness_range=0.5,
  778. brightness_prob=0.5,
  779. contrast_range=0.5,
  780. contrast_prob=0.5,
  781. saturation_range=0.5,
  782. saturation_prob=0.5,
  783. hue_range=18,
  784. hue_prob=0.5):
  785. self.brightness_range = brightness_range
  786. self.brightness_prob = brightness_prob
  787. self.contrast_range = contrast_range
  788. self.contrast_prob = contrast_prob
  789. self.saturation_range = saturation_range
  790. self.saturation_prob = saturation_prob
  791. self.hue_range = hue_range
  792. self.hue_prob = hue_prob
  793. def __call__(self, im, im_info=None, label=None):
  794. """
  795. Args:
  796. im (np.ndarray): 图像np.ndarray数据。
  797. im_info (list): 存储图像reisze或padding前的shape信息,如
  798. [('resize', [200, 300]), ('padding', [400, 600])]表示
  799. 图像在过resize前shape为(200, 300), 过padding前shape为
  800. (400, 600)
  801. label (np.ndarray): 标注图像np.ndarray数据。
  802. Returns:
  803. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  804. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  805. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  806. """
  807. brightness_lower = 1 - self.brightness_range
  808. brightness_upper = 1 + self.brightness_range
  809. contrast_lower = 1 - self.contrast_range
  810. contrast_upper = 1 + self.contrast_range
  811. saturation_lower = 1 - self.saturation_range
  812. saturation_upper = 1 + self.saturation_range
  813. hue_lower = -self.hue_range
  814. hue_upper = self.hue_range
  815. ops = [brightness, contrast, saturation, hue]
  816. random.shuffle(ops)
  817. params_dict = {
  818. 'brightness': {
  819. 'brightness_lower': brightness_lower,
  820. 'brightness_upper': brightness_upper
  821. },
  822. 'contrast': {
  823. 'contrast_lower': contrast_lower,
  824. 'contrast_upper': contrast_upper
  825. },
  826. 'saturation': {
  827. 'saturation_lower': saturation_lower,
  828. 'saturation_upper': saturation_upper
  829. },
  830. 'hue': {
  831. 'hue_lower': hue_lower,
  832. 'hue_upper': hue_upper
  833. }
  834. }
  835. prob_dict = {
  836. 'brightness': self.brightness_prob,
  837. 'contrast': self.contrast_prob,
  838. 'saturation': self.saturation_prob,
  839. 'hue': self.hue_prob
  840. }
  841. for id in range(4):
  842. params = params_dict[ops[id].__name__]
  843. prob = prob_dict[ops[id].__name__]
  844. params['im'] = im
  845. if np.random.uniform(0, 1) < prob:
  846. im = ops[id](**params)
  847. if label is None:
  848. return (im, im_info)
  849. else:
  850. return (im, im_info, label)
  851. class ArrangeSegmenter(SegTransform):
  852. """获取训练/验证/预测所需的信息。
  853. Args:
  854. mode (str): 指定数据用于何种用途,取值范围为['train', 'eval', 'test', 'quant']。
  855. Raises:
  856. ValueError: mode的取值不在['train', 'eval', 'test', 'quant']之内
  857. """
  858. def __init__(self, mode):
  859. if mode not in ['train', 'eval', 'test', 'quant']:
  860. raise ValueError(
  861. "mode should be defined as one of ['train', 'eval', 'test', 'quant']!"
  862. )
  863. self.mode = mode
  864. def __call__(self, im, im_info, label=None):
  865. """
  866. Args:
  867. im (np.ndarray): 图像np.ndarray数据。
  868. im_info (list): 存储图像reisze或padding前的shape信息,如
  869. [('resize', [200, 300]), ('padding', [400, 600])]表示
  870. 图像在过resize前shape为(200, 300), 过padding前shape为
  871. (400, 600)
  872. label (np.ndarray): 标注图像np.ndarray数据。
  873. Returns:
  874. tuple: 当mode为'train'或'eval'时,返回的tuple为(im, label),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  875. 当mode为'test'时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;当mode为
  876. 'quant'时,返回的tuple为(im,),为图像np.ndarray数据。
  877. """
  878. im = permute(im, False)
  879. if self.mode == 'train' or self.mode == 'eval':
  880. label = label[np.newaxis, :, :]
  881. return (im, label)
  882. elif self.mode == 'test':
  883. return (im, im_info)
  884. else:
  885. return (im, )
  886. class ComposedSegTransforms(Compose):
  887. """ 语义分割模型(UNet/DeepLabv3p)的图像处理流程,具体如下
  888. 训练阶段:
  889. 1. 随机对图像以0.5的概率水平翻转
  890. 2. 按不同的比例随机Resize原图
  891. 3. 从原图中随机crop出大小为train_crop_size大小的子图,如若crop出来的图小于train_crop_size,则会将图padding到对应大小
  892. 4. 图像归一化
  893. 预测阶段:
  894. 1. 图像归一化
  895. Args:
  896. mode(str): 图像处理所处阶段,训练/验证/预测,分别对应'train', 'eval', 'test'
  897. train_crop_size(list): 模型训练阶段,随机从原图crop的大小
  898. mean(list): 图像均值
  899. std(list): 图像方差
  900. """
  901. def __init__(self,
  902. mode,
  903. train_crop_size=[769, 769],
  904. mean=[0.5, 0.5, 0.5],
  905. std=[0.5, 0.5, 0.5]):
  906. if mode == 'train':
  907. # 训练时的transforms,包含数据增强
  908. pass
  909. else:
  910. # 验证/预测时的transforms
  911. transforms = [Normalize(mean=mean, std=std)]
  912. super(ComposedSegTransforms, self).__init__(transforms)