seg_transforms.py 40 KB

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