seg_transforms.py 40 KB

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