convolutional.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. """
  2. Augmenters that are based on applying convolution kernels to images.
  3. List of augmenters:
  4. * :class:`Convolve`
  5. * :class:`Sharpen`
  6. * :class:`Emboss`
  7. * :class:`EdgeDetect`
  8. * :class:`DirectedEdgeDetect`
  9. For MotionBlur, see ``blur.py``.
  10. """
  11. from __future__ import print_function, division, absolute_import
  12. import itertools
  13. import numpy as np
  14. import cv2
  15. import six.moves as sm
  16. import imgaug as ia
  17. from imgaug.imgaug import _normalize_cv2_input_arr_
  18. from . import meta
  19. from .. import parameters as iap
  20. from .. import dtypes as iadt
  21. # TODO allow 3d matrices as input (not only 2D)
  22. # TODO add _augment_keypoints and other _augment funcs, as these should do
  23. # something for e.g. [[0, 0, 1]]
  24. class Convolve(meta.Augmenter):
  25. """
  26. Apply a convolution to input images.
  27. **Supported dtypes**:
  28. * ``uint8``: yes; fully tested
  29. * ``uint16``: yes; tested
  30. * ``uint32``: no (1)
  31. * ``uint64``: no (2)
  32. * ``int8``: yes; tested (3)
  33. * ``int16``: yes; tested
  34. * ``int32``: no (2)
  35. * ``int64``: no (2)
  36. * ``float16``: yes; tested (4)
  37. * ``float32``: yes; tested
  38. * ``float64``: yes; tested
  39. * ``float128``: no (1)
  40. * ``bool``: yes; tested (4)
  41. - (1) rejected by ``cv2.filter2D()``.
  42. - (2) causes error: cv2.error: OpenCV(3.4.2) (...)/filter.cpp:4487:
  43. error: (-213:The function/feature is not implemented)
  44. Unsupported combination of source format (=1), and destination
  45. format (=1) in function 'getLinearFilter'.
  46. - (3) mapped internally to ``int16``.
  47. - (4) mapped internally to ``float32``.
  48. Parameters
  49. ----------
  50. matrix : None or (H, W) ndarray or imgaug.parameters.StochasticParameter or callable, optional
  51. The weight matrix of the convolution kernel to apply.
  52. * If ``None``, the input images will not be changed.
  53. * If a 2D numpy array, that array will always be used for all
  54. images and channels as the kernel.
  55. * If a callable, that method will be called for each image
  56. via ``parameter(image, C, random_state)``. The function must
  57. either return a list of ``C`` matrices (i.e. one per channel)
  58. or a 2D numpy array (will be used for all channels) or a
  59. 3D ``HxWxC`` numpy array. If a list is returned, each entry may
  60. be ``None``, which will result in no changes to the respective
  61. channel.
  62. 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
  63. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  64. name : None or str, optional
  65. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  66. 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
  67. Old name for parameter `seed`.
  68. Its usage will not yet cause a deprecation warning,
  69. but it is still recommended to use `seed` now.
  70. Outdated since 0.4.0.
  71. deterministic : bool, optional
  72. Deprecated since 0.4.0.
  73. See method ``to_deterministic()`` for an alternative and for
  74. details about what the "deterministic mode" actually does.
  75. Examples
  76. --------
  77. >>> import imgaug.augmenters as iaa
  78. >>> matrix = np.array([[0, -1, 0],
  79. >>> [-1, 4, -1],
  80. >>> [0, -1, 0]])
  81. >>> aug = iaa.Convolve(matrix=matrix)
  82. Convolves all input images with the kernel shown in the ``matrix``
  83. variable.
  84. >>> def gen_matrix(image, nb_channels, random_state):
  85. >>> matrix_A = np.array([[0, -1, 0],
  86. >>> [-1, 4, -1],
  87. >>> [0, -1, 0]])
  88. >>> matrix_B = np.array([[0, 1, 0],
  89. >>> [1, -4, 1],
  90. >>> [0, 1, 0]])
  91. >>> if image.shape[0] % 2 == 0:
  92. >>> return [matrix_A] * nb_channels
  93. >>> else:
  94. >>> return [matrix_B] * nb_channels
  95. >>> aug = iaa.Convolve(matrix=gen_matrix)
  96. Convolves images that have an even height with matrix A and images
  97. having an odd height with matrix B.
  98. """
  99. def __init__(self, matrix=None,
  100. seed=None, name=None,
  101. random_state="deprecated", deterministic="deprecated"):
  102. super(Convolve, self).__init__(
  103. seed=seed, name=name,
  104. random_state=random_state, deterministic=deterministic)
  105. if matrix is None:
  106. self.matrix = None
  107. self.matrix_type = "None"
  108. elif ia.is_np_array(matrix):
  109. assert matrix.ndim == 2, (
  110. "Expected convolution matrix to have exactly two dimensions, "
  111. "got %d (shape %s)." % (matrix.ndim, matrix.shape))
  112. self.matrix = matrix
  113. self.matrix_type = "constant"
  114. elif ia.is_callable(matrix):
  115. self.matrix = matrix
  116. self.matrix_type = "function"
  117. else:
  118. raise Exception(
  119. "Expected float, int, tuple/list with 2 entries or "
  120. "StochasticParameter. Got %s." % (
  121. type(matrix),))
  122. # Added in 0.4.0.
  123. def _augment_batch_(self, batch, random_state, parents, hooks):
  124. if batch.images is None:
  125. return batch
  126. images = batch.images
  127. iadt.gate_dtypes(images,
  128. allowed=["bool",
  129. "uint8", "uint16",
  130. "int8", "int16",
  131. "float16", "float32", "float64"],
  132. disallowed=["uint32", "uint64", "uint128", "uint256",
  133. "int32", "int64", "int128", "int256",
  134. "float96", "float128", "float256"],
  135. augmenter=self)
  136. rss = random_state.duplicate(len(images))
  137. for i, image in enumerate(images):
  138. _height, _width, nb_channels = image.shape
  139. # currently we don't have to worry here about alignemnt with
  140. # non-image data and therefore can just place this before any
  141. # sampling
  142. if image.size == 0:
  143. continue
  144. input_dtype = image.dtype
  145. if image.dtype.name in ["bool", "float16"]:
  146. image = image.astype(np.float32, copy=False)
  147. elif image.dtype.name == "int8":
  148. image = image.astype(np.int16, copy=False)
  149. if self.matrix_type == "None":
  150. matrices = [None] * nb_channels
  151. elif self.matrix_type == "constant":
  152. matrices = [self.matrix] * nb_channels
  153. elif self.matrix_type == "function":
  154. matrices = self.matrix(images[i], nb_channels, rss[i])
  155. if ia.is_np_array(matrices) and matrices.ndim == 2:
  156. matrices = np.tile(
  157. matrices[..., np.newaxis],
  158. (1, 1, nb_channels))
  159. is_valid_list = (isinstance(matrices, list)
  160. and len(matrices) == nb_channels)
  161. is_valid_array = (ia.is_np_array(matrices)
  162. and matrices.ndim == 3
  163. and matrices.shape[2] == nb_channels)
  164. assert is_valid_list or is_valid_array, (
  165. "Callable provided to Convole must return either a "
  166. "list of 2D matrices (one per image channel) "
  167. "or a 2D numpy array "
  168. "or a 3D numpy array where the last dimension's size "
  169. "matches the number of image channels. "
  170. "Got type %s." % (type(matrices),))
  171. if ia.is_np_array(matrices):
  172. # Shape of matrices is currently (H, W, C), but in the
  173. # loop below we need the first axis to be the channel
  174. # index to unify handling of lists of arrays and arrays.
  175. # So we move the channel axis here to the start.
  176. matrices = matrices.transpose((2, 0, 1))
  177. else:
  178. raise Exception("Invalid matrix type")
  179. # TODO check if sampled matrices are identical over channels
  180. # and then just apply once. (does that really help wrt speed?)
  181. image_aug = image
  182. for channel in sm.xrange(nb_channels):
  183. if matrices[channel] is not None:
  184. # ndimage.convolve caused problems here cv2.filter2D()
  185. # always returns same output dtype as input dtype
  186. image_aug[..., channel] = cv2.filter2D(
  187. _normalize_cv2_input_arr_(image_aug[..., channel]),
  188. -1,
  189. matrices[channel]
  190. )
  191. if input_dtype.name == "bool":
  192. image_aug = image_aug > 0.5
  193. elif input_dtype.name in ["int8", "float16"]:
  194. image_aug = iadt.restore_dtypes_(image_aug, input_dtype)
  195. batch.images[i] = image_aug
  196. return batch
  197. def get_parameters(self):
  198. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  199. return [self.matrix, self.matrix_type]
  200. class Sharpen(Convolve):
  201. """
  202. Sharpen images and alpha-blend the result with the original input images.
  203. **Supported dtypes**:
  204. See :class:`~imgaug.augmenters.convolutional.Convolve`.
  205. Parameters
  206. ----------
  207. alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  208. Blending factor of the sharpened image. At ``0.0``, only the original
  209. image is visible, at ``1.0`` only its sharpened version is visible.
  210. * If a number, exactly that value will always be used.
  211. * If a tuple ``(a, b)``, a random value will be sampled from the
  212. interval ``[a, b]`` per image.
  213. * If a list, a random value will be sampled from that list
  214. per image.
  215. * If a ``StochasticParameter``, a value will be sampled from that
  216. parameter per image.
  217. lightness : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  218. Lightness/brightness of the sharped image.
  219. Sane values are somewhere in the interval ``[0.5, 2.0]``.
  220. The value ``0.0`` results in an edge map. Values higher than ``1.0``
  221. create bright images. Default value is ``1.0``.
  222. * If a number, exactly that value will always be used.
  223. * If a tuple ``(a, b)``, a random value will be sampled from the
  224. interval ``[a, b]`` per image.
  225. * If a list, a random value will be sampled from that list
  226. per image.
  227. * If a ``StochasticParameter``, a value will be sampled from that
  228. parameter per image.
  229. 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
  230. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  231. name : None or str, optional
  232. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  233. 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
  234. Old name for parameter `seed`.
  235. Its usage will not yet cause a deprecation warning,
  236. but it is still recommended to use `seed` now.
  237. Outdated since 0.4.0.
  238. deterministic : bool, optional
  239. Deprecated since 0.4.0.
  240. See method ``to_deterministic()`` for an alternative and for
  241. details about what the "deterministic mode" actually does.
  242. Examples
  243. --------
  244. >>> import imgaug.augmenters as iaa
  245. >>> aug = iaa.Sharpen(alpha=(0.0, 1.0))
  246. Sharpens input images and blends the sharpened image with the input image
  247. using a random blending factor between ``0%`` and ``100%`` (uniformly
  248. sampled).
  249. >>> aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=(0.75, 2.0))
  250. Sharpens input images with a variable `lightness` sampled uniformly from
  251. the interval ``[0.75, 2.0]`` and with a fully random blending factor
  252. (as in the above example).
  253. """
  254. def __init__(self, alpha=(0.0, 0.2), lightness=(0.8, 1.2),
  255. seed=None, name=None,
  256. random_state="deprecated", deterministic="deprecated"):
  257. alpha_param = iap.handle_continuous_param(
  258. alpha, "alpha",
  259. value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
  260. lightness_param = iap.handle_continuous_param(
  261. lightness, "lightness",
  262. value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
  263. matrix_gen = _SharpeningMatrixGenerator(alpha_param, lightness_param)
  264. super(Sharpen, self).__init__(
  265. matrix=matrix_gen,
  266. seed=seed, name=name,
  267. random_state=random_state, deterministic=deterministic)
  268. class _SharpeningMatrixGenerator(object):
  269. def __init__(self, alpha, lightness):
  270. self.alpha = alpha
  271. self.lightness = lightness
  272. def __call__(self, _image, nb_channels, random_state):
  273. alpha_sample = self.alpha.draw_sample(random_state=random_state)
  274. assert 0 <= alpha_sample <= 1.0, (
  275. "Expected 'alpha' to be in the interval [0.0, 1.0], "
  276. "got %.4f." % (alpha_sample,))
  277. lightness_sample = self.lightness.draw_sample(random_state=random_state)
  278. matrix_nochange = np.array([
  279. [0, 0, 0],
  280. [0, 1, 0],
  281. [0, 0, 0]
  282. ], dtype=np.float32)
  283. matrix_effect = np.array([
  284. [-1, -1, -1],
  285. [-1, 8+lightness_sample, -1],
  286. [-1, -1, -1]
  287. ], dtype=np.float32)
  288. matrix = (
  289. (1-alpha_sample) * matrix_nochange
  290. + alpha_sample * matrix_effect
  291. )
  292. return [matrix] * nb_channels
  293. class Emboss(Convolve):
  294. """
  295. Emboss images and alpha-blend the result with the original input images.
  296. The embossed version pronounces highlights and shadows,
  297. letting the image look as if it was recreated on a metal plate ("embossed").
  298. **Supported dtypes**:
  299. See :class:`~imgaug.augmenters.convolutional.Convolve`.
  300. Parameters
  301. ----------
  302. alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  303. Blending factor of the embossed image. At ``0.0``, only the original
  304. image is visible, at ``1.0`` only its embossed version is visible.
  305. * If a number, exactly that value will always be used.
  306. * If a tuple ``(a, b)``, a random value will be sampled from the
  307. interval ``[a, b]`` per image.
  308. * If a list, a random value will be sampled from that list
  309. per image.
  310. * If a ``StochasticParameter``, a value will be sampled from that
  311. parameter per image.
  312. strength : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  313. Parameter that controls the strength of the embossing.
  314. Sane values are somewhere in the interval ``[0.0, 2.0]`` with ``1.0``
  315. being the standard embossing effect. Default value is ``1.0``.
  316. * If a number, exactly that value will always be used.
  317. * If a tuple ``(a, b)``, a random value will be sampled from the
  318. interval ``[a, b]`` per image.
  319. * If a list, then a random value will be sampled from that list
  320. per image.
  321. * If a ``StochasticParameter``, a value will be sampled from the
  322. parameter per image.
  323. 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
  324. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  325. name : None or str, optional
  326. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  327. 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
  328. Old name for parameter `seed`.
  329. Its usage will not yet cause a deprecation warning,
  330. but it is still recommended to use `seed` now.
  331. Outdated since 0.4.0.
  332. deterministic : bool, optional
  333. Deprecated since 0.4.0.
  334. See method ``to_deterministic()`` for an alternative and for
  335. details about what the "deterministic mode" actually does.
  336. Examples
  337. --------
  338. >>> import imgaug.augmenters as iaa
  339. >>> aug = iaa.Emboss(alpha=(0.0, 1.0), strength=(0.5, 1.5))
  340. Emboss an image with a strength sampled uniformly from the interval
  341. ``[0.5, 1.5]`` and alpha-blend the result with the original input image
  342. using a random blending factor between ``0%`` and ``100%``.
  343. """
  344. def __init__(self, alpha=(0.0, 1.0), strength=(0.25, 1.0),
  345. seed=None, name=None,
  346. random_state="deprecated", deterministic="deprecated"):
  347. alpha_param = iap.handle_continuous_param(
  348. alpha, "alpha",
  349. value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
  350. strength_param = iap.handle_continuous_param(
  351. strength, "strength",
  352. value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
  353. matrix_gen = _EmbossMatrixGenerator(alpha_param, strength_param)
  354. super(Emboss, self).__init__(
  355. matrix=matrix_gen,
  356. seed=seed, name=name,
  357. random_state=random_state, deterministic=deterministic)
  358. class _EmbossMatrixGenerator(object):
  359. def __init__(self, alpha, strength):
  360. self.alpha = alpha
  361. self.strength = strength
  362. def __call__(self, _image, nb_channels, random_state):
  363. alpha_sample = self.alpha.draw_sample(random_state=random_state)
  364. assert 0 <= alpha_sample <= 1.0, (
  365. "Expected 'alpha' to be in the interval [0.0, 1.0], "
  366. "got %.4f." % (alpha_sample,))
  367. strength_sample = self.strength.draw_sample(random_state=random_state)
  368. matrix_nochange = np.array([
  369. [0, 0, 0],
  370. [0, 1, 0],
  371. [0, 0, 0]
  372. ], dtype=np.float32)
  373. matrix_effect = np.array([
  374. [-1-strength_sample, 0-strength_sample, 0],
  375. [0-strength_sample, 1, 0+strength_sample],
  376. [0, 0+strength_sample, 1+strength_sample]
  377. ], dtype=np.float32)
  378. matrix = (
  379. (1-alpha_sample) * matrix_nochange
  380. + alpha_sample * matrix_effect
  381. )
  382. return [matrix] * nb_channels
  383. # TODO add tests
  384. # TODO move this to edges.py?
  385. class EdgeDetect(Convolve):
  386. """
  387. Generate a black & white edge image and alpha-blend it with the input image.
  388. **Supported dtypes**:
  389. See :class:`~imgaug.augmenters.convolutional.Convolve`.
  390. Parameters
  391. ----------
  392. alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  393. Blending factor of the edge image. At ``0.0``, only the original
  394. image is visible, at ``1.0`` only the edge image is visible.
  395. * If a number, exactly that value will always be used.
  396. * If a tuple ``(a, b)``, a random value will be sampled from the
  397. interval ``[a, b]`` per image.
  398. * If a list, a random value will be sampled from that list
  399. per image.
  400. * If a ``StochasticParameter``, a value will be sampled from that
  401. parameter per image.
  402. 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
  403. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  404. name : None or str, optional
  405. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  406. 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
  407. Old name for parameter `seed`.
  408. Its usage will not yet cause a deprecation warning,
  409. but it is still recommended to use `seed` now.
  410. Outdated since 0.4.0.
  411. deterministic : bool, optional
  412. Deprecated since 0.4.0.
  413. See method ``to_deterministic()`` for an alternative and for
  414. details about what the "deterministic mode" actually does.
  415. Examples
  416. --------
  417. >>> import imgaug.augmenters as iaa
  418. >>> aug = iaa.EdgeDetect(alpha=(0.0, 1.0))
  419. Detect edges in an image, mark them as black (non-edge) and white (edges)
  420. and alpha-blend the result with the original input image using a random
  421. blending factor between ``0%`` and ``100%``.
  422. """
  423. def __init__(self, alpha=(0.0, 0.75),
  424. seed=None, name=None,
  425. random_state="deprecated", deterministic="deprecated"):
  426. alpha_param = iap.handle_continuous_param(
  427. alpha, "alpha",
  428. value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
  429. matrix_gen = _EdgeDetectMatrixGenerator(alpha_param)
  430. super(EdgeDetect, self).__init__(
  431. matrix=matrix_gen,
  432. seed=seed, name=name,
  433. random_state=random_state, deterministic=deterministic)
  434. class _EdgeDetectMatrixGenerator(object):
  435. def __init__(self, alpha):
  436. self.alpha = alpha
  437. def __call__(self, _image, nb_channels, random_state):
  438. alpha_sample = self.alpha.draw_sample(random_state=random_state)
  439. assert 0 <= alpha_sample <= 1.0, (
  440. "Expected 'alpha' to be in the interval [0.0, 1.0], "
  441. "got %.4f." % (alpha_sample,))
  442. matrix_nochange = np.array([
  443. [0, 0, 0],
  444. [0, 1, 0],
  445. [0, 0, 0]
  446. ], dtype=np.float32)
  447. matrix_effect = np.array([
  448. [0, 1, 0],
  449. [1, -4, 1],
  450. [0, 1, 0]
  451. ], dtype=np.float32)
  452. matrix = (
  453. (1-alpha_sample) * matrix_nochange
  454. + alpha_sample * matrix_effect
  455. )
  456. return [matrix] * nb_channels
  457. # TODO add tests
  458. # TODO merge EdgeDetect and DirectedEdgeDetect?
  459. # TODO deprecate and rename to AngledEdgeDetect
  460. # TODO rename arg "direction" to "angle"
  461. # TODO change direction/angle value range to (0, 360)
  462. # TODO move this to edges.py?
  463. class DirectedEdgeDetect(Convolve):
  464. """
  465. Detect edges from specified angles and alpha-blend with the input image.
  466. This augmenter first detects edges along a certain angle.
  467. Usually, edges are detected in x- or y-direction, while here the edge
  468. detection kernel is rotated to match a specified angle.
  469. The result of applying the kernel is a black (non-edges) and white (edges)
  470. image. That image is alpha-blended with the input image.
  471. **Supported dtypes**:
  472. See :class:`~imgaug.augmenters.convolutional.Convolve`.
  473. Parameters
  474. ----------
  475. alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  476. Blending factor of the edge image. At ``0.0``, only the original
  477. image is visible, at ``1.0`` only the edge image is visible.
  478. * If a number, exactly that value will always be used.
  479. * If a tuple ``(a, b)``, a random value will be sampled from the
  480. interval ``[a, b]`` per image.
  481. * If a list, a random value will be sampled from that list
  482. per image.
  483. * If a ``StochasticParameter``, a value will be sampled from that
  484. parameter per image.
  485. direction : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  486. Angle (in degrees) of edges to pronounce, where ``0`` represents
  487. ``0`` degrees and ``1.0`` represents 360 degrees (both clockwise,
  488. starting at the top). Default value is ``(0.0, 1.0)``, i.e. pick a
  489. random angle per image.
  490. * If a number, exactly that value will always be used.
  491. * If a tuple ``(a, b)``, a random value will be sampled from the
  492. interval ``[a, b]`` will be sampled per image.
  493. * If a list, then a random value will be sampled from that list
  494. per image.
  495. * If a ``StochasticParameter``, a value will be sampled from the
  496. parameter per image.
  497. 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
  498. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  499. name : None or str, optional
  500. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  501. 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
  502. Old name for parameter `seed`.
  503. Its usage will not yet cause a deprecation warning,
  504. but it is still recommended to use `seed` now.
  505. Outdated since 0.4.0.
  506. deterministic : bool, optional
  507. Deprecated since 0.4.0.
  508. See method ``to_deterministic()`` for an alternative and for
  509. details about what the "deterministic mode" actually does.
  510. Examples
  511. --------
  512. >>> import imgaug.augmenters as iaa
  513. >>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=0)
  514. Turn input images into edge images in which edges are detected from
  515. the top side of the image (i.e. the top sides of horizontal edges are
  516. part of the edge image, while vertical edges are ignored).
  517. >>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=90/360)
  518. Same as before, but edges are detected from the right. Horizontal edges
  519. are now ignored.
  520. >>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=(0.0, 1.0))
  521. Same as before, but edges are detected from a random angle sampled
  522. uniformly from the interval ``[0deg, 360deg]``.
  523. >>> aug = iaa.DirectedEdgeDetect(alpha=(0.0, 0.3), direction=0)
  524. Similar to the previous examples, but here the edge image is alpha-blended
  525. with the input image. The result is a mixture between the edge image and
  526. the input image. The blending factor is randomly sampled between ``0%``
  527. and ``30%``.
  528. """
  529. def __init__(self, alpha=(0.0, 0.75), direction=(0.0, 1.0),
  530. seed=None, name=None,
  531. random_state="deprecated", deterministic="deprecated"):
  532. alpha_param = iap.handle_continuous_param(
  533. alpha, "alpha",
  534. value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
  535. direction_param = iap.handle_continuous_param(
  536. direction, "direction",
  537. value_range=None, tuple_to_uniform=True, list_to_choice=True)
  538. matrix_gen = _DirectedEdgeDetectMatrixGenerator(alpha_param,
  539. direction_param)
  540. super(DirectedEdgeDetect, self).__init__(
  541. matrix=matrix_gen,
  542. seed=seed, name=name,
  543. random_state=random_state, deterministic=deterministic)
  544. class _DirectedEdgeDetectMatrixGenerator(object):
  545. def __init__(self, alpha, direction):
  546. self.alpha = alpha
  547. self.direction = direction
  548. def __call__(self, _image, nb_channels, random_state):
  549. alpha_sample = self.alpha.draw_sample(random_state=random_state)
  550. assert 0 <= alpha_sample <= 1.0, (
  551. "Expected 'alpha' to be in the interval [0.0, 1.0], "
  552. "got %.4f." % (alpha_sample,))
  553. direction_sample = self.direction.draw_sample(random_state=random_state)
  554. deg = int(direction_sample * 360) % 360
  555. rad = np.deg2rad(deg)
  556. x = np.cos(rad - 0.5*np.pi)
  557. y = np.sin(rad - 0.5*np.pi)
  558. direction_vector = np.array([x, y])
  559. matrix_effect = np.array([
  560. [0, 0, 0],
  561. [0, 0, 0],
  562. [0, 0, 0]
  563. ], dtype=np.float32)
  564. for x, y in itertools.product([-1, 0, 1], [-1, 0, 1]):
  565. if (x, y) != (0, 0):
  566. cell_vector = np.array([x, y])
  567. distance_deg = np.rad2deg(
  568. ia.angle_between_vectors(cell_vector,
  569. direction_vector))
  570. distance = distance_deg / 180
  571. similarity = (1 - distance)**4
  572. matrix_effect[y+1, x+1] = similarity
  573. matrix_effect = matrix_effect / np.sum(matrix_effect)
  574. matrix_effect = matrix_effect * (-1)
  575. matrix_effect[1, 1] = 1
  576. matrix_nochange = np.array([
  577. [0, 0, 0],
  578. [0, 1, 0],
  579. [0, 0, 0]
  580. ], dtype=np.float32)
  581. matrix = (
  582. (1-alpha_sample) * matrix_nochange
  583. + alpha_sample * matrix_effect
  584. )
  585. return [matrix] * nb_channels