corner.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355
  1. import functools
  2. import math
  3. from itertools import combinations_with_replacement
  4. import numpy as np
  5. from scipy import ndimage as ndi
  6. from scipy import spatial, stats
  7. from .._shared.filters import gaussian
  8. from .._shared.utils import _supported_float_type, safe_as_int, warn
  9. from ..transform import integral_image
  10. from ..util import img_as_float
  11. from ._hessian_det_appx import _hessian_matrix_det
  12. from .corner_cy import _corner_fast, _corner_moravec, _corner_orientations
  13. from .peak import peak_local_max
  14. from .util import _prepare_grayscale_input_2D, _prepare_grayscale_input_nD
  15. def _compute_derivatives(image, mode='constant', cval=0):
  16. """Compute derivatives in axis directions using the Sobel operator.
  17. Parameters
  18. ----------
  19. image : ndarray
  20. Input image.
  21. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  22. How to handle values outside the image borders.
  23. cval : float, optional
  24. Used in conjunction with mode 'constant', the value outside
  25. the image boundaries.
  26. Returns
  27. -------
  28. derivatives : list of ndarray
  29. Derivatives in each axis direction.
  30. """
  31. derivatives = [
  32. ndi.sobel(image, axis=i, mode=mode, cval=cval) for i in range(image.ndim)
  33. ]
  34. return derivatives
  35. def structure_tensor(image, sigma=1, mode='constant', cval=0, order='rc'):
  36. """Compute structure tensor using sum of squared differences.
  37. The (2-dimensional) structure tensor A is defined as::
  38. A = [Arr Arc]
  39. [Arc Acc]
  40. which is approximated by the weighted sum of squared differences in a local
  41. window around each pixel in the image. This formula can be extended to a
  42. larger number of dimensions (see [1]_).
  43. Parameters
  44. ----------
  45. image : ndarray
  46. Input image.
  47. sigma : float or array-like of float, optional
  48. Standard deviation used for the Gaussian kernel, which is used as a
  49. weighting function for the local summation of squared differences.
  50. If sigma is an iterable, its length must be equal to `image.ndim` and
  51. each element is used for the Gaussian kernel applied along its
  52. respective axis.
  53. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  54. How to handle values outside the image borders.
  55. cval : float, optional
  56. Used in conjunction with mode 'constant', the value outside
  57. the image boundaries.
  58. order : {'rc', 'xy'}, optional
  59. NOTE: 'xy' is only an option for 2D images, higher dimensions must
  60. always use 'rc' order. This parameter allows for the use of reverse or
  61. forward order of the image axes in gradient computation. 'rc' indicates
  62. the use of the first axis initially (Arr, Arc, Acc), whilst 'xy'
  63. indicates the usage of the last axis initially (Axx, Axy, Ayy).
  64. Returns
  65. -------
  66. A_elems : list of ndarray
  67. Upper-diagonal elements of the structure tensor for each pixel in the
  68. input image.
  69. Examples
  70. --------
  71. >>> from skimage.feature import structure_tensor
  72. >>> square = np.zeros((5, 5))
  73. >>> square[2, 2] = 1
  74. >>> Arr, Arc, Acc = structure_tensor(square, sigma=0.1, order='rc')
  75. >>> Acc
  76. array([[0., 0., 0., 0., 0.],
  77. [0., 1., 0., 1., 0.],
  78. [0., 4., 0., 4., 0.],
  79. [0., 1., 0., 1., 0.],
  80. [0., 0., 0., 0., 0.]])
  81. See also
  82. --------
  83. structure_tensor_eigenvalues
  84. References
  85. ----------
  86. .. [1] https://en.wikipedia.org/wiki/Structure_tensor
  87. """
  88. if order == 'xy' and image.ndim > 2:
  89. raise ValueError('Only "rc" order is supported for dim > 2.')
  90. if order not in ['rc', 'xy']:
  91. raise ValueError(f'order {order} is invalid. Must be either "rc" or "xy"')
  92. if not np.isscalar(sigma):
  93. sigma = tuple(sigma)
  94. if len(sigma) != image.ndim:
  95. raise ValueError('sigma must have as many elements as image ' 'has axes')
  96. image = _prepare_grayscale_input_nD(image)
  97. derivatives = _compute_derivatives(image, mode=mode, cval=cval)
  98. if order == 'xy':
  99. derivatives = reversed(derivatives)
  100. # structure tensor
  101. A_elems = [
  102. gaussian(der0 * der1, sigma=sigma, mode=mode, cval=cval)
  103. for der0, der1 in combinations_with_replacement(derivatives, 2)
  104. ]
  105. return A_elems
  106. def _hessian_matrix_with_gaussian(image, sigma=1, mode='reflect', cval=0, order='rc'):
  107. """Compute the Hessian via convolutions with Gaussian derivatives.
  108. In 2D, the Hessian matrix is defined as:
  109. H = [Hrr Hrc]
  110. [Hrc Hcc]
  111. which is computed by convolving the image with the second derivatives
  112. of the Gaussian kernel in the respective r- and c-directions.
  113. The implementation here also supports n-dimensional data.
  114. Parameters
  115. ----------
  116. image : ndarray
  117. Input image.
  118. sigma : float or sequence of float, optional
  119. Standard deviation used for the Gaussian kernel, which sets the
  120. amount of smoothing in terms of pixel-distances. It is
  121. advised to not choose a sigma much less than 1.0, otherwise
  122. aliasing artifacts may occur.
  123. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  124. How to handle values outside the image borders.
  125. cval : float, optional
  126. Used in conjunction with mode 'constant', the value outside
  127. the image boundaries.
  128. order : {'rc', 'xy'}, optional
  129. This parameter allows for the use of reverse or forward order of
  130. the image axes in gradient computation. 'rc' indicates the use of
  131. the first axis initially (Hrr, Hrc, Hcc), whilst 'xy' indicates the
  132. usage of the last axis initially (Hxx, Hxy, Hyy)
  133. Returns
  134. -------
  135. H_elems : list of ndarray
  136. Upper-diagonal elements of the hessian matrix for each pixel in the
  137. input image. In 2D, this will be a three element list containing [Hrr,
  138. Hrc, Hcc]. In nD, the list will contain ``(n**2 + n) / 2`` arrays.
  139. """
  140. image = img_as_float(image)
  141. float_dtype = _supported_float_type(image.dtype)
  142. image = image.astype(float_dtype, copy=False)
  143. if image.ndim > 2 and order == "xy":
  144. raise ValueError("order='xy' is only supported for 2D images.")
  145. if order not in ["rc", "xy"]:
  146. raise ValueError(f"unrecognized order: {order}")
  147. if np.isscalar(sigma):
  148. sigma = (sigma,) * image.ndim
  149. # This function uses `scipy.ndimage.gaussian_filter` with the order
  150. # argument to compute convolutions. For example, specifying
  151. # ``order=[1, 0]`` would apply convolution with a first-order derivative of
  152. # the Gaussian along the first axis and simple Gaussian smoothing along the
  153. # second.
  154. # For small sigma, the SciPy Gaussian filter suffers from aliasing and edge
  155. # artifacts, given that the filter will approximate a sinc or sinc
  156. # derivative which only goes to 0 very slowly (order 1/n**2). Thus, we use
  157. # a much larger truncate value to reduce any edge artifacts.
  158. truncate = 8 if all(s > 1 for s in sigma) else 100
  159. sq1_2 = 1 / math.sqrt(2)
  160. sigma_scaled = tuple(sq1_2 * s for s in sigma)
  161. common_kwargs = dict(sigma=sigma_scaled, mode=mode, cval=cval, truncate=truncate)
  162. gaussian_ = functools.partial(ndi.gaussian_filter, **common_kwargs)
  163. # Apply two successive first order Gaussian derivative operations, as
  164. # detailed in:
  165. # https://dsp.stackexchange.com/questions/78280/are-scipy-second-order-gaussian-derivatives-correct
  166. # 1.) First order along one axis while smoothing (order=0) along the other
  167. ndim = image.ndim
  168. # orders in 2D = ([1, 0], [0, 1])
  169. # in 3D = ([1, 0, 0], [0, 1, 0], [0, 0, 1])
  170. # etc.
  171. orders = tuple([0] * d + [1] + [0] * (ndim - d - 1) for d in range(ndim))
  172. gradients = [gaussian_(image, order=orders[d]) for d in range(ndim)]
  173. # 2.) apply the derivative along another axis as well
  174. axes = range(ndim)
  175. if order == 'xy':
  176. axes = reversed(axes)
  177. H_elems = [
  178. gaussian_(gradients[ax0], order=orders[ax1])
  179. for ax0, ax1 in combinations_with_replacement(axes, 2)
  180. ]
  181. return H_elems
  182. def hessian_matrix(
  183. image, sigma=1, mode='constant', cval=0, order='rc', use_gaussian_derivatives=None
  184. ):
  185. r"""Compute the Hessian matrix.
  186. In 2D, the Hessian matrix is defined as::
  187. H = [Hrr Hrc]
  188. [Hrc Hcc]
  189. which is computed by convolving the image with the second derivatives
  190. of the Gaussian kernel in the respective r- and c-directions.
  191. The implementation here also supports n-dimensional data.
  192. Parameters
  193. ----------
  194. image : ndarray
  195. Input image.
  196. sigma : float
  197. Standard deviation used for the Gaussian kernel, which is used as
  198. weighting function for the auto-correlation matrix.
  199. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  200. How to handle values outside the image borders.
  201. cval : float, optional
  202. Used in conjunction with mode 'constant', the value outside
  203. the image boundaries.
  204. order : {'rc', 'xy'}, optional
  205. For 2D images, this parameter allows for the use of reverse or forward
  206. order of the image axes in gradient computation. 'rc' indicates the use
  207. of the first axis initially (Hrr, Hrc, Hcc), whilst 'xy' indicates the
  208. usage of the last axis initially (Hxx, Hxy, Hyy). Images with higher
  209. dimension must always use 'rc' order.
  210. use_gaussian_derivatives : bool, optional
  211. Indicates whether the Hessian is computed by convolving with Gaussian
  212. derivatives, or by a simple finite-difference operation.
  213. Returns
  214. -------
  215. H_elems : list of ndarray
  216. Upper-diagonal elements of the hessian matrix for each pixel in the
  217. input image. In 2D, this will be a three element list containing [Hrr,
  218. Hrc, Hcc]. In nD, the list will contain ``(n**2 + n) / 2`` arrays.
  219. Notes
  220. -----
  221. The distributive property of derivatives and convolutions allows us to
  222. restate the derivative of an image, I, smoothed with a Gaussian kernel, G,
  223. as the convolution of the image with the derivative of G.
  224. .. math::
  225. \frac{\partial }{\partial x_i}(I * G) =
  226. I * \left( \frac{\partial }{\partial x_i} G \right)
  227. When ``use_gaussian_derivatives`` is ``True``, this property is used to
  228. compute the second order derivatives that make up the Hessian matrix.
  229. When ``use_gaussian_derivatives`` is ``False``, simple finite differences
  230. on a Gaussian-smoothed image are used instead.
  231. Examples
  232. --------
  233. >>> from skimage.feature import hessian_matrix
  234. >>> square = np.zeros((5, 5))
  235. >>> square[2, 2] = 4
  236. >>> Hrr, Hrc, Hcc = hessian_matrix(square, sigma=0.1, order='rc',
  237. ... use_gaussian_derivatives=False)
  238. >>> Hrc
  239. array([[ 0., 0., 0., 0., 0.],
  240. [ 0., 1., 0., -1., 0.],
  241. [ 0., 0., 0., 0., 0.],
  242. [ 0., -1., 0., 1., 0.],
  243. [ 0., 0., 0., 0., 0.]])
  244. """
  245. image = img_as_float(image)
  246. float_dtype = _supported_float_type(image.dtype)
  247. image = image.astype(float_dtype, copy=False)
  248. if image.ndim > 2 and order == "xy":
  249. raise ValueError("order='xy' is only supported for 2D images.")
  250. if order not in ["rc", "xy"]:
  251. raise ValueError(f"unrecognized order: {order}")
  252. if use_gaussian_derivatives is None:
  253. use_gaussian_derivatives = False
  254. warn(
  255. "use_gaussian_derivatives currently defaults to False, but will "
  256. "change to True in a future version. Please specify this "
  257. "argument explicitly to maintain the current behavior",
  258. category=FutureWarning,
  259. stacklevel=2,
  260. )
  261. if use_gaussian_derivatives:
  262. return _hessian_matrix_with_gaussian(
  263. image, sigma=sigma, mode=mode, cval=cval, order=order
  264. )
  265. gaussian_filtered = gaussian(image, sigma=sigma, mode=mode, cval=cval)
  266. gradients = np.gradient(gaussian_filtered)
  267. axes = range(image.ndim)
  268. if order == 'xy':
  269. axes = reversed(axes)
  270. H_elems = [
  271. np.gradient(gradients[ax0], axis=ax1)
  272. for ax0, ax1 in combinations_with_replacement(axes, 2)
  273. ]
  274. return H_elems
  275. def hessian_matrix_det(image, sigma=1, approximate=True):
  276. """Compute the approximate Hessian Determinant over an image.
  277. The 2D approximate method uses box filters over integral images to
  278. compute the approximate Hessian Determinant.
  279. Parameters
  280. ----------
  281. image : ndarray
  282. The image over which to compute the Hessian Determinant.
  283. sigma : float, optional
  284. Standard deviation of the Gaussian kernel used for the Hessian
  285. matrix.
  286. approximate : bool, optional
  287. If ``True`` and the image is 2D, use a much faster approximate
  288. computation. This argument has no effect on 3D and higher images.
  289. Returns
  290. -------
  291. out : array
  292. The array of the Determinant of Hessians.
  293. References
  294. ----------
  295. .. [1] Herbert Bay, Andreas Ess, Tinne Tuytelaars, Luc Van Gool,
  296. "SURF: Speeded Up Robust Features"
  297. ftp://ftp.vision.ee.ethz.ch/publications/articles/eth_biwi_00517.pdf
  298. Notes
  299. -----
  300. For 2D images when ``approximate=True``, the running time of this method
  301. only depends on size of the image. It is independent of `sigma` as one
  302. would expect. The downside is that the result for `sigma` less than `3`
  303. is not accurate, i.e., not similar to the result obtained if someone
  304. computed the Hessian and took its determinant.
  305. """
  306. image = img_as_float(image)
  307. float_dtype = _supported_float_type(image.dtype)
  308. image = image.astype(float_dtype, copy=False)
  309. if image.ndim == 2 and approximate:
  310. integral = integral_image(image)
  311. return np.array(_hessian_matrix_det(integral, sigma))
  312. else: # slower brute-force implementation for nD images
  313. hessian_mat_array = _symmetric_image(
  314. hessian_matrix(image, sigma, use_gaussian_derivatives=False)
  315. )
  316. return np.linalg.det(hessian_mat_array)
  317. def _symmetric_compute_eigenvalues(S_elems):
  318. """Compute eigenvalues from the upper-diagonal entries of a symmetric
  319. matrix.
  320. Parameters
  321. ----------
  322. S_elems : list of ndarray
  323. The upper-diagonal elements of the matrix, as returned by
  324. `hessian_matrix` or `structure_tensor`.
  325. Returns
  326. -------
  327. eigs : ndarray
  328. The eigenvalues of the matrix, in decreasing order. The eigenvalues are
  329. the leading dimension. That is, ``eigs[i, j, k]`` contains the
  330. ith-largest eigenvalue at position (j, k).
  331. """
  332. if len(S_elems) == 3: # Fast explicit formulas for 2D.
  333. M00, M01, M11 = S_elems
  334. eigs = np.empty((2, *M00.shape), M00.dtype)
  335. eigs[:] = (M00 + M11) / 2
  336. hsqrtdet = np.sqrt(M01**2 + ((M00 - M11) / 2) ** 2)
  337. eigs[0] += hsqrtdet
  338. eigs[1] -= hsqrtdet
  339. return eigs
  340. else:
  341. matrices = _symmetric_image(S_elems)
  342. # eigvalsh returns eigenvalues in increasing order. We want decreasing
  343. eigs = np.linalg.eigvalsh(matrices)[..., ::-1]
  344. leading_axes = tuple(range(eigs.ndim - 1))
  345. return np.transpose(eigs, (eigs.ndim - 1,) + leading_axes)
  346. def _symmetric_image(S_elems):
  347. """Convert the upper-diagonal elements of a matrix to the full
  348. symmetric matrix.
  349. Parameters
  350. ----------
  351. S_elems : list of array
  352. The upper-diagonal elements of the matrix, as returned by
  353. `hessian_matrix` or `structure_tensor`.
  354. Returns
  355. -------
  356. image : array
  357. An array of shape ``(M, N[, ...], image.ndim, image.ndim)``,
  358. containing the matrix corresponding to each coordinate.
  359. """
  360. image = S_elems[0]
  361. symmetric_image = np.zeros(
  362. image.shape + (image.ndim, image.ndim), dtype=S_elems[0].dtype
  363. )
  364. for idx, (row, col) in enumerate(
  365. combinations_with_replacement(range(image.ndim), 2)
  366. ):
  367. symmetric_image[..., row, col] = S_elems[idx]
  368. symmetric_image[..., col, row] = S_elems[idx]
  369. return symmetric_image
  370. def structure_tensor_eigenvalues(A_elems):
  371. """Compute eigenvalues of structure tensor.
  372. Parameters
  373. ----------
  374. A_elems : list of ndarray
  375. The upper-diagonal elements of the structure tensor, as returned
  376. by `structure_tensor`.
  377. Returns
  378. -------
  379. ndarray
  380. The eigenvalues of the structure tensor, in decreasing order. The
  381. eigenvalues are the leading dimension. That is, the coordinate
  382. [i, j, k] corresponds to the ith-largest eigenvalue at position (j, k).
  383. Examples
  384. --------
  385. >>> from skimage.feature import structure_tensor
  386. >>> from skimage.feature import structure_tensor_eigenvalues
  387. >>> square = np.zeros((5, 5))
  388. >>> square[2, 2] = 1
  389. >>> A_elems = structure_tensor(square, sigma=0.1, order='rc')
  390. >>> structure_tensor_eigenvalues(A_elems)[0]
  391. array([[0., 0., 0., 0., 0.],
  392. [0., 2., 4., 2., 0.],
  393. [0., 4., 0., 4., 0.],
  394. [0., 2., 4., 2., 0.],
  395. [0., 0., 0., 0., 0.]])
  396. See also
  397. --------
  398. structure_tensor
  399. """
  400. return _symmetric_compute_eigenvalues(A_elems)
  401. def hessian_matrix_eigvals(H_elems):
  402. """Compute eigenvalues of Hessian matrix.
  403. Parameters
  404. ----------
  405. H_elems : list of ndarray
  406. The upper-diagonal elements of the Hessian matrix, as returned
  407. by `hessian_matrix`.
  408. Returns
  409. -------
  410. eigs : ndarray
  411. The eigenvalues of the Hessian matrix, in decreasing order. The
  412. eigenvalues are the leading dimension. That is, ``eigs[i, j, k]``
  413. contains the ith-largest eigenvalue at position (j, k).
  414. Examples
  415. --------
  416. >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals
  417. >>> square = np.zeros((5, 5))
  418. >>> square[2, 2] = 4
  419. >>> H_elems = hessian_matrix(square, sigma=0.1, order='rc',
  420. ... use_gaussian_derivatives=False)
  421. >>> hessian_matrix_eigvals(H_elems)[0]
  422. array([[ 0., 0., 2., 0., 0.],
  423. [ 0., 1., 0., 1., 0.],
  424. [ 2., 0., -2., 0., 2.],
  425. [ 0., 1., 0., 1., 0.],
  426. [ 0., 0., 2., 0., 0.]])
  427. """
  428. return _symmetric_compute_eigenvalues(H_elems)
  429. def shape_index(image, sigma=1, mode='constant', cval=0):
  430. """Compute the shape index.
  431. The shape index, as defined by Koenderink & van Doorn [1]_, is a
  432. single valued measure of local curvature, assuming the image as a 3D plane
  433. with intensities representing heights.
  434. It is derived from the eigenvalues of the Hessian, and its
  435. value ranges from -1 to 1 (and is undefined (=NaN) in *flat* regions),
  436. with following ranges representing following shapes:
  437. .. table:: Ranges of the shape index and corresponding shapes.
  438. =================== =============
  439. Interval (s in ...) Shape
  440. =================== =============
  441. [ -1, -7/8) Spherical cup
  442. [-7/8, -5/8) Through
  443. [-5/8, -3/8) Rut
  444. [-3/8, -1/8) Saddle rut
  445. [-1/8, +1/8) Saddle
  446. [+1/8, +3/8) Saddle ridge
  447. [+3/8, +5/8) Ridge
  448. [+5/8, +7/8) Dome
  449. [+7/8, +1] Spherical cap
  450. =================== =============
  451. Parameters
  452. ----------
  453. image : (M, N) ndarray
  454. Input image.
  455. sigma : float, optional
  456. Standard deviation used for the Gaussian kernel, which is used for
  457. smoothing the input data before Hessian eigen value calculation.
  458. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  459. How to handle values outside the image borders
  460. cval : float, optional
  461. Used in conjunction with mode 'constant', the value outside
  462. the image boundaries.
  463. Returns
  464. -------
  465. s : ndarray
  466. Shape index
  467. References
  468. ----------
  469. .. [1] Koenderink, J. J. & van Doorn, A. J.,
  470. "Surface shape and curvature scales",
  471. Image and Vision Computing, 1992, 10, 557-564.
  472. :DOI:`10.1016/0262-8856(92)90076-F`
  473. Examples
  474. --------
  475. >>> from skimage.feature import shape_index
  476. >>> square = np.zeros((5, 5))
  477. >>> square[2, 2] = 4
  478. >>> s = shape_index(square, sigma=0.1)
  479. >>> s
  480. array([[ nan, nan, -0.5, nan, nan],
  481. [ nan, -0. , nan, -0. , nan],
  482. [-0.5, nan, -1. , nan, -0.5],
  483. [ nan, -0. , nan, -0. , nan],
  484. [ nan, nan, -0.5, nan, nan]])
  485. """
  486. H = hessian_matrix(
  487. image,
  488. sigma=sigma,
  489. mode=mode,
  490. cval=cval,
  491. order='rc',
  492. use_gaussian_derivatives=False,
  493. )
  494. l1, l2 = hessian_matrix_eigvals(H)
  495. # don't warn on divide by 0 as occurs in the docstring example
  496. with np.errstate(divide='ignore', invalid='ignore'):
  497. return (2.0 / np.pi) * np.arctan((l2 + l1) / (l2 - l1))
  498. def corner_kitchen_rosenfeld(image, mode='constant', cval=0):
  499. """Compute Kitchen and Rosenfeld corner measure response image.
  500. The corner measure is calculated as follows::
  501. (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)
  502. / (imx**2 + imy**2)
  503. Where imx and imy are the first and imxx, imxy, imyy the second
  504. derivatives.
  505. Parameters
  506. ----------
  507. image : (M, N) ndarray
  508. Input image.
  509. mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
  510. How to handle values outside the image borders.
  511. cval : float, optional
  512. Used in conjunction with mode 'constant', the value outside
  513. the image boundaries.
  514. Returns
  515. -------
  516. response : ndarray
  517. Kitchen and Rosenfeld response image.
  518. References
  519. ----------
  520. .. [1] Kitchen, L., & Rosenfeld, A. (1982). Gray-level corner detection.
  521. Pattern recognition letters, 1(2), 95-102.
  522. :DOI:`10.1016/0167-8655(82)90020-4`
  523. """
  524. float_dtype = _supported_float_type(image.dtype)
  525. image = image.astype(float_dtype, copy=False)
  526. imy, imx = _compute_derivatives(image, mode=mode, cval=cval)
  527. imxy, imxx = _compute_derivatives(imx, mode=mode, cval=cval)
  528. imyy, imyx = _compute_derivatives(imy, mode=mode, cval=cval)
  529. numerator = imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy
  530. denominator = imx**2 + imy**2
  531. response = np.zeros_like(image, dtype=float_dtype)
  532. mask = denominator != 0
  533. response[mask] = numerator[mask] / denominator[mask]
  534. return response
  535. def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
  536. """Compute Harris corner measure response image.
  537. This corner detector uses information from the auto-correlation matrix A::
  538. A = [(imx**2) (imx*imy)] = [Axx Axy]
  539. [(imx*imy) (imy**2)] [Axy Ayy]
  540. Where imx and imy are first derivatives, averaged with a gaussian filter.
  541. The corner measure is then defined as::
  542. det(A) - k * trace(A)**2
  543. or::
  544. 2 * det(A) / (trace(A) + eps)
  545. Parameters
  546. ----------
  547. image : (M, N) ndarray
  548. Input image.
  549. method : {'k', 'eps'}, optional
  550. Method to compute the response image from the auto-correlation matrix.
  551. k : float, optional
  552. Sensitivity factor to separate corners from edges, typically in range
  553. `[0, 0.2]`. Small values of k result in detection of sharp corners.
  554. eps : float, optional
  555. Normalisation factor (Noble's corner measure).
  556. sigma : float, optional
  557. Standard deviation used for the Gaussian kernel, which is used as
  558. weighting function for the auto-correlation matrix.
  559. Returns
  560. -------
  561. response : ndarray
  562. Harris response image.
  563. References
  564. ----------
  565. .. [1] https://en.wikipedia.org/wiki/Corner_detection
  566. Examples
  567. --------
  568. >>> from skimage.feature import corner_harris, corner_peaks
  569. >>> square = np.zeros([10, 10])
  570. >>> square[2:8, 2:8] = 1
  571. >>> square.astype(int)
  572. array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  573. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  574. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  575. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  576. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  577. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  578. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  579. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  580. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  581. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
  582. >>> corner_peaks(corner_harris(square), min_distance=1)
  583. array([[2, 2],
  584. [2, 7],
  585. [7, 2],
  586. [7, 7]])
  587. """
  588. Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')
  589. # determinant
  590. detA = Arr * Acc - Arc**2
  591. # trace
  592. traceA = Arr + Acc
  593. if method == 'k':
  594. response = detA - k * traceA**2
  595. else:
  596. response = 2 * detA / (traceA + eps)
  597. return response
  598. def corner_shi_tomasi(image, sigma=1):
  599. """Compute Shi-Tomasi (Kanade-Tomasi) corner measure response image.
  600. This corner detector uses information from the auto-correlation matrix A::
  601. A = [(imx**2) (imx*imy)] = [Axx Axy]
  602. [(imx*imy) (imy**2)] [Axy Ayy]
  603. Where imx and imy are first derivatives, averaged with a gaussian filter.
  604. The corner measure is then defined as the smaller eigenvalue of A::
  605. ((Axx + Ayy) - sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2
  606. Parameters
  607. ----------
  608. image : (M, N) ndarray
  609. Input image.
  610. sigma : float, optional
  611. Standard deviation used for the Gaussian kernel, which is used as
  612. weighting function for the auto-correlation matrix.
  613. Returns
  614. -------
  615. response : ndarray
  616. Shi-Tomasi response image.
  617. References
  618. ----------
  619. .. [1] https://en.wikipedia.org/wiki/Corner_detection
  620. Examples
  621. --------
  622. >>> from skimage.feature import corner_shi_tomasi, corner_peaks
  623. >>> square = np.zeros([10, 10])
  624. >>> square[2:8, 2:8] = 1
  625. >>> square.astype(int)
  626. array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  627. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  628. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  629. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  630. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  631. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  632. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  633. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  634. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  635. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
  636. >>> corner_peaks(corner_shi_tomasi(square), min_distance=1)
  637. array([[2, 2],
  638. [2, 7],
  639. [7, 2],
  640. [7, 7]])
  641. """
  642. Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')
  643. # minimum eigenvalue of A
  644. response = ((Arr + Acc) - np.sqrt((Arr - Acc) ** 2 + 4 * Arc**2)) / 2
  645. return response
  646. def corner_foerstner(image, sigma=1):
  647. """Compute Foerstner corner measure response image.
  648. This corner detector uses information from the auto-correlation matrix A::
  649. A = [(imx**2) (imx*imy)] = [Axx Axy]
  650. [(imx*imy) (imy**2)] [Axy Ayy]
  651. Where imx and imy are first derivatives, averaged with a gaussian filter.
  652. The corner measure is then defined as::
  653. w = det(A) / trace(A) (size of error ellipse)
  654. q = 4 * det(A) / trace(A)**2 (roundness of error ellipse)
  655. Parameters
  656. ----------
  657. image : (M, N) ndarray
  658. Input image.
  659. sigma : float, optional
  660. Standard deviation used for the Gaussian kernel, which is used as
  661. weighting function for the auto-correlation matrix.
  662. Returns
  663. -------
  664. w : ndarray
  665. Error ellipse sizes.
  666. q : ndarray
  667. Roundness of error ellipse.
  668. References
  669. ----------
  670. .. [1] Förstner, W., & Gülch, E. (1987, June). A fast operator for
  671. detection and precise location of distinct points, corners and
  672. centres of circular features. In Proc. ISPRS intercommission
  673. conference on fast processing of photogrammetric data (pp. 281-305).
  674. https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
  675. .. [2] https://en.wikipedia.org/wiki/Corner_detection
  676. Examples
  677. --------
  678. >>> from skimage.feature import corner_foerstner, corner_peaks
  679. >>> square = np.zeros([10, 10])
  680. >>> square[2:8, 2:8] = 1
  681. >>> square.astype(int)
  682. array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  683. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  684. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  685. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  686. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  687. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  688. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  689. [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
  690. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  691. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
  692. >>> w, q = corner_foerstner(square)
  693. >>> accuracy_thresh = 0.5
  694. >>> roundness_thresh = 0.3
  695. >>> foerstner = (q > roundness_thresh) * (w > accuracy_thresh) * w
  696. >>> corner_peaks(foerstner, min_distance=1)
  697. array([[2, 2],
  698. [2, 7],
  699. [7, 2],
  700. [7, 7]])
  701. """
  702. Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')
  703. # determinant
  704. detA = Arr * Acc - Arc**2
  705. # trace
  706. traceA = Arr + Acc
  707. w = np.zeros_like(image, dtype=detA.dtype)
  708. q = np.zeros_like(w)
  709. mask = traceA != 0
  710. w[mask] = detA[mask] / traceA[mask]
  711. q[mask] = 4 * detA[mask] / traceA[mask] ** 2
  712. return w, q
  713. def corner_fast(image, n=12, threshold=0.15):
  714. """Extract FAST corners for a given image.
  715. Parameters
  716. ----------
  717. image : (M, N) ndarray
  718. Input image.
  719. n : int, optional
  720. Minimum number of consecutive pixels out of 16 pixels on the circle
  721. that should all be either brighter or darker w.r.t testpixel.
  722. A point c on the circle is darker w.r.t test pixel p if
  723. `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also
  724. stands for the n in `FAST-n` corner detector.
  725. threshold : float, optional
  726. Threshold used in deciding whether the pixels on the circle are
  727. brighter, darker or similar w.r.t. the test pixel. Decrease the
  728. threshold when more corners are desired and vice-versa.
  729. Returns
  730. -------
  731. response : ndarray
  732. FAST corner response image.
  733. References
  734. ----------
  735. .. [1] Rosten, E., & Drummond, T. (2006, May). Machine learning for
  736. high-speed corner detection. In European conference on computer
  737. vision (pp. 430-443). Springer, Berlin, Heidelberg.
  738. :DOI:`10.1007/11744023_34`
  739. http://www.edwardrosten.com/work/rosten_2006_machine.pdf
  740. .. [2] Wikipedia, "Features from accelerated segment test",
  741. https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test
  742. Examples
  743. --------
  744. >>> from skimage.feature import corner_fast, corner_peaks
  745. >>> square = np.zeros((12, 12))
  746. >>> square[3:9, 3:9] = 1
  747. >>> square.astype(int)
  748. array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  749. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  750. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  751. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  752. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  753. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  754. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  755. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  756. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  757. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  758. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  759. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
  760. >>> corner_peaks(corner_fast(square, 9), min_distance=1)
  761. array([[3, 3],
  762. [3, 8],
  763. [8, 3],
  764. [8, 8]])
  765. """
  766. image = _prepare_grayscale_input_2D(image)
  767. image = np.ascontiguousarray(image)
  768. response = _corner_fast(image, n, threshold)
  769. return response
  770. def corner_subpix(image, corners, window_size=11, alpha=0.99):
  771. """Determine subpixel position of corners.
  772. A statistical test decides whether the corner is defined as the
  773. intersection of two edges or a single peak. Depending on the classification
  774. result, the subpixel corner location is determined based on the local
  775. covariance of the grey-values. If the significance level for either
  776. statistical test is not sufficient, the corner cannot be classified, and
  777. the output subpixel position is set to NaN.
  778. Parameters
  779. ----------
  780. image : (M, N) ndarray
  781. Input image.
  782. corners : (K, 2) ndarray
  783. Corner coordinates `(row, col)`.
  784. window_size : int, optional
  785. Search window size for subpixel estimation.
  786. alpha : float, optional
  787. Significance level for corner classification.
  788. Returns
  789. -------
  790. positions : (K, 2) ndarray
  791. Subpixel corner positions. NaN for "not classified" corners.
  792. References
  793. ----------
  794. .. [1] Förstner, W., & Gülch, E. (1987, June). A fast operator for
  795. detection and precise location of distinct points, corners and
  796. centres of circular features. In Proc. ISPRS intercommission
  797. conference on fast processing of photogrammetric data (pp. 281-305).
  798. https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
  799. .. [2] https://en.wikipedia.org/wiki/Corner_detection
  800. Examples
  801. --------
  802. >>> from skimage.feature import corner_harris, corner_peaks, corner_subpix
  803. >>> img = np.zeros((10, 10))
  804. >>> img[:5, :5] = 1
  805. >>> img[5:, 5:] = 1
  806. >>> img.astype(int)
  807. array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
  808. [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
  809. [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
  810. [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
  811. [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
  812. [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
  813. [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
  814. [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
  815. [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
  816. [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]])
  817. >>> coords = corner_peaks(corner_harris(img), min_distance=2)
  818. >>> coords_subpix = corner_subpix(img, coords, window_size=7)
  819. >>> coords_subpix
  820. array([[4.5, 4.5]])
  821. """
  822. # window extent in one direction
  823. wext = (window_size - 1) // 2
  824. float_dtype = _supported_float_type(image.dtype)
  825. image = image.astype(float_dtype, copy=False)
  826. image = np.pad(image, pad_width=wext, mode='constant', constant_values=0)
  827. # add pad width, make sure to not modify the input values in-place
  828. corners = safe_as_int(corners + wext)
  829. # normal equation arrays
  830. N_dot = np.zeros((2, 2), dtype=float_dtype)
  831. N_edge = np.zeros((2, 2), dtype=float_dtype)
  832. b_dot = np.zeros((2,), dtype=float_dtype)
  833. b_edge = np.zeros((2,), dtype=float_dtype)
  834. # critical statistical test values
  835. redundancy = window_size**2 - 2
  836. t_crit_dot = stats.f.isf(1 - alpha, redundancy, redundancy)
  837. t_crit_edge = stats.f.isf(alpha, redundancy, redundancy)
  838. # coordinates of pixels within window
  839. y, x = np.mgrid[-wext : wext + 1, -wext : wext + 1]
  840. corners_subpix = np.zeros_like(corners, dtype=float_dtype)
  841. for i, (y0, x0) in enumerate(corners):
  842. # crop window around corner + border for sobel operator
  843. miny = y0 - wext - 1
  844. maxy = y0 + wext + 2
  845. minx = x0 - wext - 1
  846. maxx = x0 + wext + 2
  847. window = image[miny:maxy, minx:maxx]
  848. winy, winx = _compute_derivatives(window, mode='constant', cval=0)
  849. # compute gradient squares and remove border
  850. winx_winx = (winx * winx)[1:-1, 1:-1]
  851. winx_winy = (winx * winy)[1:-1, 1:-1]
  852. winy_winy = (winy * winy)[1:-1, 1:-1]
  853. # sum of squared differences (mean instead of gaussian filter)
  854. Axx = np.sum(winx_winx)
  855. Axy = np.sum(winx_winy)
  856. Ayy = np.sum(winy_winy)
  857. # sum of squared differences weighted with coordinates
  858. # (mean instead of gaussian filter)
  859. bxx_x = np.sum(winx_winx * x)
  860. bxx_y = np.sum(winx_winx * y)
  861. bxy_x = np.sum(winx_winy * x)
  862. bxy_y = np.sum(winx_winy * y)
  863. byy_x = np.sum(winy_winy * x)
  864. byy_y = np.sum(winy_winy * y)
  865. # normal equations for subpixel position
  866. N_dot[0, 0] = Axx
  867. N_dot[0, 1] = N_dot[1, 0] = -Axy
  868. N_dot[1, 1] = Ayy
  869. N_edge[0, 0] = Ayy
  870. N_edge[0, 1] = N_edge[1, 0] = Axy
  871. N_edge[1, 1] = Axx
  872. b_dot[:] = bxx_y - bxy_x, byy_x - bxy_y
  873. b_edge[:] = byy_y + bxy_x, bxx_x + bxy_y
  874. # estimated positions
  875. try:
  876. est_dot = np.linalg.solve(N_dot, b_dot)
  877. est_edge = np.linalg.solve(N_edge, b_edge)
  878. except np.linalg.LinAlgError:
  879. # if image is constant the system is singular
  880. corners_subpix[i, :] = np.nan, np.nan
  881. continue
  882. # residuals
  883. ry_dot = y - est_dot[0]
  884. rx_dot = x - est_dot[1]
  885. ry_edge = y - est_edge[0]
  886. rx_edge = x - est_edge[1]
  887. # squared residuals
  888. rxx_dot = rx_dot * rx_dot
  889. rxy_dot = rx_dot * ry_dot
  890. ryy_dot = ry_dot * ry_dot
  891. rxx_edge = rx_edge * rx_edge
  892. rxy_edge = rx_edge * ry_edge
  893. ryy_edge = ry_edge * ry_edge
  894. # determine corner class (dot or edge)
  895. # variance for different models
  896. var_dot = np.sum(
  897. winx_winx * ryy_dot - 2 * winx_winy * rxy_dot + winy_winy * rxx_dot
  898. )
  899. var_edge = np.sum(
  900. winy_winy * ryy_edge + 2 * winx_winy * rxy_edge + winx_winx * rxx_edge
  901. )
  902. # test value (F-distributed)
  903. if var_dot < np.spacing(1) and var_edge < np.spacing(1):
  904. t = np.nan
  905. elif var_dot == 0:
  906. t = np.inf
  907. else:
  908. t = var_edge / var_dot
  909. # 1 for edge, -1 for dot, 0 for "not classified"
  910. corner_class = int(t < t_crit_edge) - int(t > t_crit_dot)
  911. if corner_class == -1:
  912. corners_subpix[i, :] = y0 + est_dot[0], x0 + est_dot[1]
  913. elif corner_class == 0:
  914. corners_subpix[i, :] = np.nan, np.nan
  915. elif corner_class == 1:
  916. corners_subpix[i, :] = y0 + est_edge[0], x0 + est_edge[1]
  917. # subtract pad width
  918. corners_subpix -= wext
  919. return corners_subpix
  920. def corner_peaks(
  921. image,
  922. min_distance=1,
  923. threshold_abs=None,
  924. threshold_rel=None,
  925. exclude_border=True,
  926. indices=True,
  927. num_peaks=np.inf,
  928. footprint=None,
  929. labels=None,
  930. *,
  931. num_peaks_per_label=np.inf,
  932. p_norm=np.inf,
  933. ):
  934. """Find peaks in corner measure response image.
  935. This differs from `skimage.feature.peak_local_max` in that it suppresses
  936. multiple connected peaks with the same accumulator value.
  937. Parameters
  938. ----------
  939. image : (M, N) ndarray
  940. Input image.
  941. min_distance : int, optional
  942. The minimal allowed distance separating peaks.
  943. * : *
  944. See :py:meth:`skimage.feature.peak_local_max`.
  945. p_norm : float
  946. Which Minkowski p-norm to use. Should be in the range [1, inf].
  947. A finite large p may cause a ValueError if overflow can occur.
  948. ``inf`` corresponds to the Chebyshev distance and 2 to the
  949. Euclidean distance.
  950. Returns
  951. -------
  952. output : ndarray or ndarray of bools
  953. * If `indices = True` : (row, column, ...) coordinates of peaks.
  954. * If `indices = False` : Boolean array shaped like `image`, with peaks
  955. represented by True values.
  956. See also
  957. --------
  958. skimage.feature.peak_local_max
  959. Notes
  960. -----
  961. .. versionchanged:: 0.18
  962. The default value of `threshold_rel` has changed to None, which
  963. corresponds to letting `skimage.feature.peak_local_max` decide on the
  964. default. This is equivalent to `threshold_rel=0`.
  965. The `num_peaks` limit is applied before suppression of connected peaks.
  966. To limit the number of peaks after suppression, set `num_peaks=np.inf` and
  967. post-process the output of this function.
  968. Examples
  969. --------
  970. >>> from skimage.feature import peak_local_max
  971. >>> response = np.zeros((5, 5))
  972. >>> response[2:4, 2:4] = 1
  973. >>> response
  974. array([[0., 0., 0., 0., 0.],
  975. [0., 0., 0., 0., 0.],
  976. [0., 0., 1., 1., 0.],
  977. [0., 0., 1., 1., 0.],
  978. [0., 0., 0., 0., 0.]])
  979. >>> peak_local_max(response)
  980. array([[2, 2],
  981. [2, 3],
  982. [3, 2],
  983. [3, 3]])
  984. >>> corner_peaks(response)
  985. array([[2, 2]])
  986. """
  987. if np.isinf(num_peaks):
  988. num_peaks = None
  989. # Get the coordinates of the detected peaks
  990. coords = peak_local_max(
  991. image,
  992. min_distance=min_distance,
  993. threshold_abs=threshold_abs,
  994. threshold_rel=threshold_rel,
  995. exclude_border=exclude_border,
  996. num_peaks=np.inf,
  997. footprint=footprint,
  998. labels=labels,
  999. num_peaks_per_label=num_peaks_per_label,
  1000. )
  1001. if len(coords):
  1002. # Use KDtree to find the peaks that are too close to each other
  1003. tree = spatial.cKDTree(coords)
  1004. rejected_peaks_indices = set()
  1005. for idx, point in enumerate(coords):
  1006. if idx not in rejected_peaks_indices:
  1007. candidates = tree.query_ball_point(point, r=min_distance, p=p_norm)
  1008. candidates.remove(idx)
  1009. rejected_peaks_indices.update(candidates)
  1010. # Remove the peaks that are too close to each other
  1011. coords = np.delete(coords, tuple(rejected_peaks_indices), axis=0)[:num_peaks]
  1012. if indices:
  1013. return coords
  1014. peaks = np.zeros_like(image, dtype=bool)
  1015. peaks[tuple(coords.T)] = True
  1016. return peaks
  1017. def corner_moravec(image, window_size=1):
  1018. """Compute Moravec corner measure response image.
  1019. This is one of the simplest corner detectors and is comparatively fast but
  1020. has several limitations (e.g. not rotation invariant).
  1021. Parameters
  1022. ----------
  1023. image : (M, N) ndarray
  1024. Input image.
  1025. window_size : int, optional
  1026. Window size.
  1027. Returns
  1028. -------
  1029. response : ndarray
  1030. Moravec response image.
  1031. References
  1032. ----------
  1033. .. [1] https://en.wikipedia.org/wiki/Corner_detection
  1034. Examples
  1035. --------
  1036. >>> from skimage.feature import corner_moravec
  1037. >>> square = np.zeros([7, 7])
  1038. >>> square[3, 3] = 1
  1039. >>> square.astype(int)
  1040. array([[0, 0, 0, 0, 0, 0, 0],
  1041. [0, 0, 0, 0, 0, 0, 0],
  1042. [0, 0, 0, 0, 0, 0, 0],
  1043. [0, 0, 0, 1, 0, 0, 0],
  1044. [0, 0, 0, 0, 0, 0, 0],
  1045. [0, 0, 0, 0, 0, 0, 0],
  1046. [0, 0, 0, 0, 0, 0, 0]])
  1047. >>> corner_moravec(square).astype(int)
  1048. array([[0, 0, 0, 0, 0, 0, 0],
  1049. [0, 0, 0, 0, 0, 0, 0],
  1050. [0, 0, 1, 1, 1, 0, 0],
  1051. [0, 0, 1, 2, 1, 0, 0],
  1052. [0, 0, 1, 1, 1, 0, 0],
  1053. [0, 0, 0, 0, 0, 0, 0],
  1054. [0, 0, 0, 0, 0, 0, 0]])
  1055. """
  1056. image = img_as_float(image)
  1057. float_dtype = _supported_float_type(image.dtype)
  1058. image = image.astype(float_dtype, copy=False)
  1059. return _corner_moravec(np.ascontiguousarray(image), window_size)
  1060. def corner_orientations(image, corners, mask):
  1061. """Compute the orientation of corners.
  1062. The orientation of corners is computed using the first order central moment
  1063. i.e. the center of mass approach. The corner orientation is the angle of
  1064. the vector from the corner coordinate to the intensity centroid in the
  1065. local neighborhood around the corner calculated using first order central
  1066. moment.
  1067. Parameters
  1068. ----------
  1069. image : (M, N) array
  1070. Input grayscale image.
  1071. corners : (K, 2) array
  1072. Corner coordinates as ``(row, col)``.
  1073. mask : 2D array
  1074. Mask defining the local neighborhood of the corner used for the
  1075. calculation of the central moment.
  1076. Returns
  1077. -------
  1078. orientations : (K, 1) array
  1079. Orientations of corners in the range [-pi, pi].
  1080. References
  1081. ----------
  1082. .. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
  1083. "ORB : An efficient alternative to SIFT and SURF"
  1084. http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
  1085. .. [2] Paul L. Rosin, "Measuring Corner Properties"
  1086. http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf
  1087. Examples
  1088. --------
  1089. >>> from skimage.morphology import octagon
  1090. >>> from skimage.feature import (corner_fast, corner_peaks,
  1091. ... corner_orientations)
  1092. >>> square = np.zeros((12, 12))
  1093. >>> square[3:9, 3:9] = 1
  1094. >>> square.astype(int)
  1095. array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1096. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1097. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1098. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1099. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1100. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1101. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1102. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1103. [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
  1104. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1105. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1106. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
  1107. >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1)
  1108. >>> corners
  1109. array([[3, 3],
  1110. [3, 8],
  1111. [8, 3],
  1112. [8, 8]])
  1113. >>> orientations = corner_orientations(square, corners, octagon(3, 2))
  1114. >>> np.rad2deg(orientations)
  1115. array([ 45., 135., -45., -135.])
  1116. """
  1117. image = _prepare_grayscale_input_2D(image)
  1118. return _corner_orientations(image, corners, mask)