seg_transforms.py 46 KB

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