seg_transforms.py 42 KB

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