seg_transforms.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. # coding: utf8
  2. # copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
  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. from .imgaug_support import execute_imgaug
  17. import random
  18. import os.path as osp
  19. import numpy as np
  20. from PIL import Image
  21. import cv2
  22. from collections import OrderedDict
  23. class SegTransform:
  24. """ 分割transform基类
  25. """
  26. def __init__(self):
  27. pass
  28. class Compose(SegTransform):
  29. """根据数据预处理/增强算子对输入数据进行操作。
  30. 所有操作的输入图像流形状均是[H, W, C],其中H为图像高,W为图像宽,C为图像通道数。
  31. Args:
  32. transforms (list): 数据预处理/增强算子。
  33. Raises:
  34. TypeError: transforms不是list对象
  35. ValueError: transforms元素个数小于1。
  36. """
  37. def __init__(self, transforms):
  38. if not isinstance(transforms, list):
  39. raise TypeError('The transforms must be a list!')
  40. if len(transforms) < 1:
  41. raise ValueError('The length of transforms ' + \
  42. 'must be equal or larger than 1!')
  43. self.transforms = transforms
  44. self.to_rgb = False
  45. # 检查transforms里面的操作,目前支持PaddleX定义的或者是imgaug操作
  46. for op in self.transforms:
  47. if not isinstance(op, SegTransform):
  48. import imgaug.augmenters as iaa
  49. if not isinstance(op, iaa.Augmenter):
  50. raise Exception(
  51. "Elements in transforms should be defined in 'paddlex.seg.transforms' or class of imgaug.augmenters.Augmenter, see docs here: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/"
  52. )
  53. def __call__(self, im, im_info=None, label=None):
  54. """
  55. Args:
  56. im (str/np.ndarray): 图像路径/图像np.ndarray数据。
  57. im_info (list): 存储图像reisze或padding前的shape信息,如
  58. [('resize', [200, 300]), ('padding', [400, 600])]表示
  59. 图像在过resize前shape为(200, 300), 过padding前shape为
  60. (400, 600)
  61. label (str/np.ndarray): 标注图像路径/标注图像np.ndarray数据。
  62. Returns:
  63. tuple: 根据网络所需字段所组成的tuple;字段由transforms中的最后一个数据预处理操作决定。
  64. """
  65. if im_info is None:
  66. im_info = list()
  67. if isinstance(im, np.ndarray):
  68. if len(im.shape) != 3:
  69. raise Exception(
  70. "im should be 3-dimensions, but now is {}-dimensions".
  71. format(len(im.shape)))
  72. else:
  73. try:
  74. im = cv2.imread(im).astype('float32')
  75. except:
  76. raise ValueError('Can\'t read The image file {}!'.format(im))
  77. if self.to_rgb:
  78. im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
  79. if label is not None:
  80. if not isinstance(label, np.ndarray):
  81. label = np.asarray(Image.open(label))
  82. for op in self.transforms:
  83. if isinstance(op, SegTransform):
  84. outputs = op(im, im_info, label)
  85. im = outputs[0]
  86. if len(outputs) >= 2:
  87. im_info = outputs[1]
  88. if len(outputs) == 3:
  89. label = outputs[2]
  90. else:
  91. im = execute_imgaug(op, im)
  92. if label is not None:
  93. outputs = (im, im_info, label)
  94. else:
  95. outputs = (im, im_info)
  96. return outputs
  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 RandomRotate(SegTransform):
  707. """对图像进行随机旋转, 模型训练时的数据增强操作。
  708. 在旋转区间[-rotate_range, rotate_range]内,对图像进行随机旋转,当存在标注图像时,同步进行,
  709. 并对旋转后的图像和标注图像进行相应的padding。
  710. Args:
  711. rotate_range (float): 最大旋转角度。默认为15度。
  712. im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
  713. label_padding_value (int): 标注图像padding的值。默认为255。
  714. """
  715. def __init__(self,
  716. rotate_range=15,
  717. im_padding_value=[127.5, 127.5, 127.5],
  718. label_padding_value=255):
  719. self.rotate_range = rotate_range
  720. self.im_padding_value = im_padding_value
  721. self.label_padding_value = label_padding_value
  722. def __call__(self, im, im_info=None, label=None):
  723. """
  724. Args:
  725. im (np.ndarray): 图像np.ndarray数据。
  726. im_info (list): 存储图像reisze或padding前的shape信息,如
  727. [('resize', [200, 300]), ('padding', [400, 600])]表示
  728. 图像在过resize前shape为(200, 300), 过padding前shape为
  729. (400, 600)
  730. label (np.ndarray): 标注图像np.ndarray数据。
  731. Returns:
  732. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  733. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  734. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  735. """
  736. if self.rotate_range > 0:
  737. (h, w) = im.shape[:2]
  738. do_rotation = np.random.uniform(-self.rotate_range,
  739. self.rotate_range)
  740. pc = (w // 2, h // 2)
  741. r = cv2.getRotationMatrix2D(pc, do_rotation, 1.0)
  742. cos = np.abs(r[0, 0])
  743. sin = np.abs(r[0, 1])
  744. nw = int((h * sin) + (w * cos))
  745. nh = int((h * cos) + (w * sin))
  746. (cx, cy) = pc
  747. r[0, 2] += (nw / 2) - cx
  748. r[1, 2] += (nh / 2) - cy
  749. dsize = (nw, nh)
  750. im = cv2.warpAffine(
  751. im,
  752. r,
  753. dsize=dsize,
  754. flags=cv2.INTER_LINEAR,
  755. borderMode=cv2.BORDER_CONSTANT,
  756. borderValue=self.im_padding_value)
  757. label = cv2.warpAffine(
  758. label,
  759. r,
  760. dsize=dsize,
  761. flags=cv2.INTER_NEAREST,
  762. borderMode=cv2.BORDER_CONSTANT,
  763. borderValue=self.label_padding_value)
  764. if label is None:
  765. return (im, im_info)
  766. else:
  767. return (im, im_info, label)
  768. class RandomScaleAspect(SegTransform):
  769. """裁剪并resize回原始尺寸的图像和标注图像。
  770. 按照一定的面积比和宽高比对图像进行裁剪,并reszie回原始图像的图像,当存在标注图时,同步进行。
  771. Args:
  772. min_scale (float):裁取图像占原始图像的面积比,取值[0,1],为0时则返回原图。默认为0.5。
  773. aspect_ratio (float): 裁取图像的宽高比范围,非负值,为0时返回原图。默认为0.33。
  774. """
  775. def __init__(self, min_scale=0.5, aspect_ratio=0.33):
  776. self.min_scale = min_scale
  777. self.aspect_ratio = aspect_ratio
  778. def __call__(self, im, im_info=None, label=None):
  779. """
  780. Args:
  781. im (np.ndarray): 图像np.ndarray数据。
  782. im_info (list): 存储图像reisze或padding前的shape信息,如
  783. [('resize', [200, 300]), ('padding', [400, 600])]表示
  784. 图像在过resize前shape为(200, 300), 过padding前shape为
  785. (400, 600)
  786. label (np.ndarray): 标注图像np.ndarray数据。
  787. Returns:
  788. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  789. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  790. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  791. """
  792. if self.min_scale != 0 and self.aspect_ratio != 0:
  793. img_height = im.shape[0]
  794. img_width = im.shape[1]
  795. for i in range(0, 10):
  796. area = img_height * img_width
  797. target_area = area * np.random.uniform(self.min_scale, 1.0)
  798. aspectRatio = np.random.uniform(self.aspect_ratio,
  799. 1.0 / self.aspect_ratio)
  800. dw = int(np.sqrt(target_area * 1.0 * aspectRatio))
  801. dh = int(np.sqrt(target_area * 1.0 / aspectRatio))
  802. if (np.random.randint(10) < 5):
  803. tmp = dw
  804. dw = dh
  805. dh = tmp
  806. if (dh < img_height and dw < img_width):
  807. h1 = np.random.randint(0, img_height - dh)
  808. w1 = np.random.randint(0, img_width - dw)
  809. im = im[h1:(h1 + dh), w1:(w1 + dw), :]
  810. label = label[h1:(h1 + dh), w1:(w1 + dw)]
  811. im = cv2.resize(
  812. im, (img_width, img_height),
  813. interpolation=cv2.INTER_LINEAR)
  814. label = cv2.resize(
  815. label, (img_width, img_height),
  816. interpolation=cv2.INTER_NEAREST)
  817. break
  818. if label is None:
  819. return (im, im_info)
  820. else:
  821. return (im, im_info, label)
  822. class RandomDistort(SegTransform):
  823. """对图像进行随机失真。
  824. 1. 对变换的操作顺序进行随机化操作。
  825. 2. 按照1中的顺序以一定的概率对图像进行随机像素内容变换。
  826. Args:
  827. brightness_range (float): 明亮度因子的范围。默认为0.5。
  828. brightness_prob (float): 随机调整明亮度的概率。默认为0.5。
  829. contrast_range (float): 对比度因子的范围。默认为0.5。
  830. contrast_prob (float): 随机调整对比度的概率。默认为0.5。
  831. saturation_range (float): 饱和度因子的范围。默认为0.5。
  832. saturation_prob (float): 随机调整饱和度的概率。默认为0.5。
  833. hue_range (int): 色调因子的范围。默认为18。
  834. hue_prob (float): 随机调整色调的概率。默认为0.5。
  835. """
  836. def __init__(self,
  837. brightness_range=0.5,
  838. brightness_prob=0.5,
  839. contrast_range=0.5,
  840. contrast_prob=0.5,
  841. saturation_range=0.5,
  842. saturation_prob=0.5,
  843. hue_range=18,
  844. hue_prob=0.5):
  845. self.brightness_range = brightness_range
  846. self.brightness_prob = brightness_prob
  847. self.contrast_range = contrast_range
  848. self.contrast_prob = contrast_prob
  849. self.saturation_range = saturation_range
  850. self.saturation_prob = saturation_prob
  851. self.hue_range = hue_range
  852. self.hue_prob = hue_prob
  853. def __call__(self, im, im_info=None, label=None):
  854. """
  855. Args:
  856. im (np.ndarray): 图像np.ndarray数据。
  857. im_info (list): 存储图像reisze或padding前的shape信息,如
  858. [('resize', [200, 300]), ('padding', [400, 600])]表示
  859. 图像在过resize前shape为(200, 300), 过padding前shape为
  860. (400, 600)
  861. label (np.ndarray): 标注图像np.ndarray数据。
  862. Returns:
  863. tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  864. 当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
  865. 存储与图像相关信息的字典和标注图像np.ndarray数据。
  866. """
  867. brightness_lower = 1 - self.brightness_range
  868. brightness_upper = 1 + self.brightness_range
  869. contrast_lower = 1 - self.contrast_range
  870. contrast_upper = 1 + self.contrast_range
  871. saturation_lower = 1 - self.saturation_range
  872. saturation_upper = 1 + self.saturation_range
  873. hue_lower = -self.hue_range
  874. hue_upper = self.hue_range
  875. ops = [brightness, contrast, saturation, hue]
  876. random.shuffle(ops)
  877. params_dict = {
  878. 'brightness': {
  879. 'brightness_lower': brightness_lower,
  880. 'brightness_upper': brightness_upper
  881. },
  882. 'contrast': {
  883. 'contrast_lower': contrast_lower,
  884. 'contrast_upper': contrast_upper
  885. },
  886. 'saturation': {
  887. 'saturation_lower': saturation_lower,
  888. 'saturation_upper': saturation_upper
  889. },
  890. 'hue': {
  891. 'hue_lower': hue_lower,
  892. 'hue_upper': hue_upper
  893. }
  894. }
  895. prob_dict = {
  896. 'brightness': self.brightness_prob,
  897. 'contrast': self.contrast_prob,
  898. 'saturation': self.saturation_prob,
  899. 'hue': self.hue_prob
  900. }
  901. for id in range(4):
  902. params = params_dict[ops[id].__name__]
  903. prob = prob_dict[ops[id].__name__]
  904. params['im'] = im
  905. if np.random.uniform(0, 1) < prob:
  906. im = ops[id](**params)
  907. if label is None:
  908. return (im, im_info)
  909. else:
  910. return (im, im_info, label)
  911. class ArrangeSegmenter(SegTransform):
  912. """获取训练/验证/预测所需的信息。
  913. Args:
  914. mode (str): 指定数据用于何种用途,取值范围为['train', 'eval', 'test', 'quant']。
  915. Raises:
  916. ValueError: mode的取值不在['train', 'eval', 'test', 'quant']之内
  917. """
  918. def __init__(self, mode):
  919. if mode not in ['train', 'eval', 'test', 'quant']:
  920. raise ValueError(
  921. "mode should be defined as one of ['train', 'eval', 'test', 'quant']!"
  922. )
  923. self.mode = mode
  924. def __call__(self, im, im_info, label=None):
  925. """
  926. Args:
  927. im (np.ndarray): 图像np.ndarray数据。
  928. im_info (list): 存储图像reisze或padding前的shape信息,如
  929. [('resize', [200, 300]), ('padding', [400, 600])]表示
  930. 图像在过resize前shape为(200, 300), 过padding前shape为
  931. (400, 600)
  932. label (np.ndarray): 标注图像np.ndarray数据。
  933. Returns:
  934. tuple: 当mode为'train'或'eval'时,返回的tuple为(im, label),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
  935. 当mode为'test'时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;当mode为
  936. 'quant'时,返回的tuple为(im,),为图像np.ndarray数据。
  937. """
  938. im = permute(im, False)
  939. if self.mode == 'train' or self.mode == 'eval':
  940. label = label[np.newaxis, :, :]
  941. return (im, label)
  942. elif self.mode == 'test':
  943. return (im, im_info)
  944. else:
  945. return (im, )