orb.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import numpy as np
  2. from ..feature.util import (
  3. FeatureDetector,
  4. DescriptorExtractor,
  5. _mask_border_keypoints,
  6. _prepare_grayscale_input_2D,
  7. )
  8. from .corner import corner_fast, corner_orientations, corner_peaks, corner_harris
  9. from ..transform import pyramid_gaussian
  10. from .._shared.utils import check_nD
  11. from .._shared.compat import NP_COPY_IF_NEEDED
  12. from .orb_cy import _orb_loop
  13. OFAST_MASK = np.zeros((31, 31))
  14. OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3]
  15. for i in range(-15, 16):
  16. for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1):
  17. OFAST_MASK[15 + j, 15 + i] = 1
  18. class ORB(FeatureDetector, DescriptorExtractor):
  19. """Oriented FAST and rotated BRIEF feature detector and binary descriptor
  20. extractor.
  21. Parameters
  22. ----------
  23. n_keypoints : int, optional
  24. Number of keypoints to be returned. The function will return the best
  25. `n_keypoints` according to the Harris corner response if more than
  26. `n_keypoints` are detected. If not, then all the detected keypoints
  27. are returned.
  28. fast_n : int, optional
  29. The `n` parameter in `skimage.feature.corner_fast`. Minimum number of
  30. consecutive pixels out of 16 pixels on the circle that should all be
  31. either brighter or darker w.r.t test-pixel. A point c on the circle is
  32. darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if
  33. ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner
  34. detector.
  35. fast_threshold : float, optional
  36. The ``threshold`` parameter in ``feature.corner_fast``. Threshold used
  37. to decide whether the pixels on the circle are brighter, darker or
  38. similar w.r.t. the test pixel. Decrease the threshold when more
  39. corners are desired and vice-versa.
  40. harris_k : float, optional
  41. The `k` parameter in `skimage.feature.corner_harris`. Sensitivity
  42. factor to separate corners from edges, typically in range ``[0, 0.2]``.
  43. Small values of `k` result in detection of sharp corners.
  44. downscale : float, optional
  45. Downscale factor for the image pyramid. Default value 1.2 is chosen so
  46. that there are more dense scales which enable robust scale invariance
  47. for a subsequent feature description.
  48. n_scales : int, optional
  49. Maximum number of scales from the bottom of the image pyramid to
  50. extract the features from.
  51. Attributes
  52. ----------
  53. keypoints : (N, 2) array
  54. Keypoint coordinates as ``(row, col)``.
  55. scales : (N,) array
  56. Corresponding scales.
  57. orientations : (N,) array
  58. Corresponding orientations in radians.
  59. responses : (N,) array
  60. Corresponding Harris corner responses.
  61. descriptors : (Q, `descriptor_size`) array of dtype bool
  62. 2D array of binary descriptors of size `descriptor_size` for Q
  63. keypoints after filtering out border keypoints with value at an
  64. index ``(i, j)`` either being ``True`` or ``False`` representing
  65. the outcome of the intensity comparison for i-th keypoint on j-th
  66. decision pixel-pair. It is ``Q == np.sum(mask)``.
  67. References
  68. ----------
  69. .. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
  70. "ORB: An efficient alternative to SIFT and SURF"
  71. http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
  72. Examples
  73. --------
  74. >>> from skimage.feature import ORB, match_descriptors
  75. >>> img1 = np.zeros((100, 100))
  76. >>> img2 = np.zeros_like(img1)
  77. >>> rng = np.random.default_rng(19481137) # do not copy this value
  78. >>> square = rng.random((20, 20))
  79. >>> img1[40:60, 40:60] = square
  80. >>> img2[53:73, 53:73] = square
  81. >>> detector_extractor1 = ORB(n_keypoints=5)
  82. >>> detector_extractor2 = ORB(n_keypoints=5)
  83. >>> detector_extractor1.detect_and_extract(img1)
  84. >>> detector_extractor2.detect_and_extract(img2)
  85. >>> matches = match_descriptors(detector_extractor1.descriptors,
  86. ... detector_extractor2.descriptors)
  87. >>> matches
  88. array([[0, 0],
  89. [1, 1],
  90. [2, 2],
  91. [3, 4],
  92. [4, 3]])
  93. >>> detector_extractor1.keypoints[matches[:, 0]]
  94. array([[59. , 59. ],
  95. [40. , 40. ],
  96. [57. , 40. ],
  97. [46. , 58. ],
  98. [58.8, 58.8]])
  99. >>> detector_extractor2.keypoints[matches[:, 1]]
  100. array([[72., 72.],
  101. [53., 53.],
  102. [70., 53.],
  103. [59., 71.],
  104. [72., 72.]])
  105. """
  106. def __init__(
  107. self,
  108. downscale=1.2,
  109. n_scales=8,
  110. n_keypoints=500,
  111. fast_n=9,
  112. fast_threshold=0.08,
  113. harris_k=0.04,
  114. ):
  115. self.downscale = downscale
  116. self.n_scales = n_scales
  117. self.n_keypoints = n_keypoints
  118. self.fast_n = fast_n
  119. self.fast_threshold = fast_threshold
  120. self.harris_k = harris_k
  121. self.keypoints = None
  122. self.scales = None
  123. self.responses = None
  124. self.orientations = None
  125. self.descriptors = None
  126. def _build_pyramid(self, image):
  127. image = _prepare_grayscale_input_2D(image)
  128. return list(
  129. pyramid_gaussian(
  130. image, self.n_scales - 1, self.downscale, channel_axis=None
  131. )
  132. )
  133. def _detect_octave(self, octave_image):
  134. dtype = octave_image.dtype
  135. # Extract keypoints for current octave
  136. fast_response = corner_fast(octave_image, self.fast_n, self.fast_threshold)
  137. keypoints = corner_peaks(fast_response, min_distance=1)
  138. if len(keypoints) == 0:
  139. return (
  140. np.zeros((0, 2), dtype=dtype),
  141. np.zeros((0,), dtype=dtype),
  142. np.zeros((0,), dtype=dtype),
  143. )
  144. mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=16)
  145. keypoints = keypoints[mask]
  146. orientations = corner_orientations(octave_image, keypoints, OFAST_MASK)
  147. harris_response = corner_harris(octave_image, method='k', k=self.harris_k)
  148. responses = harris_response[keypoints[:, 0], keypoints[:, 1]]
  149. return keypoints, orientations, responses
  150. def detect(self, image):
  151. """Detect oriented FAST keypoints along with the corresponding scale.
  152. Parameters
  153. ----------
  154. image : 2D array
  155. Input image.
  156. """
  157. check_nD(image, 2)
  158. pyramid = self._build_pyramid(image)
  159. keypoints_list = []
  160. orientations_list = []
  161. scales_list = []
  162. responses_list = []
  163. for octave in range(len(pyramid)):
  164. octave_image = np.ascontiguousarray(pyramid[octave])
  165. if np.squeeze(octave_image).ndim < 2:
  166. # No further keypoints can be detected if the image is not really 2d
  167. break
  168. keypoints, orientations, responses = self._detect_octave(octave_image)
  169. keypoints_list.append(keypoints * self.downscale**octave)
  170. orientations_list.append(orientations)
  171. scales_list.append(
  172. np.full(
  173. keypoints.shape[0],
  174. self.downscale**octave,
  175. dtype=octave_image.dtype,
  176. )
  177. )
  178. responses_list.append(responses)
  179. keypoints = np.vstack(keypoints_list)
  180. orientations = np.hstack(orientations_list)
  181. scales = np.hstack(scales_list)
  182. responses = np.hstack(responses_list)
  183. if keypoints.shape[0] < self.n_keypoints:
  184. self.keypoints = keypoints
  185. self.scales = scales
  186. self.orientations = orientations
  187. self.responses = responses
  188. else:
  189. # Choose best n_keypoints according to Harris corner response
  190. best_indices = responses.argsort()[::-1][: self.n_keypoints]
  191. self.keypoints = keypoints[best_indices]
  192. self.scales = scales[best_indices]
  193. self.orientations = orientations[best_indices]
  194. self.responses = responses[best_indices]
  195. def _extract_octave(self, octave_image, keypoints, orientations):
  196. mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=20)
  197. keypoints = np.array(
  198. keypoints[mask], dtype=np.intp, order='C', copy=NP_COPY_IF_NEEDED
  199. )
  200. orientations = np.array(orientations[mask], order='C', copy=False)
  201. descriptors = _orb_loop(octave_image, keypoints, orientations)
  202. return descriptors, mask
  203. def extract(self, image, keypoints, scales, orientations):
  204. """Extract rBRIEF binary descriptors for given keypoints in image.
  205. Note that the keypoints must be extracted using the same `downscale`
  206. and `n_scales` parameters. Additionally, if you want to extract both
  207. keypoints and descriptors you should use the faster
  208. `detect_and_extract`.
  209. Parameters
  210. ----------
  211. image : 2D array
  212. Input image.
  213. keypoints : (N, 2) array
  214. Keypoint coordinates as ``(row, col)``.
  215. scales : (N,) array
  216. Corresponding scales.
  217. orientations : (N,) array
  218. Corresponding orientations in radians.
  219. """
  220. check_nD(image, 2)
  221. pyramid = self._build_pyramid(image)
  222. descriptors_list = []
  223. mask_list = []
  224. # Determine octaves from scales
  225. octaves = (np.log(scales) / np.log(self.downscale)).astype(np.intp)
  226. for octave in range(len(pyramid)):
  227. # Mask for all keypoints in current octave
  228. octave_mask = octaves == octave
  229. if np.sum(octave_mask) > 0:
  230. octave_image = np.ascontiguousarray(pyramid[octave])
  231. octave_keypoints = keypoints[octave_mask]
  232. octave_keypoints /= self.downscale**octave
  233. octave_orientations = orientations[octave_mask]
  234. descriptors, mask = self._extract_octave(
  235. octave_image, octave_keypoints, octave_orientations
  236. )
  237. descriptors_list.append(descriptors)
  238. mask_list.append(mask)
  239. self.descriptors = np.vstack(descriptors_list).view(bool)
  240. self.mask_ = np.hstack(mask_list)
  241. def detect_and_extract(self, image):
  242. """Detect oriented FAST keypoints and extract rBRIEF descriptors.
  243. Note that this is faster than first calling `detect` and then
  244. `extract`.
  245. Parameters
  246. ----------
  247. image : 2D array
  248. Input image.
  249. """
  250. check_nD(image, 2)
  251. pyramid = self._build_pyramid(image)
  252. keypoints_list = []
  253. responses_list = []
  254. scales_list = []
  255. orientations_list = []
  256. descriptors_list = []
  257. for octave in range(len(pyramid)):
  258. octave_image = np.ascontiguousarray(pyramid[octave])
  259. if np.squeeze(octave_image).ndim < 2:
  260. # No further keypoints can be detected if the image is not really 2d
  261. break
  262. keypoints, orientations, responses = self._detect_octave(octave_image)
  263. if len(keypoints) == 0:
  264. keypoints_list.append(keypoints)
  265. responses_list.append(responses)
  266. descriptors_list.append(np.zeros((0, 256), dtype=bool))
  267. continue
  268. descriptors, mask = self._extract_octave(
  269. octave_image, keypoints, orientations
  270. )
  271. scaled_keypoints = keypoints[mask] * self.downscale**octave
  272. keypoints_list.append(scaled_keypoints)
  273. responses_list.append(responses[mask])
  274. orientations_list.append(orientations[mask])
  275. scales_list.append(
  276. self.downscale**octave
  277. * np.ones(scaled_keypoints.shape[0], dtype=np.intp)
  278. )
  279. descriptors_list.append(descriptors)
  280. if len(scales_list) == 0:
  281. raise RuntimeError(
  282. "ORB found no features. Try passing in an image containing "
  283. "greater intensity contrasts between adjacent pixels."
  284. )
  285. keypoints = np.vstack(keypoints_list)
  286. responses = np.hstack(responses_list)
  287. scales = np.hstack(scales_list)
  288. orientations = np.hstack(orientations_list)
  289. descriptors = np.vstack(descriptors_list).view(bool)
  290. if keypoints.shape[0] < self.n_keypoints:
  291. self.keypoints = keypoints
  292. self.scales = scales
  293. self.orientations = orientations
  294. self.responses = responses
  295. self.descriptors = descriptors
  296. else:
  297. # Choose best n_keypoints according to Harris corner response
  298. best_indices = responses.argsort()[::-1][: self.n_keypoints]
  299. self.keypoints = keypoints[best_indices]
  300. self.scales = scales[best_indices]
  301. self.orientations = orientations[best_indices]
  302. self.responses = responses[best_indices]
  303. self.descriptors = descriptors[best_indices]