| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- import numpy as np
- from ..feature.util import (
- FeatureDetector,
- DescriptorExtractor,
- _mask_border_keypoints,
- _prepare_grayscale_input_2D,
- )
- from .corner import corner_fast, corner_orientations, corner_peaks, corner_harris
- from ..transform import pyramid_gaussian
- from .._shared.utils import check_nD
- from .._shared.compat import NP_COPY_IF_NEEDED
- from .orb_cy import _orb_loop
- OFAST_MASK = np.zeros((31, 31))
- OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3]
- for i in range(-15, 16):
- for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1):
- OFAST_MASK[15 + j, 15 + i] = 1
- class ORB(FeatureDetector, DescriptorExtractor):
- """Oriented FAST and rotated BRIEF feature detector and binary descriptor
- extractor.
- Parameters
- ----------
- n_keypoints : int, optional
- Number of keypoints to be returned. The function will return the best
- `n_keypoints` according to the Harris corner response if more than
- `n_keypoints` are detected. If not, then all the detected keypoints
- are returned.
- fast_n : int, optional
- The `n` parameter in `skimage.feature.corner_fast`. Minimum number of
- consecutive pixels out of 16 pixels on the circle that should all be
- either brighter or darker w.r.t test-pixel. A point c on the circle is
- darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if
- ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner
- detector.
- fast_threshold : float, optional
- The ``threshold`` parameter in ``feature.corner_fast``. Threshold used
- to decide whether the pixels on the circle are brighter, darker or
- similar w.r.t. the test pixel. Decrease the threshold when more
- corners are desired and vice-versa.
- harris_k : float, optional
- The `k` parameter in `skimage.feature.corner_harris`. Sensitivity
- factor to separate corners from edges, typically in range ``[0, 0.2]``.
- Small values of `k` result in detection of sharp corners.
- downscale : float, optional
- Downscale factor for the image pyramid. Default value 1.2 is chosen so
- that there are more dense scales which enable robust scale invariance
- for a subsequent feature description.
- n_scales : int, optional
- Maximum number of scales from the bottom of the image pyramid to
- extract the features from.
- Attributes
- ----------
- keypoints : (N, 2) array
- Keypoint coordinates as ``(row, col)``.
- scales : (N,) array
- Corresponding scales.
- orientations : (N,) array
- Corresponding orientations in radians.
- responses : (N,) array
- Corresponding Harris corner responses.
- descriptors : (Q, `descriptor_size`) array of dtype bool
- 2D array of binary descriptors of size `descriptor_size` for Q
- keypoints after filtering out border keypoints with value at an
- index ``(i, j)`` either being ``True`` or ``False`` representing
- the outcome of the intensity comparison for i-th keypoint on j-th
- decision pixel-pair. It is ``Q == np.sum(mask)``.
- References
- ----------
- .. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
- "ORB: An efficient alternative to SIFT and SURF"
- http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
- Examples
- --------
- >>> from skimage.feature import ORB, match_descriptors
- >>> img1 = np.zeros((100, 100))
- >>> img2 = np.zeros_like(img1)
- >>> rng = np.random.default_rng(19481137) # do not copy this value
- >>> square = rng.random((20, 20))
- >>> img1[40:60, 40:60] = square
- >>> img2[53:73, 53:73] = square
- >>> detector_extractor1 = ORB(n_keypoints=5)
- >>> detector_extractor2 = ORB(n_keypoints=5)
- >>> detector_extractor1.detect_and_extract(img1)
- >>> detector_extractor2.detect_and_extract(img2)
- >>> matches = match_descriptors(detector_extractor1.descriptors,
- ... detector_extractor2.descriptors)
- >>> matches
- array([[0, 0],
- [1, 1],
- [2, 2],
- [3, 4],
- [4, 3]])
- >>> detector_extractor1.keypoints[matches[:, 0]]
- array([[59. , 59. ],
- [40. , 40. ],
- [57. , 40. ],
- [46. , 58. ],
- [58.8, 58.8]])
- >>> detector_extractor2.keypoints[matches[:, 1]]
- array([[72., 72.],
- [53., 53.],
- [70., 53.],
- [59., 71.],
- [72., 72.]])
- """
- def __init__(
- self,
- downscale=1.2,
- n_scales=8,
- n_keypoints=500,
- fast_n=9,
- fast_threshold=0.08,
- harris_k=0.04,
- ):
- self.downscale = downscale
- self.n_scales = n_scales
- self.n_keypoints = n_keypoints
- self.fast_n = fast_n
- self.fast_threshold = fast_threshold
- self.harris_k = harris_k
- self.keypoints = None
- self.scales = None
- self.responses = None
- self.orientations = None
- self.descriptors = None
- def _build_pyramid(self, image):
- image = _prepare_grayscale_input_2D(image)
- return list(
- pyramid_gaussian(
- image, self.n_scales - 1, self.downscale, channel_axis=None
- )
- )
- def _detect_octave(self, octave_image):
- dtype = octave_image.dtype
- # Extract keypoints for current octave
- fast_response = corner_fast(octave_image, self.fast_n, self.fast_threshold)
- keypoints = corner_peaks(fast_response, min_distance=1)
- if len(keypoints) == 0:
- return (
- np.zeros((0, 2), dtype=dtype),
- np.zeros((0,), dtype=dtype),
- np.zeros((0,), dtype=dtype),
- )
- mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=16)
- keypoints = keypoints[mask]
- orientations = corner_orientations(octave_image, keypoints, OFAST_MASK)
- harris_response = corner_harris(octave_image, method='k', k=self.harris_k)
- responses = harris_response[keypoints[:, 0], keypoints[:, 1]]
- return keypoints, orientations, responses
- def detect(self, image):
- """Detect oriented FAST keypoints along with the corresponding scale.
- Parameters
- ----------
- image : 2D array
- Input image.
- """
- check_nD(image, 2)
- pyramid = self._build_pyramid(image)
- keypoints_list = []
- orientations_list = []
- scales_list = []
- responses_list = []
- for octave in range(len(pyramid)):
- octave_image = np.ascontiguousarray(pyramid[octave])
- if np.squeeze(octave_image).ndim < 2:
- # No further keypoints can be detected if the image is not really 2d
- break
- keypoints, orientations, responses = self._detect_octave(octave_image)
- keypoints_list.append(keypoints * self.downscale**octave)
- orientations_list.append(orientations)
- scales_list.append(
- np.full(
- keypoints.shape[0],
- self.downscale**octave,
- dtype=octave_image.dtype,
- )
- )
- responses_list.append(responses)
- keypoints = np.vstack(keypoints_list)
- orientations = np.hstack(orientations_list)
- scales = np.hstack(scales_list)
- responses = np.hstack(responses_list)
- if keypoints.shape[0] < self.n_keypoints:
- self.keypoints = keypoints
- self.scales = scales
- self.orientations = orientations
- self.responses = responses
- else:
- # Choose best n_keypoints according to Harris corner response
- best_indices = responses.argsort()[::-1][: self.n_keypoints]
- self.keypoints = keypoints[best_indices]
- self.scales = scales[best_indices]
- self.orientations = orientations[best_indices]
- self.responses = responses[best_indices]
- def _extract_octave(self, octave_image, keypoints, orientations):
- mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=20)
- keypoints = np.array(
- keypoints[mask], dtype=np.intp, order='C', copy=NP_COPY_IF_NEEDED
- )
- orientations = np.array(orientations[mask], order='C', copy=False)
- descriptors = _orb_loop(octave_image, keypoints, orientations)
- return descriptors, mask
- def extract(self, image, keypoints, scales, orientations):
- """Extract rBRIEF binary descriptors for given keypoints in image.
- Note that the keypoints must be extracted using the same `downscale`
- and `n_scales` parameters. Additionally, if you want to extract both
- keypoints and descriptors you should use the faster
- `detect_and_extract`.
- Parameters
- ----------
- image : 2D array
- Input image.
- keypoints : (N, 2) array
- Keypoint coordinates as ``(row, col)``.
- scales : (N,) array
- Corresponding scales.
- orientations : (N,) array
- Corresponding orientations in radians.
- """
- check_nD(image, 2)
- pyramid = self._build_pyramid(image)
- descriptors_list = []
- mask_list = []
- # Determine octaves from scales
- octaves = (np.log(scales) / np.log(self.downscale)).astype(np.intp)
- for octave in range(len(pyramid)):
- # Mask for all keypoints in current octave
- octave_mask = octaves == octave
- if np.sum(octave_mask) > 0:
- octave_image = np.ascontiguousarray(pyramid[octave])
- octave_keypoints = keypoints[octave_mask]
- octave_keypoints /= self.downscale**octave
- octave_orientations = orientations[octave_mask]
- descriptors, mask = self._extract_octave(
- octave_image, octave_keypoints, octave_orientations
- )
- descriptors_list.append(descriptors)
- mask_list.append(mask)
- self.descriptors = np.vstack(descriptors_list).view(bool)
- self.mask_ = np.hstack(mask_list)
- def detect_and_extract(self, image):
- """Detect oriented FAST keypoints and extract rBRIEF descriptors.
- Note that this is faster than first calling `detect` and then
- `extract`.
- Parameters
- ----------
- image : 2D array
- Input image.
- """
- check_nD(image, 2)
- pyramid = self._build_pyramid(image)
- keypoints_list = []
- responses_list = []
- scales_list = []
- orientations_list = []
- descriptors_list = []
- for octave in range(len(pyramid)):
- octave_image = np.ascontiguousarray(pyramid[octave])
- if np.squeeze(octave_image).ndim < 2:
- # No further keypoints can be detected if the image is not really 2d
- break
- keypoints, orientations, responses = self._detect_octave(octave_image)
- if len(keypoints) == 0:
- keypoints_list.append(keypoints)
- responses_list.append(responses)
- descriptors_list.append(np.zeros((0, 256), dtype=bool))
- continue
- descriptors, mask = self._extract_octave(
- octave_image, keypoints, orientations
- )
- scaled_keypoints = keypoints[mask] * self.downscale**octave
- keypoints_list.append(scaled_keypoints)
- responses_list.append(responses[mask])
- orientations_list.append(orientations[mask])
- scales_list.append(
- self.downscale**octave
- * np.ones(scaled_keypoints.shape[0], dtype=np.intp)
- )
- descriptors_list.append(descriptors)
- if len(scales_list) == 0:
- raise RuntimeError(
- "ORB found no features. Try passing in an image containing "
- "greater intensity contrasts between adjacent pixels."
- )
- keypoints = np.vstack(keypoints_list)
- responses = np.hstack(responses_list)
- scales = np.hstack(scales_list)
- orientations = np.hstack(orientations_list)
- descriptors = np.vstack(descriptors_list).view(bool)
- if keypoints.shape[0] < self.n_keypoints:
- self.keypoints = keypoints
- self.scales = scales
- self.orientations = orientations
- self.responses = responses
- self.descriptors = descriptors
- else:
- # Choose best n_keypoints according to Harris corner response
- best_indices = responses.argsort()[::-1][: self.n_keypoints]
- self.keypoints = keypoints[best_indices]
- self.scales = scales[best_indices]
- self.orientations = orientations[best_indices]
- self.responses = responses[best_indices]
- self.descriptors = descriptors[best_indices]
|