image_common.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. # copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import math
  15. import numpy as np
  16. import cv2
  17. from ..transform import BaseTransform
  18. from ..io.readers import ImageReader
  19. from . import image_functions as F
  20. __all__ = [
  21. 'ReadImage', 'Flip', 'Crop', 'Resize', 'ResizeByLong', 'ResizeByShort',
  22. 'Pad', 'Normalize', 'ToCHWImage'
  23. ]
  24. def _check_image_size(input_):
  25. """ check image size """
  26. if not (isinstance(input_, (list, tuple)) and len(input_) == 2 and
  27. isinstance(input_[0], int) and isinstance(input_[1], int)):
  28. raise TypeError(f"{input_} cannot represent a valid image size.")
  29. class ReadImage(BaseTransform):
  30. """Load image from the file."""
  31. _FLAGS_DICT = {
  32. 'BGR': cv2.IMREAD_COLOR,
  33. 'RGB': cv2.IMREAD_COLOR,
  34. 'GRAY': cv2.IMREAD_GRAYSCALE
  35. }
  36. def __init__(self, format='BGR'):
  37. """
  38. Initialize the instance.
  39. Args:
  40. format (str, optional): Target color format to convert the image to.
  41. Choices are 'BGR', 'RGB', and 'GRAY'. Default: 'BGR'.
  42. """
  43. super().__init__()
  44. self.format = format
  45. flags = self._FLAGS_DICT[self.format]
  46. self._reader = ImageReader(backend='opencv', flags=flags)
  47. def apply(self, data):
  48. """ apply """
  49. im_path = data['input_path']
  50. blob = self._reader.read(im_path)
  51. if self.format == 'RGB':
  52. if blob.ndim != 3:
  53. raise RuntimeError("Array is not 3-dimensional.")
  54. # BGR to RGB
  55. blob = blob[..., ::-1]
  56. data['image'] = blob
  57. data['original_image'] = blob
  58. data['original_image_size'] = [blob.shape[1], blob.shape[0]]
  59. return data
  60. @classmethod
  61. def get_input_keys(cls):
  62. """ get input keys """
  63. # input_path: Path of the image.
  64. return ['input_path']
  65. @classmethod
  66. def get_output_keys(cls):
  67. """ get output keys """
  68. # image: Image in hw or hwc format.
  69. # original_image: Original image in hw or hwc format.
  70. # original_image_size: Width and height of the original image.
  71. return ['image', 'original_image', 'original_image_size']
  72. class GetImageInfo(BaseTransform):
  73. """Get Image Info
  74. """
  75. def __init__(self):
  76. super().__init__()
  77. def apply(self, data):
  78. """ apply """
  79. blob = data['image']
  80. data['original_image'] = blob
  81. data['original_image_size'] = [blob.shape[1], blob.shape[0]]
  82. return data
  83. @classmethod
  84. def get_input_keys(cls):
  85. """ get input keys """
  86. # input_path: Path of the image.
  87. return ['image']
  88. @classmethod
  89. def get_output_keys(cls):
  90. """ get output keys """
  91. # image: Image in hw or hwc format.
  92. # original_image: Original image in hw or hwc format.
  93. # original_image_size: Width and height of the original image.
  94. return ['original_image', 'original_image_size']
  95. class Flip(BaseTransform):
  96. """Flip the image vertically or horizontally."""
  97. def __init__(self, mode='H'):
  98. """
  99. Initialize the instance.
  100. Args:
  101. mode (str, optional): 'H' for horizontal flipping and 'V' for vertical
  102. flipping. Default: 'H'.
  103. """
  104. super().__init__()
  105. if mode not in ('H', 'V'):
  106. raise ValueError("`mode` should be 'H' or 'V'.")
  107. self.mode = mode
  108. def apply(self, data):
  109. """ apply """
  110. im = data['image']
  111. if self.mode == 'H':
  112. im = F.flip_h(im)
  113. elif self.mode == 'V':
  114. im = F.flip_v(im)
  115. data['image'] = im
  116. return data
  117. @classmethod
  118. def get_input_keys(cls):
  119. """ get input keys """
  120. # image: Image in hw or hwc format.
  121. return ['image']
  122. @classmethod
  123. def get_output_keys(cls):
  124. """ get output keys """
  125. # image: Image in hw or hwc format.
  126. return ['image']
  127. class Crop(BaseTransform):
  128. """Crop region from the image."""
  129. def __init__(self, crop_size, mode='C'):
  130. """
  131. Initialize the instance.
  132. Args:
  133. crop_size (list|tuple|int): Width and height of the region to crop.
  134. mode (str, optional): 'C' for cropping the center part and 'TL' for
  135. cropping the top left part. Default: 'C'.
  136. """
  137. super().__init__()
  138. if isinstance(crop_size, int):
  139. crop_size = [crop_size, crop_size]
  140. _check_image_size(crop_size)
  141. self.crop_size = crop_size
  142. if mode not in ('C', 'TL'):
  143. raise ValueError("Unsupported interpolation method")
  144. self.mode = mode
  145. def apply(self, data):
  146. """ apply """
  147. im = data['image']
  148. h, w = im.shape[:2]
  149. cw, ch = self.crop_size
  150. if self.mode == 'C':
  151. x1 = max(0, (w - cw) // 2)
  152. y1 = max(0, (h - ch) // 2)
  153. elif self.mode == 'TL':
  154. x1, y1 = 0, 0
  155. x2 = min(w, x1 + cw)
  156. y2 = min(h, y1 + ch)
  157. coords = (x1, y1, x2, y2)
  158. if coords == (0, 0, w, h):
  159. raise ValueError(
  160. f"Input image ({w}, {h}) smaller than the target size ({cw}, {ch})."
  161. )
  162. im = F.slice(im, coords=coords)
  163. data['image'] = im
  164. data['image_size'] = [im.shape[1], im.shape[0]]
  165. return data
  166. @classmethod
  167. def get_input_keys(cls):
  168. """ get input keys """
  169. # image: Image in hw or hwc format.
  170. return ['image']
  171. @classmethod
  172. def get_output_keys(cls):
  173. """ get output keys """
  174. # image: Image in hw or hwc format.
  175. # image_size: Width and height of the image.
  176. return ['image', 'image_size']
  177. class _BaseResize(BaseTransform):
  178. _INTERP_DICT = {
  179. 'NEAREST': cv2.INTER_NEAREST,
  180. 'LINEAR': cv2.INTER_LINEAR,
  181. 'CUBIC': cv2.INTER_CUBIC,
  182. 'AREA': cv2.INTER_AREA,
  183. 'LANCZOS4': cv2.INTER_LANCZOS4
  184. }
  185. def __init__(self, size_divisor, interp):
  186. super().__init__()
  187. if size_divisor is not None:
  188. assert isinstance(size_divisor,
  189. int), "`size_divisor` should be None or int."
  190. self.size_divisor = size_divisor
  191. try:
  192. interp = self._INTERP_DICT[interp]
  193. except KeyError:
  194. raise ValueError("`interp` should be one of {}.".format(
  195. self._INTERP_DICT.keys()))
  196. self.interp = interp
  197. @staticmethod
  198. def _rescale_size(img_size, target_size):
  199. """ rescale size """
  200. scale = min(
  201. max(target_size) / max(img_size), min(target_size) / min(img_size))
  202. rescaled_size = [round(i * scale) for i in img_size]
  203. return rescaled_size, scale
  204. class Resize(_BaseResize):
  205. """Resize the image."""
  206. def __init__(self,
  207. target_size,
  208. keep_ratio=False,
  209. size_divisor=None,
  210. interp='LINEAR'):
  211. """
  212. Initialize the instance.
  213. Args:
  214. target_size (list|tuple|int): Target width and height.
  215. keep_ratio (bool, optional): Whether to keep the aspect ratio of resized
  216. image. Default: False.
  217. size_divisor (int|None, optional): Divisor of resized image size.
  218. Default: None.
  219. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  220. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  221. """
  222. super().__init__(size_divisor=size_divisor, interp=interp)
  223. if isinstance(target_size, int):
  224. target_size = [target_size, target_size]
  225. _check_image_size(target_size)
  226. self.target_size = target_size
  227. self.keep_ratio = keep_ratio
  228. def apply(self, data):
  229. """ apply """
  230. target_size = self.target_size
  231. im = data['image']
  232. original_size = im.shape[:2]
  233. if self.keep_ratio:
  234. h, w = im.shape[0:2]
  235. target_size, _ = self._rescale_size((w, h), self.target_size)
  236. if self.size_divisor:
  237. target_size = [
  238. math.ceil(i / self.size_divisor) * self.size_divisor
  239. for i in target_size
  240. ]
  241. im_scale_w, im_scale_h = [
  242. target_size[1] / original_size[1], target_size[0] / original_size[0]
  243. ]
  244. im = F.resize(im, target_size, interp=self.interp)
  245. data['image'] = im
  246. data['image_size'] = [im.shape[1], im.shape[0]]
  247. data['scale_factors'] = [im_scale_w, im_scale_h]
  248. return data
  249. @classmethod
  250. def get_input_keys(cls):
  251. """ get input keys """
  252. # image: Image in hw or hwc format.
  253. return ['image']
  254. @classmethod
  255. def get_output_keys(cls):
  256. """ get output keys """
  257. # image: Image in hw or hwc format.
  258. # image_size: Width and height of the image.
  259. # scale_factors: Scale factors for image width and height.
  260. return ['image', 'image_size', 'scale_factors']
  261. class ResizeByLong(_BaseResize):
  262. """
  263. Proportionally resize the image by specifying the target length of the
  264. longest side.
  265. """
  266. def __init__(self, target_long_edge, size_divisor=None, interp='LINEAR'):
  267. """
  268. Initialize the instance.
  269. Args:
  270. target_long_edge (int): Target length of the longest side of image.
  271. size_divisor (int|None, optional): Divisor of resized image size.
  272. Default: None.
  273. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  274. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  275. """
  276. super().__init__(size_divisor=size_divisor, interp=interp)
  277. self.target_long_edge = target_long_edge
  278. def apply(self, data):
  279. """ apply """
  280. im = data['image']
  281. h, w = im.shape[:2]
  282. scale = self.target_long_edge / max(h, w)
  283. h_resize = round(h * scale)
  284. w_resize = round(w * scale)
  285. if self.size_divisor is not None:
  286. h_resize = math.ceil(h_resize /
  287. self.size_divisor) * self.size_divisor
  288. w_resize = math.ceil(w_resize /
  289. self.size_divisor) * self.size_divisor
  290. im = F.resize(im, (w_resize, h_resize), interp=self.interp)
  291. data['image'] = im
  292. data['image_size'] = [im.shape[1], im.shape[0]]
  293. return data
  294. @classmethod
  295. def get_input_keys(cls):
  296. """ get input keys """
  297. # image: Image in hw or hwc format.
  298. return ['image']
  299. @classmethod
  300. def get_output_keys(cls):
  301. """ get output keys """
  302. # image: Image in hw or hwc format.
  303. # image_size: Width and height of the image.
  304. return ['image', 'image_size']
  305. class ResizeByShort(_BaseResize):
  306. """
  307. Proportionally resize the image by specifying the target length of the
  308. shortest side.
  309. """
  310. def __init__(self, target_short_edge, size_divisor=None, interp='LINEAR'):
  311. """
  312. Initialize the instance.
  313. Args:
  314. target_short_edge (int): Target length of the shortest side of image.
  315. size_divisor (int|None, optional): Divisor of resized image size.
  316. Default: None.
  317. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  318. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  319. """
  320. super().__init__(size_divisor=size_divisor, interp=interp)
  321. self.target_short_edge = target_short_edge
  322. def apply(self, data):
  323. """ apply """
  324. im = data['image']
  325. h, w = im.shape[:2]
  326. scale = self.target_short_edge / min(h, w)
  327. h_resize = round(h * scale)
  328. w_resize = round(w * scale)
  329. if self.size_divisor is not None:
  330. h_resize = math.ceil(h_resize /
  331. self.size_divisor) * self.size_divisor
  332. w_resize = math.ceil(w_resize /
  333. self.size_divisor) * self.size_divisor
  334. im = F.resize(im, (w_resize, h_resize), interp=self.interp)
  335. data['image'] = im
  336. data['image_size'] = [im.shape[1], im.shape[0]]
  337. return data
  338. @classmethod
  339. def get_input_keys(cls):
  340. """ get input keys """
  341. # image: Image in hw or hwc format.
  342. return ['image']
  343. @classmethod
  344. def get_output_keys(cls):
  345. """ get output keys """
  346. # image: Image in hw or hwc format.
  347. # image_size: Width and height of the image.
  348. return ['image', 'image_size']
  349. class Pad(BaseTransform):
  350. """Pad the image."""
  351. def __init__(self, target_size, val=127.5):
  352. """
  353. Initialize the instance.
  354. Args:
  355. target_size (list|tuple|int): Target width and height of the image after
  356. padding.
  357. val (float, optional): Value to fill the padded area. Default: 127.5.
  358. """
  359. super().__init__()
  360. if isinstance(target_size, int):
  361. target_size = [target_size, target_size]
  362. _check_image_size(target_size)
  363. self.target_size = target_size
  364. self.val = val
  365. def apply(self, data):
  366. """ apply """
  367. im = data['image']
  368. h, w = im.shape[:2]
  369. tw, th = self.target_size
  370. ph = th - h
  371. pw = tw - w
  372. if ph < 0 or pw < 0:
  373. raise ValueError(
  374. f"Input image ({w}, {h}) smaller than the target size ({tw}, {th})."
  375. )
  376. else:
  377. im = F.pad(im, pad=(0, ph, 0, pw), val=self.val)
  378. data['image'] = im
  379. data['image_size'] = [im.shape[1], im.shape[0]]
  380. return data
  381. @classmethod
  382. def get_input_keys(cls):
  383. """ get input keys """
  384. # image: Image in hw or hwc format.
  385. return ['image']
  386. @classmethod
  387. def get_output_keys(cls):
  388. """ get output keys """
  389. # image: Image in hw or hwc format.
  390. # image_size: Width and height of the image.
  391. return ['image', 'image_size']
  392. class Normalize(BaseTransform):
  393. """Normalize the image."""
  394. def __init__(self, scale=1. / 255, mean=0.5, std=0.5, preserve_dtype=False):
  395. """
  396. Initialize the instance.
  397. Args:
  398. scale (float, optional): Scaling factor to apply to the image before
  399. applying normalization. Default: 1/255.
  400. mean (float|tuple|list, optional): Means for each channel of the image.
  401. Default: 0.5.
  402. std (float|tuple|list, optional): Standard deviations for each channel
  403. of the image. Default: 0.5.
  404. preserve_dtype (bool, optional): Whether to preserve the original dtype
  405. of the image.
  406. """
  407. super().__init__()
  408. self.scale = np.float32(scale)
  409. if isinstance(mean, float):
  410. mean = [mean]
  411. self.mean = np.asarray(mean).astype('float32')
  412. if isinstance(std, float):
  413. std = [std]
  414. self.std = np.asarray(std).astype('float32')
  415. self.preserve_dtype = preserve_dtype
  416. def apply(self, data):
  417. """ apply """
  418. im = data['image']
  419. old_type = im.dtype
  420. # XXX: If `old_type` has higher precision than float32,
  421. # we will lose some precision.
  422. im = im.astype('float32', copy=False)
  423. im *= self.scale
  424. im -= self.mean
  425. im /= self.std
  426. if self.preserve_dtype:
  427. im = im.astype(old_type, copy=False)
  428. data['image'] = im
  429. return data
  430. @classmethod
  431. def get_input_keys(cls):
  432. """ get input keys """
  433. # image: Image in hw or hwc format.
  434. return ['image']
  435. @classmethod
  436. def get_output_keys(cls):
  437. """ get output keys """
  438. # image: Image in hw or hwc format.
  439. return ['image']
  440. class ToCHWImage(BaseTransform):
  441. """Reorder the dimensions of the image from HWC to CHW."""
  442. def apply(self, data):
  443. """ apply """
  444. im = data['image']
  445. im = im.transpose((2, 0, 1))
  446. data['image'] = im
  447. return data
  448. @classmethod
  449. def get_input_keys(cls):
  450. """ get input keys """
  451. # image: Image in hwc format.
  452. return ['image']
  453. @classmethod
  454. def get_output_keys(cls):
  455. """ get output keys """
  456. # image: Image in chw format.
  457. return ['image']