artistic.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. """
  2. Augmenters that apply artistic image filters.
  3. List of augmenters:
  4. * :class:`Cartoon`
  5. Added in 0.4.0.
  6. """
  7. from __future__ import print_function, division, absolute_import
  8. import numpy as np
  9. import cv2
  10. from imgaug.imgaug import _normalize_cv2_input_arr_
  11. from . import meta
  12. from . import color as colorlib
  13. from .. import dtypes as iadt
  14. from .. import parameters as iap
  15. def stylize_cartoon(image, blur_ksize=3, segmentation_size=1.0,
  16. saturation=2.0, edge_prevalence=1.0,
  17. suppress_edges=True,
  18. from_colorspace=colorlib.CSPACE_RGB):
  19. """Convert the style of an image to a more cartoonish one.
  20. This function was primarily designed for images with a size of ``200``
  21. to ``800`` pixels. Smaller or larger images may cause issues.
  22. Note that the quality of the results can currently not compete with
  23. learned style transfer, let alone human-made images. A lack of detected
  24. edges or also too many detected edges are probably the most significant
  25. drawbacks.
  26. This method is loosely based on the one proposed in
  27. https://stackoverflow.com/a/11614479/3760780
  28. Added in 0.4.0.
  29. **Supported dtypes**:
  30. * ``uint8``: yes; fully tested
  31. * ``uint16``: no
  32. * ``uint32``: no
  33. * ``uint64``: no
  34. * ``int8``: no
  35. * ``int16``: no
  36. * ``int32``: no
  37. * ``int64``: no
  38. * ``float16``: no
  39. * ``float32``: no
  40. * ``float64``: no
  41. * ``float128``: no
  42. * ``bool``: no
  43. Parameters
  44. ----------
  45. image : ndarray
  46. A ``(H,W,3) uint8`` image array.
  47. blur_ksize : int, optional
  48. Kernel size of the median blur filter applied initially to the input
  49. image. Expected to be an odd value and ``>=0``. If an even value,
  50. thn automatically increased to an odd one. If ``<=1``, no blur will
  51. be applied.
  52. segmentation_size : float, optional
  53. Size multiplier to decrease/increase the base size of the initial
  54. mean-shift segmentation of the image. Expected to be ``>=0``.
  55. Note that the base size is increased by roughly a factor of two for
  56. images with height and/or width ``>=400``.
  57. edge_prevalence : float, optional
  58. Multiplier for the prevalance of edges. Higher values lead to more
  59. edges. Note that the default value of ``1.0`` is already fairly
  60. conservative, so there is limit effect from lowerin it further.
  61. saturation : float, optional
  62. Multiplier for the saturation. Set to ``1.0`` to not change the
  63. image's saturation.
  64. suppress_edges : bool, optional
  65. Whether to run edge suppression to remove blobs containing too many
  66. or too few edge pixels.
  67. from_colorspace : str, optional
  68. The source colorspace. Use one of ``imgaug.augmenters.color.CSPACE_*``.
  69. Defaults to ``RGB``.
  70. Returns
  71. -------
  72. ndarray
  73. Image in cartoonish style.
  74. """
  75. iadt.gate_dtypes(
  76. image,
  77. allowed=["uint8"],
  78. disallowed=["bool",
  79. "uint16", "uint32", "uint64", "uint128", "uint256",
  80. "int8", "int16", "int32", "int64", "int128", "int256",
  81. "float16", "float32", "float64", "float96", "float128",
  82. "float256"],
  83. augmenter=None)
  84. assert image.ndim == 3 and image.shape[2] == 3, (
  85. "Expected to get a (H,W,C) image, got shape %s." % (image.shape,))
  86. blur_ksize = max(int(np.round(blur_ksize)), 1)
  87. segmentation_size = max(segmentation_size, 0.0)
  88. saturation = max(saturation, 0.0)
  89. is_small_image = max(image.shape[0:2]) < 400
  90. image = _blur_median(image, blur_ksize)
  91. image_seg = np.zeros_like(image)
  92. if is_small_image:
  93. spatial_window_radius = int(10 * segmentation_size)
  94. color_window_radius = int(20 * segmentation_size)
  95. else:
  96. spatial_window_radius = int(15 * segmentation_size)
  97. color_window_radius = int(40 * segmentation_size)
  98. if segmentation_size <= 0:
  99. image_seg = image
  100. else:
  101. cv2.pyrMeanShiftFiltering(_normalize_cv2_input_arr_(image),
  102. sp=spatial_window_radius,
  103. sr=color_window_radius,
  104. dst=image_seg)
  105. if is_small_image:
  106. edges_raw = _find_edges_canny(image_seg,
  107. edge_prevalence,
  108. from_colorspace)
  109. else:
  110. edges_raw = _find_edges_laplacian(image_seg,
  111. edge_prevalence,
  112. from_colorspace)
  113. edges = edges_raw
  114. edges = ((edges > 100) * 255).astype(np.uint8)
  115. if suppress_edges:
  116. # Suppress dense 3x3 blobs full of detected edges. They are visually
  117. # ugly.
  118. edges = _suppress_edge_blobs(edges, 3, 8, inverse=False)
  119. # Suppress spurious few-pixel edges (5x5 size with <=3 edge pixels).
  120. edges = _suppress_edge_blobs(edges, 5, 3, inverse=True)
  121. return _saturate(_blend_edges(image_seg, edges),
  122. saturation,
  123. from_colorspace)
  124. # Added in 0.4.0.
  125. def _find_edges_canny(image, edge_multiplier, from_colorspace):
  126. image_gray = colorlib.change_colorspace_(np.copy(image),
  127. to_colorspace=colorlib.CSPACE_GRAY,
  128. from_colorspace=from_colorspace)
  129. image_gray = image_gray[..., 0]
  130. thresh = min(int(200 * (1/edge_multiplier)), 254)
  131. edges = cv2.Canny(_normalize_cv2_input_arr_(image_gray), thresh, thresh)
  132. return edges
  133. # Added in 0.4.0.
  134. def _find_edges_laplacian(image, edge_multiplier, from_colorspace):
  135. image_gray = colorlib.change_colorspace_(np.copy(image),
  136. to_colorspace=colorlib.CSPACE_GRAY,
  137. from_colorspace=from_colorspace)
  138. image_gray = image_gray[..., 0]
  139. edges_f = cv2.Laplacian(_normalize_cv2_input_arr_(image_gray / 255.0),
  140. cv2.CV_64F)
  141. edges_f = np.abs(edges_f)
  142. edges_f = edges_f ** 2
  143. vmax = np.percentile(edges_f, min(int(90 * (1/edge_multiplier)), 99))
  144. edges_f = np.clip(edges_f, 0.0, vmax) / vmax
  145. edges_uint8 = np.clip(np.round(edges_f * 255), 0, 255.0).astype(np.uint8)
  146. edges_uint8 = _blur_median(edges_uint8, 3)
  147. edges_uint8 = _threshold(edges_uint8, 50)
  148. return edges_uint8
  149. # Added in 0.4.0.
  150. def _blur_median(image, ksize):
  151. if ksize % 2 == 0:
  152. ksize += 1
  153. if ksize <= 1:
  154. return image
  155. return cv2.medianBlur(_normalize_cv2_input_arr_(image), ksize)
  156. # Added in 0.4.0.
  157. def _threshold(image, thresh):
  158. mask = (image < thresh)
  159. result = np.copy(image)
  160. result[mask] = 0
  161. return result
  162. # Added in 0.4.0.
  163. def _suppress_edge_blobs(edges, size, thresh, inverse):
  164. kernel = np.ones((size, size), dtype=np.float32)
  165. counts = cv2.filter2D(_normalize_cv2_input_arr_(edges / 255.0), -1, kernel)
  166. if inverse:
  167. mask = (counts < thresh)
  168. else:
  169. mask = (counts >= thresh)
  170. edges = np.copy(edges)
  171. edges[mask] = 0
  172. return edges
  173. # Added in 0.4.0.
  174. def _saturate(image, factor, from_colorspace):
  175. image = np.copy(image)
  176. if np.isclose(factor, 1.0, atol=1e-2):
  177. return image
  178. hsv = colorlib.change_colorspace_(image,
  179. to_colorspace=colorlib.CSPACE_HSV,
  180. from_colorspace=from_colorspace)
  181. sat = hsv[:, :, 1]
  182. sat = np.clip(sat.astype(np.int32) * factor, 0, 255).astype(np.uint8)
  183. hsv[:, :, 1] = sat
  184. image_sat = colorlib.change_colorspace_(hsv,
  185. to_colorspace=from_colorspace,
  186. from_colorspace=colorlib.CSPACE_HSV)
  187. return image_sat
  188. # Added in 0.4.0.
  189. def _blend_edges(image, image_edges):
  190. image_edges = 1.0 - (image_edges / 255.0)
  191. image_edges = np.tile(image_edges[..., np.newaxis], (1, 1, 3))
  192. return np.clip(
  193. np.round(image * image_edges),
  194. 0.0, 255.0
  195. ).astype(np.uint8)
  196. class Cartoon(meta.Augmenter):
  197. """Convert the style of images to a more cartoonish one.
  198. This augmenter was primarily designed for images with a size of ``200``
  199. to ``800`` pixels. Smaller or larger images may cause issues.
  200. Note that the quality of the results can currently not compete with
  201. learned style transfer, let alone human-made images. A lack of detected
  202. edges or also too many detected edges are probably the most significant
  203. drawbacks.
  204. Added in 0.4.0.
  205. **Supported dtypes**:
  206. See :func:`~imgaug.augmenters.artistic.stylize_cartoon`.
  207. Parameters
  208. ----------
  209. blur_ksize : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  210. Median filter kernel size.
  211. See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
  212. * If ``number``: That value will be used for all images.
  213. * If ``tuple (a, b) of number``: A random value will be uniformly
  214. sampled per image from the interval ``[a, b)``.
  215. * If ``list``: A random value will be picked per image from the
  216. ``list``.
  217. * If ``StochasticParameter``: The parameter will be queried once
  218. per batch for ``(N,)`` values, where ``N`` is the number of
  219. images.
  220. segmentation_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  221. Mean-Shift segmentation size multiplier.
  222. See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
  223. * If ``number``: That value will be used for all images.
  224. * If ``tuple (a, b) of number``: A random value will be uniformly
  225. sampled per image from the interval ``[a, b)``.
  226. * If ``list``: A random value will be picked per image from the
  227. ``list``.
  228. * If ``StochasticParameter``: The parameter will be queried once
  229. per batch for ``(N,)`` values, where ``N`` is the number of
  230. images.
  231. saturation : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  232. Saturation multiplier.
  233. See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
  234. * If ``number``: That value will be used for all images.
  235. * If ``tuple (a, b) of number``: A random value will be uniformly
  236. sampled per image from the interval ``[a, b)``.
  237. * If ``list``: A random value will be picked per image from the
  238. ``list``.
  239. * If ``StochasticParameter``: The parameter will be queried once
  240. per batch for ``(N,)`` values, where ``N`` is the number of
  241. images.
  242. edge_prevalence : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  243. Multiplier for the prevalence of edges.
  244. See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
  245. * If ``number``: That value will be used for all images.
  246. * If ``tuple (a, b) of number``: A random value will be uniformly
  247. sampled per image from the interval ``[a, b)``.
  248. * If ``list``: A random value will be picked per image from the
  249. ``list``.
  250. * If ``StochasticParameter``: The parameter will be queried once
  251. per batch for ``(N,)`` values, where ``N`` is the number of
  252. images.
  253. from_colorspace : str, optional
  254. The source colorspace. Use one of ``imgaug.augmenters.color.CSPACE_*``.
  255. Defaults to ``RGB``.
  256. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  257. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  258. name : None or str, optional
  259. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  260. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  261. Old name for parameter `seed`.
  262. Its usage will not yet cause a deprecation warning,
  263. but it is still recommended to use `seed` now.
  264. Outdated since 0.4.0.
  265. deterministic : bool, optional
  266. Deprecated since 0.4.0.
  267. See method ``to_deterministic()`` for an alternative and for
  268. details about what the "deterministic mode" actually does.
  269. Examples
  270. --------
  271. >>> import imgaug.augmenters as iaa
  272. >>> aug = iaa.Cartoon()
  273. Create an example image, then apply a cartoon filter to it.
  274. >>> aug = iaa.Cartoon(blur_ksize=3, segmentation_size=1.0,
  275. >>> saturation=2.0, edge_prevalence=1.0)
  276. Create a non-stochastic cartoon augmenter that produces decent-looking
  277. images.
  278. """
  279. # Added in 0.4.0.
  280. def __init__(self, blur_ksize=(1, 5), segmentation_size=(0.8, 1.2),
  281. saturation=(1.5, 2.5), edge_prevalence=(0.9, 1.1),
  282. from_colorspace=colorlib.CSPACE_RGB,
  283. seed=None, name=None,
  284. random_state="deprecated", deterministic="deprecated"):
  285. super(Cartoon, self).__init__(
  286. seed=seed, name=name,
  287. random_state=random_state, deterministic=deterministic)
  288. self.blur_ksize = iap.handle_continuous_param(
  289. blur_ksize, "blur_ksize", value_range=(0, None),
  290. tuple_to_uniform=True, list_to_choice=True)
  291. self.segmentation_size = iap.handle_continuous_param(
  292. segmentation_size, "segmentation_size", value_range=(0.0, None),
  293. tuple_to_uniform=True, list_to_choice=True)
  294. self.saturation = iap.handle_continuous_param(
  295. saturation, "saturation", value_range=(0.0, None),
  296. tuple_to_uniform=True, list_to_choice=True)
  297. self.edge_prevalence = iap.handle_continuous_param(
  298. edge_prevalence, "edge_prevalence", value_range=(0.0, None),
  299. tuple_to_uniform=True, list_to_choice=True)
  300. self.from_colorspace = from_colorspace
  301. # Added in 0.4.0.
  302. def _augment_batch_(self, batch, random_state, parents, hooks):
  303. if batch.images is not None:
  304. samples = self._draw_samples(batch, random_state)
  305. for i, image in enumerate(batch.images):
  306. image[...] = stylize_cartoon(
  307. image,
  308. blur_ksize=samples[0][i],
  309. segmentation_size=samples[1][i],
  310. saturation=samples[2][i],
  311. edge_prevalence=samples[3][i],
  312. from_colorspace=self.from_colorspace
  313. )
  314. return batch
  315. # Added in 0.4.0.
  316. def _draw_samples(self, batch, random_state):
  317. nb_rows = batch.nb_rows
  318. return (
  319. self.blur_ksize.draw_samples((nb_rows,), random_state=random_state),
  320. self.segmentation_size.draw_samples((nb_rows,),
  321. random_state=random_state),
  322. self.saturation.draw_samples((nb_rows,), random_state=random_state),
  323. self.edge_prevalence.draw_samples((nb_rows,),
  324. random_state=random_state)
  325. )
  326. # Added in 0.4.0.
  327. def get_parameters(self):
  328. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  329. return [self.blur_ksize, self.segmentation_size, self.saturation,
  330. self.edge_prevalence, self.from_colorspace]