Refactor ORB

This commit is contained in:
Johannes Schönberger
2013-11-30 01:46:16 +01:00
parent 79616815ff
commit 2705907270
5 changed files with 284 additions and 201 deletions
+10 -10
View File
@@ -11,9 +11,10 @@ from .corner_cy import corner_moravec, corner_orientations
from .template import match_template
from .brief import BRIEF
from .censure import CenSurE
from .orb import ORB
from .match import match_binary_descriptors
from .util import pairwise_hamming_distance
from .orb import keypoints_orb, descriptor_orb
__all__ = ['daisy',
'hog',
@@ -21,6 +22,10 @@ __all__ = ['daisy',
'greycoprops',
'local_binary_pattern',
'peak_local_max',
'structure_tensor',
'structure_tensor_eigvals',
'hessian_matrix',
'hessian_matrix_eigvals',
'corner_kitchen_rosenfeld',
'corner_harris',
'corner_shi_tomasi',
@@ -28,16 +33,11 @@ __all__ = ['daisy',
'corner_subpix',
'corner_peaks',
'corner_moravec',
'corner_fast',
'corner_orientations',
'match_template',
'BRIEF',
'CenSurE',
'ORB',
'pairwise_hamming_distance',
'match_binary_descriptors',
'corner_fast',
'corner_orientations',
'structure_tensor',
'structure_tensor_eigvals',
'hessian_matrix',
'hessian_matrix_eigvals',
'keypoints_orb',
'descriptor_orb']
'match_binary_descriptors']
+2 -2
View File
@@ -182,8 +182,8 @@ class CenSurE(FeatureDetector):
-------
keypoints : (N, 2) array
Keypoint coordinates as ``(row, col)``.
scales : (N, 1) array
Corresponding scales of the N extracted keypoints.
scales : (N, ) array
Corresponding scales.
"""
+194 -140
View File
@@ -1,6 +1,7 @@
import numpy as np
from skimage.feature.util import (_mask_border_keypoints,
from skimage.feature.util import (FeatureDetector, DescriptorExtractor,
_mask_border_keypoints,
_prepare_grayscale_input_2D)
from skimage.feature import (corner_fast, corner_orientations, corner_peaks,
@@ -17,18 +18,13 @@ for i in range(-15, 16):
OFAST_MASK[15 + j, 15 + i] = 1
class ORB(FeatureDetector, DescriptorExtractor):
def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08,
harris_k=0.04, downscale=1.2, n_scales=8):
"""Detect oriented FAST keypoints.
"""Oriented FAST and rotated BRIEF feature detector and binary descriptor
extractor.
Parameters
----------
image : 2D ndarray
Input image.
n_keypoints : int
Number of keypoints to be returned. The function will return the best
``n_keypoints`` according to the Harris corner response if more than
@@ -58,165 +54,223 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08,
Maximum number of scales from the bottom of the image pyramid to
extract the features from.
Returns
-------
keypoints : (N, ...) recarray
Record array as returned by `skimage.feature.create_keypoint_recarray`
with the fields: `row`, `col`, `scale`, `orientation` and `response`.
References
----------
.. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
"ORB : An efficient alternative to SIFT and SURF"
"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 keypoints_orb, descriptor_orb
>>> square = np.zeros((50, 50))
>>> square[20:30, 20:30] = 1
>>> keypoints = keypoints_orb(square, n_keypoints=8, n_scales=2)
>>> keypoints.shape
(8,)
>>> keypoints.row
array([ 29. , 29. , 20. , 20. , 20.4, 20.4, 28.8, 28.8])
>>> keypoints.col
array([ 29. , 20. , 29. , 20. , 28.8, 20.4, 28.8, 20.4])
>>> keypoints.octave
array([ 1. , 1. , 1. , 1. , 1.2, 1.2, 1.2, 1.2])
>>> np.rad2deg(keypoints.orientation)
array([-135., -45., 135., 45., 135., 45., -135., -45.])
>>> keypoints.response
array([ 21.4776577 , 21.4776577 , 21.4776577 , 21.4776577 ,
14.03845308, 14.03845308, 14.03845308, 14.03845308])
"""
image = _prepare_grayscale_input_2D(image)
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
pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale))
keypoints_list = []
orientations_list = []
scales_list = []
harris_response_list = []
for octave in range(len(pyramid)):
def _build_pyramid(self, image):
image = _prepare_grayscale_input_2D(image)
return list(pyramid_gaussian(image, self.n_scales - 1, self.downscale))
def _detect_octave(self, octave_image):
# Extract keypoints for current octave
corners = corner_peaks(corner_fast(pyramid[octave], fast_n,
fast_threshold), min_distance=1)
fast_response = corner_fast(octave_image, self.fast_n,
self.fast_threshold)
keypoints = corner_peaks(fast_response, min_distance=1)
# Scale keypoint coordinates so they correspond to the original
# image shape
keypoints_list.append(corners * downscale ** octave)
mask = _mask_border_keypoints(octave_image.shape, keypoints,
distance=16)
keypoints = keypoints[mask]
orientations_list.append(corner_orientations(pyramid[octave], corners,
OFAST_MASK))
orientations = corner_orientations(octave_image, keypoints,
OFAST_MASK)
scales_list.append(octave * np.ones(corners.shape[0], dtype=np.intp))
harris_response = corner_harris(octave_image, method='k',
k=self.harris_k)
responses = harris_response[keypoints[:, 0], keypoints[:, 1]]
harris_response = corner_harris(pyramid[octave], method='k',
k=harris_k)
harris_response_list.append(harris_response[corners[:, 0],
corners[:, 1]])
return keypoints, orientations, responses
keypoints_array = np.vstack(keypoints_list)
orientations = np.hstack(orientations_list)
scales = downscale ** np.hstack(scales_list)
harris_measure = np.hstack(harris_response_list)
keypoints = create_keypoint_recarray(keypoints_array[:, 0],
keypoints_array[:, 1],
scales, orientations,
harris_measure)
def detect(self, image):
"""Detect oriented FAST keypoints along with the corresponding scale.
if keypoints.shape[0] < n_keypoints:
return keypoints
else:
# Choose best n_keypoints according to Harris corner response
best_indices = harris_measure.argsort()[::-1][:n_keypoints]
return keypoints[best_indices]
Parameters
----------
image : 2D array
Input image.
Returns
-------
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.
def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8):
"""Compute rBRIEF descriptors for keypoints.
"""
Parameters
----------
image : 2D ndarray
Input image.
keypoints : (N, ...) recarray
Record array as returned by `skimage.feature.create_keypoint_recarray`
with the fields: `row`, `col`, `scale`, `orientation` and `response`.
downscale : float
Downscale factor for the image pyramid. Should be the same as that
used in ``keypoints_orb``.
n_scales : int
Number of scales from the bottom of the image pyramid to extract
the features from.
pyramid = self._build_pyramid(image)
Returns
-------
descriptors : (P, 256) bool ndarray
2darray of type bool describing the P keypoints obtained after
filtering out those near the image border. Size of each descriptor
is 32 bytes or 256 bits.
filtered_keypoints : (P, 2) ndarray
Record array with fields row, col, octave, orientation, response for
P keypoints obtained after removing out those that are near the
border.
keypoints_list = []
orientations_list = []
octave_list = []
responses_list = []
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
for octave in range(len(pyramid)):
Examples
--------
>>> import numpy as np
>>> from skimage.feature import keypoints_orb, descriptor_orb
>>> square = np.zeros((50, 50))
>>> square[20:30, 20:30] = 1
>>> keypoints = keypoints_orb(square, n_keypoints=8, n_scales=2)
>>> keypoints.shape
(8,)
>>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, n_scales=2)
>>> filtered_keypoints.shape
(8,)
>>> descriptors.shape
(8, 256)
octave_image = np.ascontiguousarray(pyramid[octave])
"""
image = _prepare_grayscale_input_2D(image)
keypoints, orientations, responses = \
self._detect_octave(octave_image)
pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale))
keypoints_list.append(keypoints * self.downscale ** octave)
orientations_list.append(orientations)
octave_list.append(self.downscale ** octave
* np.ones(keypoints.shape[0], dtype=np.intp))
responses_list.append(responses)
descriptors_list = []
keypoints_list = []
keypoints = np.vstack(keypoints_list)
orientations = np.hstack(orientations_list)
scales = np.hstack(octave_list)
responses = np.hstack(responses_list)
for scale in range(n_scales):
curr_image = np.ascontiguousarray(pyramid[scale])
if keypoints.shape[0] < self.n_keypoints:
return keypoints, scales, orientations, responses
else:
# Choose best n_keypoints according to Harris corner response
best_indices = responses.argsort()[::-1][:self.n_keypoints]
return (keypoints[best_indices], scales[best_indices],
orientations[best_indices], responses[best_indices])
curr_scale_mask = (np.log(keypoints.octave) /
np.log(downscale)).astype(np.intp) == scale
if np.sum(curr_scale_mask) > 0:
curr_keypoints = keypoints[curr_scale_mask]
curr_scale_kpts = np.squeeze(np.dstack((curr_keypoints.row / curr_keypoints.octave,
curr_keypoints.col / curr_keypoints.octave)))
border_mask = _mask_border_keypoints(curr_image,
curr_scale_kpts,
dist=16)
def _extract_octave(self, octave_image, keypoints, orientations):
mask = _mask_border_keypoints(octave_image.shape, keypoints,
distance=16)
keypoints = np.array(keypoints[mask], dtype=np.intp, order='C',
copy=False)
orientations = np.array(orientations[mask], dtype=np.double, order='C',
copy=False)
curr_keypoints = curr_keypoints[border_mask]
descriptors = _orb_loop(octave_image, keypoints, orientations)
curr_scale_kpts = np.ascontiguousarray(np.round(curr_scale_kpts[border_mask]).astype(np.intp))
curr_scale_orientation = np.ascontiguousarray(curr_keypoints.orientation)
curr_scale_descriptors = _orb_loop(curr_image, curr_scale)
return descriptors, mask
descriptors_list.append(curr_scale_descriptors)
keypoints_list.append(curr_keypoints)
def extract(self, image, keypoints, scales, orientations):
"""Extract rBRIEF binary descriptors for given keypoints in image.
descriptors = np.vstack(descriptors_list).view(np.bool)
filtered_keypoints = np.hstack(keypoints_list)
return descriptors, filtered_keypoints.view(np.recarray)
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.
Returns
-------
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)``.
mask : (N, ) array of dtype bool
Mask indicating whether a keypoint has been filtered out
(``False``) or is described in the `descriptors` array (``True``).
"""
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)
descriptors = np.vstack(descriptors_list).view(np.bool)
mask = np.hstack(mask_list)
return descriptors, mask
def detect_and_extract(self, image):
"""Detect oriented FAST keypoints and extract rBRIEF descriptors.
Parameters
----------
image : 2D array
Input image.
Returns
-------
keypoints : (Q, 2) array
Keypoint coordinates as ``(row, col)``.
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)``.
"""
pyramid = self._build_pyramid(image)
keypoints_list = []
responses_list = []
descriptors_list = []
for octave in range(len(pyramid)):
octave_image = np.ascontiguousarray(pyramid[octave])
keypoints, orientations, responses = \
self._detect_octave(octave_image)
descriptors, mask = self._extract_octave(octave_image, keypoints,
orientations)
keypoints_list.append(keypoints * self.downscale ** octave)
responses_list.append(responses)
descriptors_list.append(descriptors)
keypoints = np.vstack(keypoints_list)
responses = np.hstack(responses_list)
descriptors = np.vstack(descriptors_list).view(np.bool)
if keypoints.shape[0] < self.n_keypoints:
return keypoints, descriptors
else:
# Choose best n_keypoints according to Harris corner response
best_indices = responses.argsort()[::-1][:self.n_keypoints]
return (keypoints[best_indices], descriptors[best_indices])
+7 -5
View File
@@ -13,21 +13,23 @@ from libc.math cimport sin, cos, round
POS = np.loadtxt(os.path.join(data_dir, "orb_descriptor_positions.txt"),
dtype=np.int8)
POS0 = np.ascontiguousarray(POS[:, :2])
POS1 = np.ascontiguousarray(POS[:, 2:])
def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints,
double[:] orientations, pos):
double[:] orientations):
cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1, spr0, spc0, spr1, spc1
cdef int[:, ::1] steered_pos0, steered_pos1
cdef double angle
cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0],
pos.shape[0]), dtype=np.uint8)
cdef char[:, ::1] cpos0 = pos[:, :2]
cdef char[:, ::1] cpos1 = pos[:, 2:]
POS.shape[0]), dtype=np.uint8)
cdef char[:, ::1] cpos0 = POS0
cdef char[:, ::1] cpos1 = POS1
for i in range(descriptors.shape[0]):
angle = orientations[i]
sin_a = sin(angle)
cos_a = cos(angle)
+71 -44
View File
@@ -1,21 +1,24 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_almost_equal
from skimage.feature import keypoints_orb, descriptor_orb
from skimage.feature import ORB
from skimage.data import lena
from skimage.color import rgb2gray
def test_keypoints_orb_desired_no_of_keypoints():
img = rgb2gray(lena())
keypoints = keypoints_orb(img, n_keypoints=10, fast_n=12,
fast_threshold=0.20)
exp_row = np.array([ 435. , 435.6 , 376. , 455. , 434.88, 269. ,
375.6 , 310.8 , 413. , 311.04])
exp_col = np.array([ 180. , 180. , 156. , 176. , 180. , 111. ,
156. , 172.8, 70. , 172.8])
img = rgb2gray(lena())
exp_octaves = np.array([ 1. , 1.2 , 1. , 1. , 1.44 , 1. ,
1.2 , 1.2 , 1. , 1.728])
def test_keypoints_orb_desired_no_of_keypoints():
detector_extractor = ORB(n_keypoints=10, fast_n=12, fast_threshold=0.20)
keypoints, scales, orientations, responses = detector_extractor.detect(img)
exp_rows = np.array([ 435. , 435.6 , 376. , 455. , 434.88, 269. ,
375.6 , 310.8 , 413. , 311.04])
exp_cols = np.array([ 180. , 180. , 156. , 176. , 180. , 111. ,
156. , 172.8, 70. , 172.8])
exp_scales = np.array([ 1. , 1.2 , 1. , 1. , 1.44 , 1. ,
1.2 , 1.2 , 1. , 1.728])
exp_orientations = np.array([-175.64733392, -167.94842949, -148.98350192,
-142.03599837, -176.08535837, -53.08162354,
@@ -25,24 +28,30 @@ def test_keypoints_orb_desired_no_of_keypoints():
0.5626413 , 0.5097993 , 0.44351774,
0.39154173, 0.39084861, 0.39063076,
0.37602487])
assert_almost_equal(exp_row, keypoints.row)
assert_almost_equal(exp_col, keypoints.col)
assert_almost_equal(exp_octaves, keypoints.octave)
assert_almost_equal(exp_response, keypoints.response)
assert_almost_equal(exp_orientations, np.rad2deg(keypoints.orientation))
assert_almost_equal(exp_rows, keypoints[:, 0])
assert_almost_equal(exp_cols, keypoints[:, 1])
assert_almost_equal(exp_scales, scales)
assert_almost_equal(exp_response, responses)
assert_almost_equal(exp_orientations, np.rad2deg(orientations), 5)
keypoints, _ = detector_extractor.detect_and_extract(img)
assert_almost_equal(exp_rows, keypoints[:, 0])
assert_almost_equal(exp_cols, keypoints[:, 1])
def test_keypoints_orb_less_than_desired_no_of_keypoints():
img = rgb2gray(lena())
keypoints = keypoints_orb(img, n_keypoints=15, fast_n=12,
fast_threshold=0.33, downscale=2, n_scales=2)
detector_extractor = ORB(n_keypoints=15, fast_n=12,
fast_threshold=0.33, downscale=2, n_scales=2)
keypoints, scales, orientations, responses = detector_extractor.detect(img)
exp_row = np.array([ 67., 247., 269., 413., 435., 230., 264.,
330., 372.])
exp_col = np.array([ 157., 146., 111., 70., 180., 136., 336.,
148., 156.])
exp_rows = np.array([ 67., 247., 269., 413., 435., 230., 264.,
330., 372.])
exp_cols = np.array([ 157., 146., 111., 70., 180., 136., 336.,
148., 156.])
exp_octaves = np.array([ 1., 1., 1., 1., 1., 2., 2., 2., 2.])
exp_scales = np.array([ 1., 1., 1., 1., 1., 2., 2., 2., 2.])
exp_orientations = np.array([-105.76503839, -96.28973044, -53.08162354,
-173.4479964 , -175.64733392, -106.07927215,
@@ -52,33 +61,51 @@ def test_keypoints_orb_less_than_desired_no_of_keypoints():
0.39063076, 0.96770745, 0.04935129,
0.21431068, 0.15826555, 0.42403573])
assert_almost_equal(exp_row, keypoints.row)
assert_almost_equal(exp_col, keypoints.col)
assert_almost_equal(exp_octaves, keypoints.octave)
assert_almost_equal(exp_response, keypoints.response)
assert_almost_equal(exp_orientations, np.rad2deg(keypoints.orientation))
assert_almost_equal(exp_rows, keypoints[:, 0])
assert_almost_equal(exp_cols, keypoints[:, 1])
assert_almost_equal(exp_scales, scales)
assert_almost_equal(exp_response, responses)
assert_almost_equal(exp_orientations, np.rad2deg(orientations), 5)
keypoints, _ = detector_extractor.detect_and_extract(img)
assert_almost_equal(exp_rows, keypoints[:, 0])
assert_almost_equal(exp_cols, keypoints[:, 1])
def test_descriptor_orb():
img = rgb2gray(lena())
keypoints = keypoints_orb(img, n_keypoints=10, fast_n=12,
fast_threshold=0.20)
descriptors, filtered_keypoints = descriptor_orb(img, keypoints)
detector_extractor = ORB(fast_n=12, fast_threshold=0.20)
descriptors_120_129 = np.array([[ True, False, False, True, False, False, False, False, False, False],
[ True, True, False, False, True, False, False, True, False, True],
[False, True, True, False, True, False, True, True, True, True],
[False, False, False, True, True, False, True, False, True, False],
[False, True, True, True, True, False, True, True, True, False],
[ True, False, True, True, True, False, False, False, True, False],
[ True, False, True, False, True, False, True, True, False, True],
[ True, True, True, True, True, True, False, True, True, True],
[ True, True, True, False, True, False, True, True, True, False],
[ True, False, False, False, False, False, True, True, True, False]],
dtype=bool)
exp_descriptors = np.array([[ True, False, True, True, False, False, False, False, False, False],
[False, False, True, True, False, True, True, False, True, True],
[ True, False, False, False, True, False, True, True, True, False],
[ True, False, False, True, False, True, True, False, False, False],
[False, True, True, True, False, False, False, True, True, False],
[False, False, False, False, False, True, False, True, True, True],
[False, True, True, True, True, False, False, True, False, True],
[ True, True, True, False, True, True, True, True, False, False],
[ True, True, False, True, True, True, True, False, False, False],
[ True, False, False, False, False, True, False, False, True, True],
[ True, False, False, False, True, True, True, False, False, False],
[False, False, True, False, True, False, False, True, False, False],
[False, False, True, True, False, False, False, False, False, True],
[ True, True, False, False, False, True, True, True, True, True],
[ True, True, True, False, False, True, False, True, True, False],
[False, True, True, False, False, True, True, True, True, True],
[ True, True, True, False, False, False, False, True, True, True],
[False, False, False, False, True, False, False, True, True, False],
[False, True, False, False, True, False, False, False, True, True],
[ True, False, True, False, False, False, True, True, False, False]], dtype=bool)
keypoints1, scales1, orientations1, responses1 \
= detector_extractor.detect(img)
descriptors1, mask1 \
= detector_extractor.extract(img, keypoints1, scales1, orientations1)
assert_array_equal(exp_descriptors, descriptors1[100:120, 10:20])
assert_array_equal(descriptors_120_129, descriptors[:, 120:130])
keypoints2, descriptors2 = detector_extractor.detect_and_extract(img)
assert_array_equal(exp_descriptors, descriptors2[100:120, 10:20])
assert_array_equal(keypoints1[mask1], keypoints2)
if __name__ == '__main__':