seg_transforms.py 44 KB

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