from itertools import chain from operator import add import numpy as np from ._haar import haar_like_feature_coord_wrapper from ._haar import haar_like_feature_wrapper from ..color import gray2rgb from ..draw import rectangle from .._shared.utils import check_random_state from ..util import img_as_float FEATURE_TYPE = ('type-2-x', 'type-2-y', 'type-3-x', 'type-3-y', 'type-4') def _validate_feature_type(feature_type): """Transform feature type to an iterable and check that it exists.""" if feature_type is None: feature_type_ = FEATURE_TYPE else: if isinstance(feature_type, str): feature_type_ = [feature_type] else: feature_type_ = feature_type for feat_t in feature_type_: if feat_t not in FEATURE_TYPE: raise ValueError( 'The given feature type is unknown. Got {} instead of one' ' of {}.'.format(feat_t, FEATURE_TYPE)) return feature_type_ def haar_like_feature_coord(width, height, feature_type=None): """Compute the coordinates of Haar-like features. Parameters ---------- width : int Width of the detection window. height : int Height of the detection window. feature_type : str or list of str or None, optional The type of feature to consider: - 'type-2-x': 2 rectangles varying along the x axis; - 'type-2-y': 2 rectangles varying along the y axis; - 'type-3-x': 3 rectangles varying along the x axis; - 'type-3-y': 3 rectangles varying along the y axis; - 'type-4': 4 rectangles varying along x and y axis. By default all features are extracted. Returns ------- feature_coord : (n_features, n_rectangles, 2, 2), ndarray of list of \ tuple coord Coordinates of the rectangles for each feature. feature_type : (n_features,), ndarray of str The corresponding type for each feature. Examples -------- >>> import numpy as np >>> from skimage.transform import integral_image >>> from skimage.feature import haar_like_feature_coord >>> feat_coord, feat_type = haar_like_feature_coord(2, 2, 'type-4') >>> feat_coord # doctest: +SKIP array([ list([[(0, 0), (0, 0)], [(0, 1), (0, 1)], [(1, 1), (1, 1)], [(1, 0), (1, 0)]])], dtype=object) >>> feat_type array(['type-4'], dtype=object) """ feature_type_ = _validate_feature_type(feature_type) feat_coord, feat_type = zip(*[haar_like_feature_coord_wrapper(width, height, feat_t) for feat_t in feature_type_]) return np.concatenate(feat_coord), np.hstack(feat_type) def haar_like_feature(int_image, r, c, width, height, feature_type=None, feature_coord=None): """Compute the Haar-like features for a region of interest (ROI) of an integral image. Haar-like features have been successfully used for image classification and object detection [1]_. It has been used for real-time face detection algorithm proposed in [2]_. Parameters ---------- int_image : (M, N) ndarray Integral image for which the features need to be computed. r : int Row-coordinate of top left corner of the detection window. c : int Column-coordinate of top left corner of the detection window. width : int Width of the detection window. height : int Height of the detection window. feature_type : str or list of str or None, optional The type of feature to consider: - 'type-2-x': 2 rectangles varying along the x axis; - 'type-2-y': 2 rectangles varying along the y axis; - 'type-3-x': 3 rectangles varying along the x axis; - 'type-3-y': 3 rectangles varying along the y axis; - 'type-4': 4 rectangles varying along x and y axis. By default all features are extracted. If using with `feature_coord`, it should correspond to the feature type of each associated coordinate feature. feature_coord : ndarray of list of tuples or None, optional The array of coordinates to be extracted. This is useful when you want to recompute only a subset of features. In this case `feature_type` needs to be an array containing the type of each feature, as returned by :func:`haar_like_feature_coord`. By default, all coordinates are computed. Returns ------- haar_features : (n_features,) ndarray of int or float Resulting Haar-like features. Each value is equal to the subtraction of sums of the positive and negative rectangles. The data type depends of the data type of `int_image`: `int` when the data type of `int_image` is `uint` or `int` and `float` when the data type of `int_image` is `float`. Notes ----- When extracting those features in parallel, be aware that the choice of the backend (i.e. multiprocessing vs threading) will have an impact on the performance. The rule of thumb is as follows: use multiprocessing when extracting features for all possible ROI in an image; use threading when extracting the feature at specific location for a limited number of ROIs. Refer to the example :ref:`sphx_glr_auto_examples_applications_plot_haar_extraction_selection_classification.py` for more insights. Examples -------- >>> import numpy as np >>> from skimage.transform import integral_image >>> from skimage.feature import haar_like_feature >>> img = np.ones((5, 5), dtype=np.uint8) >>> img_ii = integral_image(img) >>> feature = haar_like_feature(img_ii, 0, 0, 5, 5, 'type-3-x') >>> feature array([-1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -1, -2, -3, -1, -2, -3, -1, -2, -1, -2, -1, -2, -1, -1, -1]) You can compute the feature for some pre-computed coordinates. >>> from skimage.feature import haar_like_feature_coord >>> feature_coord, feature_type = zip( ... *[haar_like_feature_coord(5, 5, feat_t) ... for feat_t in ('type-2-x', 'type-3-x')]) >>> # only select one feature over two >>> feature_coord = np.concatenate([x[::2] for x in feature_coord]) >>> feature_type = np.concatenate([x[::2] for x in feature_type]) >>> feature = haar_like_feature(img_ii, 0, 0, 5, 5, ... feature_type=feature_type, ... feature_coord=feature_coord) >>> feature array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -3, -1, -3, -1, -3, -1, -3, -1, -3, -1, -3, -1, -3, -2, -1, -3, -2, -2, -2, -1]) References ---------- .. [1] https://en.wikipedia.org/wiki/Haar-like_feature .. [2] Oren, M., Papageorgiou, C., Sinha, P., Osuna, E., & Poggio, T. (1997, June). Pedestrian detection using wavelet templates. In Computer Vision and Pattern Recognition, 1997. Proceedings., 1997 IEEE Computer Society Conference on (pp. 193-199). IEEE. http://tinyurl.com/y6ulxfta :DOI:`10.1109/CVPR.1997.609319` .. [3] Viola, Paul, and Michael J. Jones. "Robust real-time face detection." International journal of computer vision 57.2 (2004): 137-154. https://www.merl.com/publications/docs/TR2004-043.pdf :DOI:`10.1109/CVPR.2001.990517` """ if feature_coord is None: feature_type_ = _validate_feature_type(feature_type) return np.hstack(list(chain.from_iterable( haar_like_feature_wrapper(int_image, r, c, width, height, feat_t, feature_coord) for feat_t in feature_type_))) else: if feature_coord.shape[0] != feature_type.shape[0]: raise ValueError("Inconsistent size between feature coordinates" "and feature types.") mask_feature = [feature_type == feat_t for feat_t in FEATURE_TYPE] haar_feature_idx, haar_feature = zip( *[(np.flatnonzero(mask), haar_like_feature_wrapper(int_image, r, c, width, height, feat_t, feature_coord[mask])) for mask, feat_t in zip(mask_feature, FEATURE_TYPE) if np.count_nonzero(mask)]) haar_feature_idx = np.concatenate(haar_feature_idx) haar_feature = np.concatenate(haar_feature) haar_feature[haar_feature_idx] = haar_feature.copy() return haar_feature def draw_haar_like_feature(image, r, c, width, height, feature_coord, color_positive_block=(1., 0., 0.), color_negative_block=(0., 1., 0.), alpha=0.5, max_n_features=None, random_state=None): """Visualization of Haar-like features. Parameters ---------- image : (M, N) ndarray The region of an integral image for which the features need to be computed. r : int Row-coordinate of top left corner of the detection window. c : int Column-coordinate of top left corner of the detection window. width : int Width of the detection window. height : int Height of the detection window. feature_coord : ndarray of list of tuples or None, optional The array of coordinates to be extracted. This is useful when you want to recompute only a subset of features. In this case `feature_type` needs to be an array containing the type of each feature, as returned by :func:`haar_like_feature_coord`. By default, all coordinates are computed. color_positive_rectangle : tuple of 3 floats Floats specifying the color for the positive block. Corresponding values define (R, G, B) values. Default value is red (1, 0, 0). color_negative_block : tuple of 3 floats Floats specifying the color for the negative block Corresponding values define (R, G, B) values. Default value is blue (0, 1, 0). alpha : float Value in the range [0, 1] that specifies opacity of visualization. 1 - fully transparent, 0 - opaque. max_n_features : int, default=None The maximum number of features to be returned. By default, all features are returned. random_state : int, RandomState instance or None, optional If int, random_state is the seed used by the random number generator; If RandomState instance, random_state is the random number generator; If None, the random number generator is the RandomState instance used by `np.random`. The random state is used when generating a set of features smaller than the total number of available features. Returns ------- features : (M, N), ndarray An image in which the different features will be added. Examples -------- >>> import numpy as np >>> from skimage.feature import haar_like_feature_coord >>> from skimage.feature import draw_haar_like_feature >>> feature_coord, _ = haar_like_feature_coord(2, 2, 'type-4') >>> image = draw_haar_like_feature(np.zeros((2, 2)), ... 0, 0, 2, 2, ... feature_coord, ... max_n_features=1) >>> image array([[[0. , 0.5, 0. ], [0.5, 0. , 0. ]], [[0.5, 0. , 0. ], [0. , 0.5, 0. ]]]) """ random_state = check_random_state(random_state) color_positive_block = np.asarray(color_positive_block, dtype=np.float64) color_negative_block = np.asarray(color_negative_block, dtype=np.float64) if max_n_features is None: feature_coord_ = feature_coord else: feature_coord_ = random_state.choice(feature_coord, size=max_n_features, replace=False) output = np.copy(image) if len(image.shape) < 3: output = gray2rgb(image) output = img_as_float(output) for coord in feature_coord_: for idx_rect, rect in enumerate(coord): coord_start, coord_end = rect coord_start = tuple(map(add, coord_start, [r, c])) coord_end = tuple(map(add, coord_end, [r, c])) rr, cc = rectangle(coord_start, coord_end) if ((idx_rect + 1) % 2) == 0: new_value = ((1 - alpha) * output[rr, cc] + alpha * color_positive_block) else: new_value = ((1 - alpha) * output[rr, cc] + alpha * color_negative_block) output[rr, cc] = new_value return output