haar.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. from itertools import chain
  2. from operator import add
  3. import numpy as np
  4. from ._haar import haar_like_feature_coord_wrapper
  5. from ._haar import haar_like_feature_wrapper
  6. from ..color import gray2rgb
  7. from ..draw import rectangle
  8. from ..util import img_as_float
  9. FEATURE_TYPE = ('type-2-x', 'type-2-y', 'type-3-x', 'type-3-y', 'type-4')
  10. def _validate_feature_type(feature_type):
  11. """Transform feature type to an iterable and check that it exists."""
  12. if feature_type is None:
  13. feature_type_ = FEATURE_TYPE
  14. else:
  15. if isinstance(feature_type, str):
  16. feature_type_ = [feature_type]
  17. else:
  18. feature_type_ = feature_type
  19. for feat_t in feature_type_:
  20. if feat_t not in FEATURE_TYPE:
  21. raise ValueError(
  22. f'The given feature type is unknown. Got {feat_t} instead of one '
  23. f'of {FEATURE_TYPE}.'
  24. )
  25. return feature_type_
  26. def haar_like_feature_coord(width, height, feature_type=None):
  27. """Compute the coordinates of Haar-like features.
  28. Parameters
  29. ----------
  30. width : int
  31. Width of the detection window.
  32. height : int
  33. Height of the detection window.
  34. feature_type : str or list of str or None, optional
  35. The type of feature to consider:
  36. - 'type-2-x': 2 rectangles varying along the x axis;
  37. - 'type-2-y': 2 rectangles varying along the y axis;
  38. - 'type-3-x': 3 rectangles varying along the x axis;
  39. - 'type-3-y': 3 rectangles varying along the y axis;
  40. - 'type-4': 4 rectangles varying along x and y axis.
  41. By default all features are extracted.
  42. Returns
  43. -------
  44. feature_coord : (n_features, n_rectangles, 2, 2), ndarray of list of \
  45. tuple coord
  46. Coordinates of the rectangles for each feature.
  47. feature_type : (n_features,), ndarray of str
  48. The corresponding type for each feature.
  49. Examples
  50. --------
  51. >>> import numpy as np
  52. >>> from skimage.transform import integral_image
  53. >>> from skimage.feature import haar_like_feature_coord
  54. >>> feat_coord, feat_type = haar_like_feature_coord(2, 2, 'type-4')
  55. >>> feat_coord # doctest: +SKIP
  56. array([ list([[(0, 0), (0, 0)], [(0, 1), (0, 1)],
  57. [(1, 1), (1, 1)], [(1, 0), (1, 0)]])], dtype=object)
  58. >>> feat_type
  59. array(['type-4'], dtype=object)
  60. """
  61. feature_type_ = _validate_feature_type(feature_type)
  62. feat_coord, feat_type = zip(
  63. *[
  64. haar_like_feature_coord_wrapper(width, height, feat_t)
  65. for feat_t in feature_type_
  66. ]
  67. )
  68. return np.concatenate(feat_coord), np.hstack(feat_type)
  69. def haar_like_feature(
  70. int_image, r, c, width, height, feature_type=None, feature_coord=None
  71. ):
  72. """Compute the Haar-like features for a region of interest (ROI) of an
  73. integral image.
  74. Haar-like features have been successfully used for image classification and
  75. object detection [1]_. It has been used for real-time face detection
  76. algorithm proposed in [2]_.
  77. Parameters
  78. ----------
  79. int_image : (M, N) ndarray
  80. Integral image for which the features need to be computed.
  81. r : int
  82. Row-coordinate of top left corner of the detection window.
  83. c : int
  84. Column-coordinate of top left corner of the detection window.
  85. width : int
  86. Width of the detection window.
  87. height : int
  88. Height of the detection window.
  89. feature_type : str or list of str or None, optional
  90. The type of feature to consider:
  91. - 'type-2-x': 2 rectangles varying along the x axis;
  92. - 'type-2-y': 2 rectangles varying along the y axis;
  93. - 'type-3-x': 3 rectangles varying along the x axis;
  94. - 'type-3-y': 3 rectangles varying along the y axis;
  95. - 'type-4': 4 rectangles varying along x and y axis.
  96. By default all features are extracted.
  97. If using with `feature_coord`, it should correspond to the feature
  98. type of each associated coordinate feature.
  99. feature_coord : ndarray of list of tuples or None, optional
  100. The array of coordinates to be extracted. This is useful when you want
  101. to recompute only a subset of features. In this case `feature_type`
  102. needs to be an array containing the type of each feature, as returned
  103. by :func:`haar_like_feature_coord`. By default, all coordinates are
  104. computed.
  105. Returns
  106. -------
  107. haar_features : (n_features,) ndarray of int or float
  108. Resulting Haar-like features. Each value is equal to the subtraction of
  109. sums of the positive and negative rectangles. The data type depends of
  110. the data type of `int_image`: `int` when the data type of `int_image`
  111. is `uint` or `int` and `float` when the data type of `int_image` is
  112. `float`.
  113. Notes
  114. -----
  115. When extracting those features in parallel, be aware that the choice of the
  116. backend (i.e. multiprocessing vs threading) will have an impact on the
  117. performance. The rule of thumb is as follows: use multiprocessing when
  118. extracting features for all possible ROI in an image; use threading when
  119. extracting the feature at specific location for a limited number of ROIs.
  120. Refer to the example
  121. :ref:`sphx_glr_auto_examples_applications_plot_haar_extraction_selection_classification.py`
  122. for more insights.
  123. Examples
  124. --------
  125. >>> import numpy as np
  126. >>> from skimage.transform import integral_image
  127. >>> from skimage.feature import haar_like_feature
  128. >>> img = np.ones((5, 5), dtype=np.uint8)
  129. >>> img_ii = integral_image(img)
  130. >>> feature = haar_like_feature(img_ii, 0, 0, 5, 5, 'type-3-x')
  131. >>> feature
  132. array([-1, -2, -3, -4, -5, -1, -2, -3, -4, -5, -1, -2, -3, -4, -5, -1, -2,
  133. -3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -1, -2, -3, -1,
  134. -2, -3, -1, -2, -1, -2, -1, -2, -1, -1, -1])
  135. You can compute the feature for some pre-computed coordinates.
  136. >>> from skimage.feature import haar_like_feature_coord
  137. >>> feature_coord, feature_type = zip(
  138. ... *[haar_like_feature_coord(5, 5, feat_t)
  139. ... for feat_t in ('type-2-x', 'type-3-x')])
  140. >>> # only select one feature over two
  141. >>> feature_coord = np.concatenate([x[::2] for x in feature_coord])
  142. >>> feature_type = np.concatenate([x[::2] for x in feature_type])
  143. >>> feature = haar_like_feature(img_ii, 0, 0, 5, 5,
  144. ... feature_type=feature_type,
  145. ... feature_coord=feature_coord)
  146. >>> feature
  147. array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  148. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  149. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -3, -5, -2, -4, -1,
  150. -3, -5, -2, -4, -2, -4, -2, -4, -2, -1, -3, -2, -1, -1, -1, -1, -1])
  151. References
  152. ----------
  153. .. [1] https://en.wikipedia.org/wiki/Haar-like_feature
  154. .. [2] Oren, M., Papageorgiou, C., Sinha, P., Osuna, E., & Poggio, T.
  155. (1997, June). Pedestrian detection using wavelet templates.
  156. In Computer Vision and Pattern Recognition, 1997. Proceedings.,
  157. 1997 IEEE Computer Society Conference on (pp. 193-199). IEEE.
  158. http://tinyurl.com/y6ulxfta
  159. :DOI:`10.1109/CVPR.1997.609319`
  160. .. [3] Viola, Paul, and Michael J. Jones. "Robust real-time face
  161. detection." International journal of computer vision 57.2
  162. (2004): 137-154.
  163. https://www.merl.com/publications/docs/TR2004-043.pdf
  164. :DOI:`10.1109/CVPR.2001.990517`
  165. """
  166. if feature_coord is None:
  167. feature_type_ = _validate_feature_type(feature_type)
  168. return np.hstack(
  169. list(
  170. chain.from_iterable(
  171. haar_like_feature_wrapper(
  172. int_image, r, c, width, height, feat_t, feature_coord
  173. )
  174. for feat_t in feature_type_
  175. )
  176. )
  177. )
  178. else:
  179. if feature_coord.shape[0] != feature_type.shape[0]:
  180. raise ValueError(
  181. "Inconsistent size between feature coordinates" "and feature types."
  182. )
  183. mask_feature = [feature_type == feat_t for feat_t in FEATURE_TYPE]
  184. haar_feature_idx, haar_feature = zip(
  185. *[
  186. (
  187. np.flatnonzero(mask),
  188. haar_like_feature_wrapper(
  189. int_image, r, c, width, height, feat_t, feature_coord[mask]
  190. ),
  191. )
  192. for mask, feat_t in zip(mask_feature, FEATURE_TYPE)
  193. if np.count_nonzero(mask)
  194. ]
  195. )
  196. haar_feature_idx = np.concatenate(haar_feature_idx)
  197. haar_feature = np.concatenate(haar_feature)
  198. haar_feature[haar_feature_idx] = haar_feature.copy()
  199. return haar_feature
  200. def draw_haar_like_feature(
  201. image,
  202. r,
  203. c,
  204. width,
  205. height,
  206. feature_coord,
  207. color_positive_block=(1.0, 0.0, 0.0),
  208. color_negative_block=(0.0, 1.0, 0.0),
  209. alpha=0.5,
  210. max_n_features=None,
  211. rng=None,
  212. ):
  213. """Visualization of Haar-like features.
  214. Parameters
  215. ----------
  216. image : (M, N) ndarray
  217. The region of an integral image for which the features need to be
  218. computed.
  219. r : int
  220. Row-coordinate of top left corner of the detection window.
  221. c : int
  222. Column-coordinate of top left corner of the detection window.
  223. width : int
  224. Width of the detection window.
  225. height : int
  226. Height of the detection window.
  227. feature_coord : ndarray of list of tuples or None, optional
  228. The array of coordinates to be extracted. This is useful when you want
  229. to recompute only a subset of features. In this case `feature_type`
  230. needs to be an array containing the type of each feature, as returned
  231. by :func:`haar_like_feature_coord`. By default, all coordinates are
  232. computed.
  233. color_positive_block : tuple of 3 floats
  234. Floats specifying the color for the positive block. Corresponding
  235. values define (R, G, B) values. Default value is red (1, 0, 0).
  236. color_negative_block : tuple of 3 floats
  237. Floats specifying the color for the negative block Corresponding values
  238. define (R, G, B) values. Default value is blue (0, 1, 0).
  239. alpha : float
  240. Value in the range [0, 1] that specifies opacity of visualization. 1 -
  241. fully transparent, 0 - opaque.
  242. max_n_features : int, default=None
  243. The maximum number of features to be returned.
  244. By default, all features are returned.
  245. rng : {`numpy.random.Generator`, int}, optional
  246. Pseudo-random number generator.
  247. By default, a PCG64 generator is used (see :func:`numpy.random.default_rng`).
  248. If `rng` is an int, it is used to seed the generator.
  249. The rng is used when generating a set of features smaller than
  250. the total number of available features.
  251. Returns
  252. -------
  253. features : (M, N), ndarray
  254. An image in which the different features will be added.
  255. Examples
  256. --------
  257. >>> import numpy as np
  258. >>> from skimage.feature import haar_like_feature_coord
  259. >>> from skimage.feature import draw_haar_like_feature
  260. >>> feature_coord, _ = haar_like_feature_coord(2, 2, 'type-4')
  261. >>> image = draw_haar_like_feature(np.zeros((2, 2)),
  262. ... 0, 0, 2, 2,
  263. ... feature_coord,
  264. ... max_n_features=1)
  265. >>> image
  266. array([[[0. , 0.5, 0. ],
  267. [0.5, 0. , 0. ]],
  268. <BLANKLINE>
  269. [[0.5, 0. , 0. ],
  270. [0. , 0.5, 0. ]]])
  271. """
  272. rng = np.random.default_rng(rng)
  273. color_positive_block = np.asarray(color_positive_block, dtype=np.float64)
  274. color_negative_block = np.asarray(color_negative_block, dtype=np.float64)
  275. if max_n_features is None:
  276. feature_coord_ = feature_coord
  277. else:
  278. feature_coord_ = rng.choice(feature_coord, size=max_n_features, replace=False)
  279. output = np.copy(image)
  280. if len(image.shape) < 3:
  281. output = gray2rgb(image)
  282. output = img_as_float(output)
  283. for coord in feature_coord_:
  284. for idx_rect, rect in enumerate(coord):
  285. coord_start, coord_end = rect
  286. coord_start = tuple(map(add, coord_start, [r, c]))
  287. coord_end = tuple(map(add, coord_end, [r, c]))
  288. rr, cc = rectangle(coord_start, coord_end)
  289. if ((idx_rect + 1) % 2) == 0:
  290. new_value = (1 - alpha) * output[rr, cc] + alpha * color_positive_block
  291. else:
  292. new_value = (1 - alpha) * output[rr, cc] + alpha * color_negative_block
  293. output[rr, cc] = new_value
  294. return output