Implement object oriented interface for BRIEF

This commit is contained in:
Johannes Schönberger
2013-11-29 22:59:13 +01:00
parent 2f11f2277b
commit 845448a152
6 changed files with 173 additions and 194 deletions
+3 -4
View File
@@ -9,9 +9,9 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris,
hessian_matrix_eigvals)
from .corner_cy import corner_moravec, corner_orientations
from .template import match_template
from ._brief import descriptor_brief
from ._brief import BRIEF
from .match import match_binary_descriptors
from .util import pairwise_hamming_distance, create_keypoint_recarray
from .util import pairwise_hamming_distance
from .censure import keypoints_censure
from .orb import keypoints_orb, descriptor_orb
@@ -29,9 +29,8 @@ __all__ = ['daisy',
'corner_peaks',
'corner_moravec',
'match_template',
'descriptor_brief',
'BRIEF',
'pairwise_hamming_distance',
'create_keypoint_recarray',
'match_binary_descriptors',
'keypoints_censure',
'corner_fast',
+97 -105
View File
@@ -1,15 +1,15 @@
import numpy as np
from scipy.ndimage.filters import gaussian_filter
from .util import (_mask_border_keypoints, pairwise_hamming_distance,
from .util import (DescriptorExtractor, _mask_border_keypoints,
_prepare_grayscale_input_2D)
from ._brief_cy import _brief_loop
def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
patch_size=49, sample_seed=1, variance=2):
"""Extract BRIEF binary descriptors for given keypoints in an image.
class BRIEF(DescriptorExtractor):
"""BRIEF binary descriptor extractor.
BRIEF (Binary Robust Independent Elementary Features) is an efficient
feature point descriptor. It it is highly discriminative even when using
@@ -24,58 +24,34 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
Parameters
----------
image : 2D ndarray
Input image.
keypoints : (P, ...) recarray
Record array as returned by `skimage.feature.create_keypoint_recarray`
with the fields: `row`, `col`, `scale`, `orientation` and `response`.
descriptor_size : int
Size of BRIEF descriptor for each keypoint. Sizes 128, 256 and 512
recommended by the authors. Default is 256.
mode : {'normal', 'uniform'}
Probability distribution for sampling location of decision pixel-pairs
around keypoints.
patch_size : int
Length of the two dimensional square patch sampling region around
the keypoints. Default is 49.
mode : {'normal', 'uniform'}
Probability distribution for sampling location of decision pixel-pairs
around keypoints.
sample_seed : int
Seed for the random sampling of the decision pixel-pairs. From a square
window with length patch_size, pixel pairs are sampled using the `mode`
parameter to build the descriptors using intensity comparison. The
value of `sample_seed` must be the same for the images to be matched
while building the descriptors.
variance : float
Variance of the Gaussian low pass filter applied to the image to
alleviate noise sensitivity, which is strongly recommended to obtain
sigma : float
Standard deviation of the Gaussian low pass filter applied to the image
to alleviate noise sensitivity, which is strongly recommended to obtain
discriminative and good descriptors.
Returns
-------
descriptors : (Q, `descriptor_size`) ndarray of dtype bool
2D ndarray 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.
keypoints : (Q, ...) recarray
Record array as returned by `skimage.feature.create_keypoint_recarray`
with the fields: `row`, `col`, `scale`, `orientation` and `response`.
References
----------
.. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha and Pascal Fua
"BRIEF : Binary robust independent elementary features", 2010
http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf
Examples
--------
>> from skimage.feature.corner import corner_peaks, corner_harris
>> from skimage.feature import (pairwise_hamming_distance, descriptor_brief,
... match_binary_descriptors,
... create_keypoint_recarray)
>> square1 = np.zeros([8, 8], dtype=np.int32)
>> square1[2:6, 2:6] = 1
>> square1
>>> from skimage.feature import (corner_harris, corner_peaks, BRIEF,
... match_binary_descriptors)
>>> import numpy as np
>>> square1 = np.zeros((8, 8), dtype=np.int32)
>>> square1[2:6, 2:6] = 1
>>> square1
array([[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
@@ -84,17 +60,9 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
>> keypoints1 = create_keypoint_recarray(keypoints1[:, 0], keypoints1[:, 1])
>> descriptors1, keypoints1 = descriptor_brief(square1, keypoints1, patch_size=5)
>> keypoints1
rec.array([(2.0, 2.0, nan, nan, nan), (2.0, 5.0, nan, nan, nan),
(5.0, 2.0, nan, nan, nan), (5.0, 5.0, nan, nan, nan)],
dtype=[('row', '<f8'), ('col', '<f8'), ('octave', '<f8'),
('orientation', '<f8'), ('response', '<f8')])
>> square2 = np.zeros([9, 9], dtype=np.int32)
>> square2[2:7, 2:7] = 1
>> square2
>>> square2 = np.zeros((9, 9), dtype=np.int32)
>>> square2[2:7, 2:7] = 1
>>> square2
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
@@ -104,24 +72,14 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
>> keypoints2 = create_keypoint_recarray(keypoints2[:, 0], keypoints2[:, 1])
>> keypoints2
rec.array([(2.0, 2.0, nan, nan, nan), (2.0, 6.0, nan, nan, nan),
(6.0, 2.0, nan, nan, nan), (6.0, 6.0, nan, nan, nan)],
dtype=[('row', '<f8'), ('col', '<f8'), ('octave', '<f8'),
('orientation', '<f8'), ('response', '<f8')])
>> descriptors2, keypoints2 = descriptor_brief(square2, keypoints2, patch_size=5)
>> pairwise_hamming_distance(descriptors1, descriptors2)
array([[ 0.03125 , 0.3203125, 0.3671875, 0.6171875],
[ 0.3203125, 0.03125 , 0.640625 , 0.375 ],
[ 0.375 , 0.6328125, 0.0390625, 0.328125 ],
[ 0.625 , 0.3671875, 0.34375 , 0.0234375]])
>> matched_kpts, mask1, mask2 = match_binary_descriptors(keypoints1,
... descriptors1,
... keypoints2,
... descriptors2)
>> matched_kpts
>>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
>>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
>>> extractor = BRIEF(patch_size=5)
>>> descs1, _ = extractor.extract(square1, keypoints1)
>>> descs2, _ = extractor.extract(square2, keypoints2)
>>> matches, idxs1, idxs2 = match_binary_descriptors(keypoints1, descs1,
... keypoints2, descs2)
>>> matches
array([[[2, 2],
[2, 2]],
@@ -133,53 +91,87 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
[[5, 5],
[6, 6]]])
>>> mask1
array([0, 1, 2, 3])
>>> mask2
array([0, 1, 2, 3])
"""
if mode not in ('normal', 'uniform'):
raise ValueError("`mode` must be one of 'normal' or 'uniform'.")
def __init__(self, descriptor_size=256, patch_size=49,
mode='normal', sigma=1, sample_seed=1):
np.random.seed(sample_seed)
if mode not in ('normal', 'uniform'):
raise ValueError("`mode` must be 'normal' or 'uniform'.")
image = _prepare_grayscale_input_2D(image)
self.descriptor_size = descriptor_size
self.patch_size = patch_size
self.mode = mode
self.sigma = sigma
self.sample_seed = sample_seed
# Gaussian Low pass filtering to alleviate noise sensitivity
image = gaussian_filter(image, variance)
image = np.ascontiguousarray(image)
def extract(self, image, keypoints):
"""Extract BRIEF binary descriptors for given keypoints in image.
# Sampling pairs of decision pixels in patch_size x patch_size window
if mode == 'normal':
samples = (patch_size / 5.0) * np.random.randn(descriptor_size * 8)
samples = np.array(samples, dtype=np.int32)
samples = samples[(samples < (patch_size // 2))
& (samples > - (patch_size - 2) // 2)]
Parameters
----------
image : 2D array
Input image.
keypoints : (N, 2) array
Keypoint coordinates as ``(row, col)``.
pos1 = samples[:descriptor_size * 2]
pos1 = pos1.reshape(descriptor_size, 2)
pos2 = samples[descriptor_size * 2:descriptor_size * 4]
pos2 = pos2.reshape(descriptor_size, 2)
elif mode == 'uniform':
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(descriptor_size * 2, 2))
samples = np.array(samples, dtype=np.int32)
pos1, pos2 = np.split(samples, 2)
Returns
-------
descriptors : (Q, `descriptor_size`) array of dtype bool
2D ndarray 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``).
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
"""
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
border_mask = _mask_border_keypoints(image.shape, keypoints.row,
keypoints.col, patch_size // 2)
np.random.seed(self.sample_seed)
keypoints_row = keypoints.row[border_mask].astype(np.intp)
keypoints_col = keypoints.col[border_mask].astype(np.intp)
image = _prepare_grayscale_input_2D(image)
descriptors = np.zeros((keypoints_row.shape[0], descriptor_size),
dtype=bool, order='C')
# Gaussian Low pass filtering to alleviate noise sensitivity
image = np.ascontiguousarray(gaussian_filter(image, self.sigma))
_brief_loop(image, descriptors.view(np.uint8),
keypoints_row, keypoints_col, pos1, pos2)
# Sampling pairs of decision pixels in patch_size x patch_size window
desc_size = self.descriptor_size
patch_size = self.patch_size
if self.mode == 'normal':
samples = (patch_size / 5.0) * np.random.randn(desc_size * 8)
samples = np.array(samples, dtype=np.int32)
samples = samples[(samples < (patch_size // 2))
& (samples > - (patch_size - 2) // 2)]
return descriptors, keypoints
pos1 = samples[:desc_size * 2].reshape(desc_size, 2)
pos2 = samples[desc_size * 2:desc_size * 4].reshape(desc_size, 2)
elif self.mode == 'uniform':
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(desc_size * 2, 2))
samples = np.array(samples, dtype=np.int32)
pos1, pos2 = np.split(samples, 2)
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
mask = _mask_border_keypoints(image.shape, keypoints, patch_size // 2)
keypoints = np.array(keypoints[mask, :], dtype=np.intp, order='C',
copy=False)
descriptors = np.zeros((keypoints.shape[0], desc_size),
dtype=bool, order='C')
_brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2)
return descriptors, mask
+4 -4
View File
@@ -7,7 +7,7 @@ cimport numpy as cnp
def _brief_loop(double[:, ::1] image, unsigned char[:, ::1] descriptors,
Py_ssize_t[::1] keypoints_row, Py_ssize_t[::1] keypoints_col,
Py_ssize_t[:, ::1] keypoints,
int[:, ::1] pos0, int[:, ::1] pos1):
cdef Py_ssize_t k, d, kr, kc, pr0, pr1, pc0, pc1
@@ -17,8 +17,8 @@ def _brief_loop(double[:, ::1] image, unsigned char[:, ::1] descriptors,
pc0 = pos0[p, 1]
pr1 = pos1[p, 0]
pc1 = pos1[p, 1]
for k in range(keypoints_row.shape[0]):
kr = keypoints_row[k]
kc = keypoints_col[k]
for k in range(keypoints.shape[0]):
kr = keypoints[k, 0]
kc = keypoints[k, 1]
if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]:
descriptors[k, p] = True
+12 -14
View File
@@ -34,9 +34,9 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2,
-------
matches : (Q, 2, 2) ndarray
Location of Q matched keypoint pairs from two images.
mask1 : (Q,) ndarray
idxs1 : (Q,) ndarray
Indices of keypoints in keypoints1 that have been matched.
mask2 : (Q,) ndarray
idxs2 : (Q,) ndarray
Indices of keypoints in keypoints2 that have been matched.
"""
@@ -51,33 +51,31 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2,
# Get hamming distances between keypoints1 and keypoints2
distance = pairwise_hamming_distance(descriptors1, descriptors2)
kp1 = np.squeeze(np.dstack((keypoints1.row, keypoints1.col)))
kp2 = np.squeeze(np.dstack((keypoints2.row, keypoints2.col)))
if cross_check:
matched_keypoints1_index = np.argmin(distance, axis=1)
matched_keypoints2_index = np.argmin(distance, axis=0)
matched_index = _binary_cross_check_loop(matched_keypoints1_index,
matched_idxs = _binary_cross_check_loop(matched_keypoints1_index,
matched_keypoints2_index,
distance, threshold)
matches = np.zeros((matched_index.shape[0], 2, 2),
matches = np.zeros((matched_idxs.shape[0], 2, 2),
dtype=np.intp)
mask1 = matched_index[:, 0]
mask2 = matched_index[:, 1]
matches[:, 0, :] = kp1[mask1]
matches[:, 1, :] = kp2[mask2]
idxs1 = matched_idxs[:, 0]
idxs2 = matched_idxs[:, 1]
matches[:, 0, :] = keypoints1[idxs1]
matches[:, 1, :] = keypoints2[idxs2]
else:
temp = distance > threshold
row_check = np.any(~temp, axis=1)
matched_keypoints2 = kp2[np.argmin(distance, axis=1)]
matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)]
matches = np.zeros((np.sum(row_check), 2, 2),
dtype=np.intp)
matches[:, 0, :] = kp1[row_check]
matches[:, 0, :] = keypoints1[row_check]
matches[:, 1, :] = matched_keypoints2[row_check]
mask1 = np.where(row_check == True)[0]
mask2 = np.argmin(distance, axis=1)[row_check]
idxs1 = np.where(row_check == True)[0]
idxs2 = np.argmin(distance, axis=1)[row_check]
return matches, mask1, mask2
+17 -22
View File
@@ -3,30 +3,26 @@ from numpy.testing import assert_array_equal, assert_raises
from skimage import data
from skimage import transform as tf
from skimage.color import rgb2gray
from skimage.feature import (descriptor_brief, match_binary_descriptors,
corner_peaks, corner_harris,
create_keypoint_recarray)
from skimage.feature import (BRIEF, match_binary_descriptors,
corner_peaks, corner_harris)
def test_descriptor_brief_color_image_unsupported_error():
"""Brief descriptors can be evaluated on gray-scale images only."""
img = np.zeros((20, 20, 3))
keypoints_loc = np.asarray([[7, 5], [11, 13]])
keypoints = create_keypoint_recarray(keypoints_loc[:, 0],
keypoints_loc[:, 1])
assert_raises(ValueError, descriptor_brief, img, keypoints)
keypoints = np.asarray([[7, 5], [11, 13]])
assert_raises(ValueError, BRIEF().extract, img, keypoints)
def test_descriptor_brief_normal_mode():
"""Verify the computed BRIEF descriptors with expected for normal mode."""
img = data.lena()
img = rgb2gray(img)
keypoints_loc = corner_peaks(corner_harris(img), min_distance=5)
keypoints = create_keypoint_recarray(keypoints_loc[:, 0],
keypoints_loc[:, 1])
img = rgb2gray(data.lena())
descriptors, keypoints = descriptor_brief(img, keypoints[:8],
descriptor_size=8)
keypoints = corner_peaks(corner_harris(img), min_distance=5)
extractor = BRIEF(descriptor_size=8, sigma=2)
descriptors, mask = extractor.extract(img, keypoints[:8])
expected = np.array([[ True, False, True, False, True, True, False, False],
[False, False, False, False, True, False, False, False],
@@ -42,14 +38,13 @@ def test_descriptor_brief_normal_mode():
def test_descriptor_brief_uniform_mode():
"""Verify the computed BRIEF descriptors with expected for uniform mode."""
img = data.lena()
img = rgb2gray(img)
keypoints_loc = corner_peaks(corner_harris(img), min_distance=5)
keypoints = create_keypoint_recarray(keypoints_loc[:, 0],
keypoints_loc[:, 1])
descriptors, keypoints = descriptor_brief(img, keypoints[:8],
descriptor_size=8,
mode='uniform')
img = rgb2gray(data.lena())
keypoints = corner_peaks(corner_harris(img), min_distance=5)
extractor = BRIEF(descriptor_size=8, sigma=2, mode='uniform')
descriptors, mask = extractor.extract(img, keypoints[:8])
expected = np.array([[ True, False, True, False, False, True, False, False],
[False, True, False, False, True, True, True, True],
+40 -45
View File
@@ -3,43 +3,40 @@ import numpy as np
from skimage.util import img_as_float
def create_keypoint_recarray(rows, cols, scales=None, orientations=None,
responses=None):
"""Create keypoint array that allows field access through attributes.
class FeatureDetector(object):
Parameters
----------
rows : (N, ) array
Row coordinates of keypoints.
cols : (N, ) array
Column coordinates of keypoints.
scales : (N, ) array
Scales in which the keypoints have been detected.
orientations : (N, ) array
Orientations of the keypoints.
responses : (N, ) array
Detector response (strength) of the keypoints.
def __init__(self):
raise NotImplementedError()
Returns
-------
recarray : (N, ...) recarray
Array with the fields: `row`, `col`, `scale`, `orientation` and
`response`.
def detect(self, image):
"""Detect keypoints in image.
"""
Parameters
----------
image : 2D array
Input image.
dtype = [('row', np.double),
('col', np.double),
('scale', np.double),
('orientation', np.double),
('response', np.double)]
keypoints = np.zeros(rows.shape[0], dtype=dtype)
keypoints['row'] = rows
keypoints['col'] = cols
keypoints['scale'] = scales
keypoints['orientation'] = orientations
keypoints['response'] = responses
return keypoints.view(np.recarray)
"""
raise NotImplementedError()
class DescriptorExtractor(object):
def __init__(self):
raise NotImplementedError()
def extract(self, image, keypoints):
"""Extract feature descriptors in image for given keypoints.
Parameters
----------
image : 2D array
Input image.
keypoints : (N, 2) array
Keypoint locations as ``(row, col)``.
"""
raise NotImplementedError()
def _prepare_grayscale_input_2D(image):
@@ -50,17 +47,15 @@ def _prepare_grayscale_input_2D(image):
return img_as_float(image)
def _mask_border_keypoints(shape, rr, cc, distance):
def _mask_border_keypoints(image_shape, keypoints, distance):
"""Mask coordinates that are within certain distance from the image border.
Parameters
----------
shape : (2, ) array_like
image_shape : (2, ) array_like
Shape of the image as ``(rows, cols)``.
rr : (N, ) array
Row coordinates.
cc : (N, ) array
Column coordinates.
coords : (N, 2) array
Keypoint coordinates as ``(rows, cols)``.
distance : int
Image border distance.
@@ -72,13 +67,13 @@ def _mask_border_keypoints(shape, rr, cc, distance):
"""
rows = shape[0]
cols = shape[1]
rows = image_shape[0]
cols = image_shape[1]
mask = (((distance - 1) < rr)
& (rr < (rows - distance + 1))
& ((distance - 1) < cc)
& (cc < (cols - distance + 1)))
mask = (((distance - 1) < keypoints[:, 0])
& (keypoints[:, 0] < (rows - distance + 1))
& ((distance - 1) < keypoints[:, 1])
& (keypoints[:, 1] < (cols - distance + 1)))
return mask