common.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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. from pathlib import Path
  16. from copy import deepcopy
  17. import numpy as np
  18. import cv2
  19. from .....utils.cache import CACHE_DIR, temp_file_manager
  20. from ....utils.io import ImageReader, ImageWriter, PDFReader
  21. from ...utils.mixin import BatchSizeMixin
  22. from ...base import BaseComponent
  23. from ..read_data import _BaseRead
  24. from . import funcs as F
  25. __all__ = [
  26. "ReadImage",
  27. "Flip",
  28. "Crop",
  29. "Resize",
  30. "ResizeByLong",
  31. "ResizeByShort",
  32. "Pad",
  33. "Normalize",
  34. "ToCHWImage",
  35. "PadStride",
  36. ]
  37. def _check_image_size(input_):
  38. """check image size"""
  39. if not (
  40. isinstance(input_, (list, tuple))
  41. and len(input_) == 2
  42. and isinstance(input_[0], int)
  43. and isinstance(input_[1], int)
  44. ):
  45. raise TypeError(f"{input_} cannot represent a valid image size.")
  46. class ReadImage(_BaseRead):
  47. """Load image from the file."""
  48. INPUT_KEYS = ["img"]
  49. OUTPUT_KEYS = ["img", "img_size", "ori_img", "ori_img_size"]
  50. DEAULT_INPUTS = {"img": "img"}
  51. DEAULT_OUTPUTS = {
  52. "img": "img",
  53. "input_path": "input_path",
  54. "img_size": "img_size",
  55. "ori_img": "ori_img",
  56. "ori_img_size": "ori_img_size",
  57. }
  58. _FLAGS_DICT = {
  59. "BGR": cv2.IMREAD_COLOR,
  60. "RGB": cv2.IMREAD_COLOR,
  61. "GRAY": cv2.IMREAD_GRAYSCALE,
  62. }
  63. SUFFIX = ["jpg", "png", "jpeg", "JPEG", "JPG", "bmp", "PDF", "pdf"]
  64. def __init__(self, batch_size=1, format="BGR"):
  65. """
  66. Initialize the instance.
  67. Args:
  68. format (str, optional): Target color format to convert the image to.
  69. Choices are 'BGR', 'RGB', and 'GRAY'. Default: 'BGR'.
  70. """
  71. super().__init__(batch_size)
  72. self.format = format
  73. flags = self._FLAGS_DICT[self.format]
  74. self._img_reader = ImageReader(backend="opencv", flags=flags)
  75. self._pdf_reader = PDFReader()
  76. self._writer = ImageWriter(backend="opencv")
  77. def apply(self, img):
  78. """apply"""
  79. if isinstance(img, np.ndarray):
  80. with temp_file_manager.temp_file_context(suffix=".png") as temp_file:
  81. img_path = Path(temp_file.name)
  82. self._writer.write(img_path, img)
  83. yield [
  84. {
  85. "input_path": img_path,
  86. "img": img,
  87. "img_size": [img.shape[1], img.shape[0]],
  88. "ori_img": deepcopy(img),
  89. "ori_img_size": deepcopy([img.shape[1], img.shape[0]]),
  90. }
  91. ]
  92. elif isinstance(img, str):
  93. file_path = img
  94. file_path = self._download_from_url(file_path)
  95. file_list = self._get_files_list(file_path)
  96. batch = []
  97. for file_path in file_list:
  98. img = self._read(file_path)
  99. batch.extend(img)
  100. if len(batch) >= self.batch_size:
  101. yield batch
  102. batch = []
  103. if len(batch) > 0:
  104. yield batch
  105. else:
  106. raise TypeError(
  107. f"ReadImage only supports the following types:\n"
  108. f"1. str, indicating a image file path or a directory containing image files.\n"
  109. f"2. numpy.ndarray.\n"
  110. f"However, got type: {type(img).__name__}."
  111. )
  112. def _read(self, file_path):
  113. if str(file_path).lower().endswith(".pdf"):
  114. return self._read_pdf(file_path)
  115. else:
  116. return self._read_img(file_path)
  117. def _read_img(self, img_path):
  118. blob = self._img_reader.read(img_path)
  119. if blob is None:
  120. raise Exception("Image read Error")
  121. if self.format == "RGB":
  122. if blob.ndim != 3:
  123. raise RuntimeError("Array is not 3-dimensional.")
  124. # BGR to RGB
  125. blob = blob[..., ::-1]
  126. return [
  127. {
  128. "input_path": img_path,
  129. "img": blob,
  130. "img_size": [blob.shape[1], blob.shape[0]],
  131. "ori_img": deepcopy(blob),
  132. "ori_img_size": deepcopy([blob.shape[1], blob.shape[0]]),
  133. }
  134. ]
  135. def _read_pdf(self, pdf_path):
  136. img_list = self._pdf_reader.read(pdf_path)
  137. return [
  138. {
  139. "input_path": pdf_path,
  140. "img": img,
  141. "img_size": [img.shape[1], img.shape[0]],
  142. "ori_img": deepcopy(img),
  143. "ori_img_size": deepcopy([img.shape[1], img.shape[0]]),
  144. }
  145. for img in img_list
  146. ]
  147. class GetImageInfo(BaseComponent):
  148. """Get Image Info"""
  149. INPUT_KEYS = "img"
  150. OUTPUT_KEYS = "img_size"
  151. DEAULT_INPUTS = {"img": "img"}
  152. DEAULT_OUTPUTS = {"img_size": "img_size"}
  153. def __init__(self):
  154. super().__init__()
  155. def apply(self, img):
  156. """apply"""
  157. return {"img_size": [img.shape[1], img.shape[0]]}
  158. class Flip(BaseComponent):
  159. """Flip the image vertically or horizontally."""
  160. INPUT_KEYS = "img"
  161. OUTPUT_KEYS = "img"
  162. DEAULT_INPUTS = {"img": "img"}
  163. DEAULT_OUTPUTS = {"img": "img"}
  164. def __init__(self, mode="H"):
  165. """
  166. Initialize the instance.
  167. Args:
  168. mode (str, optional): 'H' for horizontal flipping and 'V' for vertical
  169. flipping. Default: 'H'.
  170. """
  171. super().__init__()
  172. if mode not in ("H", "V"):
  173. raise ValueError("`mode` should be 'H' or 'V'.")
  174. self.mode = mode
  175. def apply(self, img):
  176. """apply"""
  177. if self.mode == "H":
  178. img = F.flip_h(img)
  179. elif self.mode == "V":
  180. img = F.flip_v(img)
  181. return {"img": img}
  182. class Crop(BaseComponent):
  183. """Crop region from the image."""
  184. INPUT_KEYS = "img"
  185. OUTPUT_KEYS = ["img", "img_size"]
  186. DEAULT_INPUTS = {"img": "img"}
  187. DEAULT_OUTPUTS = {"img": "img", "img_size": "img_size"}
  188. def __init__(self, crop_size, mode="C"):
  189. """
  190. Initialize the instance.
  191. Args:
  192. crop_size (list|tuple|int): Width and height of the region to crop.
  193. mode (str, optional): 'C' for cropping the center part and 'TL' for
  194. cropping the top left part. Default: 'C'.
  195. """
  196. super().__init__()
  197. if isinstance(crop_size, int):
  198. crop_size = [crop_size, crop_size]
  199. _check_image_size(crop_size)
  200. self.crop_size = crop_size
  201. if mode not in ("C", "TL"):
  202. raise ValueError("Unsupported interpolation method")
  203. self.mode = mode
  204. def apply(self, img):
  205. """apply"""
  206. h, w = img.shape[:2]
  207. cw, ch = self.crop_size
  208. if self.mode == "C":
  209. x1 = max(0, (w - cw) // 2)
  210. y1 = max(0, (h - ch) // 2)
  211. elif self.mode == "TL":
  212. x1, y1 = 0, 0
  213. x2 = min(w, x1 + cw)
  214. y2 = min(h, y1 + ch)
  215. coords = (x1, y1, x2, y2)
  216. if coords == (0, 0, w, h):
  217. raise ValueError(
  218. f"Input image ({w}, {h}) smaller than the target size ({cw}, {ch})."
  219. )
  220. img = F.slice(img, coords=coords)
  221. return {"img": img, "img_size": [img.shape[1], img.shape[0]]}
  222. class _BaseResize(BaseComponent):
  223. _INTERP_DICT = {
  224. "NEAREST": cv2.INTER_NEAREST,
  225. "LINEAR": cv2.INTER_LINEAR,
  226. "CUBIC": cv2.INTER_CUBIC,
  227. "AREA": cv2.INTER_AREA,
  228. "LANCZOS4": cv2.INTER_LANCZOS4,
  229. }
  230. def __init__(self, size_divisor, interp):
  231. super().__init__()
  232. if size_divisor is not None:
  233. assert isinstance(
  234. size_divisor, int
  235. ), "`size_divisor` should be None or int."
  236. self.size_divisor = size_divisor
  237. try:
  238. interp = self._INTERP_DICT[interp]
  239. except KeyError:
  240. raise ValueError(
  241. "`interp` should be one of {}.".format(self._INTERP_DICT.keys())
  242. )
  243. self.interp = interp
  244. @staticmethod
  245. def _rescale_size(img_size, target_size):
  246. """rescale size"""
  247. scale = min(max(target_size) / max(img_size), min(target_size) / min(img_size))
  248. rescaled_size = [round(i * scale) for i in img_size]
  249. return rescaled_size, scale
  250. class Resize(_BaseResize):
  251. """Resize the image."""
  252. INPUT_KEYS = "img"
  253. OUTPUT_KEYS = ["img", "img_size", "scale_factors"]
  254. DEAULT_INPUTS = {"img": "img"}
  255. DEAULT_OUTPUTS = {
  256. "img": "img",
  257. "img_size": "img_size",
  258. "scale_factors": "scale_factors",
  259. }
  260. def __init__(
  261. self, target_size, keep_ratio=False, size_divisor=None, interp="LINEAR"
  262. ):
  263. """
  264. Initialize the instance.
  265. Args:
  266. target_size (list|tuple|int): Target width and height.
  267. keep_ratio (bool, optional): Whether to keep the aspect ratio of resized
  268. image. Default: False.
  269. size_divisor (int|None, optional): Divisor of resized image size.
  270. Default: None.
  271. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  272. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  273. """
  274. super().__init__(size_divisor=size_divisor, interp=interp)
  275. if isinstance(target_size, int):
  276. target_size = [target_size, target_size]
  277. _check_image_size(target_size)
  278. self.target_size = target_size
  279. self.keep_ratio = keep_ratio
  280. def apply(self, img):
  281. """apply"""
  282. target_size = self.target_size
  283. original_size = img.shape[:2][::-1]
  284. if self.keep_ratio:
  285. h, w = img.shape[0:2]
  286. target_size, _ = self._rescale_size((w, h), self.target_size)
  287. if self.size_divisor:
  288. target_size = [
  289. math.ceil(i / self.size_divisor) * self.size_divisor
  290. for i in target_size
  291. ]
  292. img_scale_w, img_scale_h = [
  293. target_size[0] / original_size[0],
  294. target_size[1] / original_size[1],
  295. ]
  296. img = F.resize(img, target_size, interp=self.interp)
  297. return {
  298. "img": img,
  299. "img_size": [img.shape[1], img.shape[0]],
  300. "scale_factors": [img_scale_w, img_scale_h],
  301. }
  302. class ResizeByLong(_BaseResize):
  303. """
  304. Proportionally resize the image by specifying the target length of the
  305. longest side.
  306. """
  307. INPUT_KEYS = "img"
  308. OUTPUT_KEYS = ["img", "img_size"]
  309. DEAULT_INPUTS = {"img": "img"}
  310. DEAULT_OUTPUTS = {"img": "img", "img_size": "img_size"}
  311. def __init__(self, target_long_edge, size_divisor=None, interp="LINEAR"):
  312. """
  313. Initialize the instance.
  314. Args:
  315. target_long_edge (int): Target length of the longest side of image.
  316. size_divisor (int|None, optional): Divisor of resized image size.
  317. Default: None.
  318. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  319. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  320. """
  321. super().__init__(size_divisor=size_divisor, interp=interp)
  322. self.target_long_edge = target_long_edge
  323. def apply(self, img):
  324. """apply"""
  325. h, w = img.shape[:2]
  326. scale = self.target_long_edge / max(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 / self.size_divisor) * self.size_divisor
  331. w_resize = math.ceil(w_resize / self.size_divisor) * self.size_divisor
  332. img = F.resize(img, (w_resize, h_resize), interp=self.interp)
  333. return {"img": img, "img_size": [img.shape[1], img.shape[0]]}
  334. class ResizeByShort(_BaseResize):
  335. """
  336. Proportionally resize the image by specifying the target length of the
  337. shortest side.
  338. """
  339. INPUT_KEYS = "img"
  340. OUTPUT_KEYS = ["img", "img_size"]
  341. DEAULT_INPUTS = {"img": "img"}
  342. DEAULT_OUTPUTS = {"img": "img", "img_size": "img_size"}
  343. def __init__(self, target_short_edge, size_divisor=None, interp="LINEAR"):
  344. """
  345. Initialize the instance.
  346. Args:
  347. target_short_edge (int): Target length of the shortest side of image.
  348. size_divisor (int|None, optional): Divisor of resized image size.
  349. Default: None.
  350. interp (str, optional): Interpolation method. Choices are 'NEAREST',
  351. 'LINEAR', 'CUBIC', 'AREA', and 'LANCZOS4'. Default: 'LINEAR'.
  352. """
  353. super().__init__(size_divisor=size_divisor, interp=interp)
  354. self.target_short_edge = target_short_edge
  355. def apply(self, img):
  356. """apply"""
  357. h, w = img.shape[:2]
  358. scale = self.target_short_edge / min(h, w)
  359. h_resize = round(h * scale)
  360. w_resize = round(w * scale)
  361. if self.size_divisor is not None:
  362. h_resize = math.ceil(h_resize / self.size_divisor) * self.size_divisor
  363. w_resize = math.ceil(w_resize / self.size_divisor) * self.size_divisor
  364. img = F.resize(img, (w_resize, h_resize), interp=self.interp)
  365. return {"img": img, "img_size": [img.shape[1], img.shape[0]]}
  366. class Pad(BaseComponent):
  367. """Pad the image."""
  368. INPUT_KEYS = "img"
  369. OUTPUT_KEYS = ["img", "img_size"]
  370. DEAULT_INPUTS = {"img": "img"}
  371. DEAULT_OUTPUTS = {"img": "img", "img_size": "img_size"}
  372. def __init__(self, target_size, val=127.5):
  373. """
  374. Initialize the instance.
  375. Args:
  376. target_size (list|tuple|int): Target width and height of the image after
  377. padding.
  378. val (float, optional): Value to fill the padded area. Default: 127.5.
  379. """
  380. super().__init__()
  381. if isinstance(target_size, int):
  382. target_size = [target_size, target_size]
  383. _check_image_size(target_size)
  384. self.target_size = target_size
  385. self.val = val
  386. def apply(self, img):
  387. """apply"""
  388. h, w = img.shape[:2]
  389. tw, th = self.target_size
  390. ph = th - h
  391. pw = tw - w
  392. if ph < 0 or pw < 0:
  393. raise ValueError(
  394. f"Input image ({w}, {h}) smaller than the target size ({tw}, {th})."
  395. )
  396. else:
  397. img = F.pad(img, pad=(0, ph, 0, pw), val=self.val)
  398. return {"img": img, "img_size": [img.shape[1], img.shape[0]]}
  399. class PadStride(BaseComponent):
  400. """padding image for model with FPN , instead PadBatch(pad_to_stride, pad_gt) in original config
  401. Args:
  402. stride (bool): model with FPN need image shape % stride == 0
  403. """
  404. INPUT_KEYS = "img"
  405. OUTPUT_KEYS = "img"
  406. DEAULT_INPUTS = {"img": "img"}
  407. DEAULT_OUTPUTS = {"img": "img"}
  408. def __init__(self, stride=0):
  409. super().__init__()
  410. self.coarsest_stride = stride
  411. def apply(self, img):
  412. """
  413. Args:
  414. im (np.ndarray): image (np.ndarray)
  415. Returns:
  416. im (np.ndarray): processed image (np.ndarray)
  417. """
  418. im = img
  419. coarsest_stride = self.coarsest_stride
  420. if coarsest_stride <= 0:
  421. return {"img": im}
  422. im_c, im_h, im_w = im.shape
  423. pad_h = int(np.ceil(float(im_h) / coarsest_stride) * coarsest_stride)
  424. pad_w = int(np.ceil(float(im_w) / coarsest_stride) * coarsest_stride)
  425. padding_im = np.zeros((im_c, pad_h, pad_w), dtype=np.float32)
  426. padding_im[:, :im_h, :im_w] = im
  427. return {"img": padding_im}
  428. class Normalize(BaseComponent):
  429. """Normalize the image."""
  430. INPUT_KEYS = "img"
  431. OUTPUT_KEYS = "img"
  432. DEAULT_INPUTS = {"img": "img"}
  433. DEAULT_OUTPUTS = {"img": "img"}
  434. def __init__(self, scale=1.0 / 255, mean=0.5, std=0.5, preserve_dtype=False):
  435. """
  436. Initialize the instance.
  437. Args:
  438. scale (float, optional): Scaling factor to apply to the image before
  439. applying normalization. Default: 1/255.
  440. mean (float|tuple|list, optional): Means for each channel of the image.
  441. Default: 0.5.
  442. std (float|tuple|list, optional): Standard deviations for each channel
  443. of the image. Default: 0.5.
  444. preserve_dtype (bool, optional): Whether to preserve the original dtype
  445. of the image.
  446. """
  447. super().__init__()
  448. self.scale = np.float32(scale)
  449. if isinstance(mean, float):
  450. mean = [mean]
  451. self.mean = np.asarray(mean).astype("float32")
  452. if isinstance(std, float):
  453. std = [std]
  454. self.std = np.asarray(std).astype("float32")
  455. self.preserve_dtype = preserve_dtype
  456. def apply(self, img):
  457. """apply"""
  458. old_type = img.dtype
  459. # XXX: If `old_type` has higher precision than float32,
  460. # we will lose some precision.
  461. img = img.astype("float32", copy=False)
  462. img *= self.scale
  463. img -= self.mean
  464. img /= self.std
  465. if self.preserve_dtype:
  466. img = img.astype(old_type, copy=False)
  467. return {"img": img}
  468. class ToCHWImage(BaseComponent):
  469. """Reorder the dimensions of the image from HWC to CHW."""
  470. INPUT_KEYS = "img"
  471. OUTPUT_KEYS = "img"
  472. DEAULT_INPUTS = {"img": "img"}
  473. DEAULT_OUTPUTS = {"img": "img"}
  474. def apply(self, img):
  475. """apply"""
  476. img = img.transpose((2, 0, 1))
  477. return {"img": img}