Improve BRIEF and various tweaks

This commit is contained in:
Johannes Schönberger
2013-11-29 21:46:04 +01:00
parent 87c566c6bc
commit ea1ae457f1
6 changed files with 98 additions and 72 deletions
+52 -44
View File
@@ -9,51 +9,62 @@ from ._brief_cy import _brief_loop
def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
patch_size=49, sample_seed=1, variance=2):
"""**Experimental function**.
"""Extract BRIEF binary descriptors for given keypoints in an image.
Extract BRIEF Descriptor about given keypoints for a given image.
BRIEF (Binary Robust Independent Elementary Features) is an efficient
feature point descriptor. It it is highly discriminative even when using
relatively few bits and can be computed using simple intensity difference
tests.
For each keypoint intensity comparisons are carried out for a specifically
distributed number N of pixel-pairs resulting in a binary descriptor of
length N. The descriptor similarity can thus be computed using the Hamming
distance which leads to very good matching performance in contrast to the
L2 norm.
Parameters
----------
image : 2D ndarray
Input image.
keypoints : record array with P rows
Record array with fields row, col, octave, orientation, response.
Octave, orientation and response can be None.
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 about each keypoint. Sizes 128, 256 and 512
preferred by the authors. Default is 256.
mode : string
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. Default is 'normal' otherwise uniform.
around keypoints.
patch_size : int
Length of the two dimensional square patch sampling region around
the keypoints. Default is 49.
sample_seed : int
Seed for sampling 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` should be the same for the images to be matched while
building the descriptors. Default is 1.
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 on the image to
alleviate noise sensitivity. Default is 2.
Variance 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` about Q
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 Intensity comparison about ith keypoint on jth decision pixel-pair.
keypoints : record array with Q rows
Record array with fields row, col, octave, orientation, response.
Octave, orientation and response can be None.
``(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",
"BRIEF : Binary robust independent elementary features", 2010
http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf
Examples
@@ -125,32 +136,19 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
"""
if mode not in ('normal', 'uniform'):
raise ValueError("`mode` must be one of 'normal' or 'uniform'.")
np.random.seed(sample_seed)
image = _prepare_grayscale_input_2D(image)
# Gaussian Low pass filtering to alleviate noise
# sensitivity
# Gaussian Low pass filtering to alleviate noise sensitivity
image = gaussian_filter(image, variance)
image = np.ascontiguousarray(image)
keypoints_loc = np.array(np.squeeze(np.dstack((keypoints.row, keypoints.col)))
+ 0.5, dtype=np.intp, order='C')
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
border_mask = _mask_border_keypoints(image, keypoints_loc, patch_size // 2)
keypoints = keypoints[border_mask]
keypoints_loc = keypoints_loc[border_mask]
keypoints_loc = np.ascontiguousarray(keypoints_loc)
descriptors = np.zeros((keypoints_loc.shape[0], descriptor_size),
dtype=bool, order='C')
# 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))
@@ -160,9 +158,7 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
pos1 = pos1.reshape(descriptor_size, 2)
pos2 = samples[descriptor_size * 2:descriptor_size * 4]
pos2 = pos2.reshape(descriptor_size, 2)
else:
elif mode == 'uniform':
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(descriptor_size * 2, 2))
@@ -172,6 +168,18 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal',
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
_brief_loop(image, descriptors.view(np.uint8), keypoints_loc, pos1, 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)
keypoints_row = keypoints.row[border_mask].astype(np.intp)
keypoints_col = keypoints.col[border_mask].astype(np.intp)
descriptors = np.zeros((keypoints_row.shape[0], descriptor_size),
dtype=bool, order='C')
_brief_loop(image, descriptors.view(np.uint8),
keypoints_row, keypoints_col, pos1, pos2)
return descriptors, keypoints
+5 -5
View File
@@ -6,8 +6,8 @@
cimport numpy as cnp
def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors,
Py_ssize_t[:, ::1] keypoints,
def _brief_loop(double[:, ::1] image, unsigned char[:, ::1] descriptors,
Py_ssize_t[::1] keypoints_row, Py_ssize_t[::1] keypoints_col,
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, char[:, ::1] descriptors,
pc0 = pos0[p, 1]
pr1 = pos1[p, 0]
pc1 = pos1[p, 1]
for k in range(keypoints.shape[0]):
kr = keypoints[k, 0]
kc = keypoints[k, 1]
for k in range(keypoints_row.shape[0]):
kr = keypoints_row[k]
kc = keypoints_col[k]
if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]:
descriptors[k, p] = True
+1 -2
View File
@@ -201,8 +201,7 @@ def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8):
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_kpts,
curr_scale_orientation)
curr_scale_descriptors = _orb_loop(curr_image, curr_scale)
descriptors_list.append(curr_scale_descriptors)
keypoints_list.append(curr_keypoints)
+9 -10
View File
@@ -3,29 +3,28 @@
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
import numpy as np
import os
import numpy as np
from skimage import data_dir
cimport numpy as cnp
from libc.math cimport sin, cos, round
pos = np.loadtxt(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"orb_descriptor_positions.txt"), dtype=np.int8)
pos0 = np.ascontiguousarray(pos[:, :2])
pos1 = np.ascontiguousarray(pos[:, 2:])
POS = np.loadtxt(os.path.join(data_dir, "orb_descriptor_positions.txt"),
dtype=np.int8)
def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints,
double[:] orientations):
double[:] orientations, pos):
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 = pos0
cdef char[:, ::1] cpos1 = pos1
cdef char[:, ::1] cpos0 = pos[:, :2]
cdef char[:, ::1] cpos1 = pos[:, 2:]
for i in range(descriptors.shape[0]):
+31 -11
View File
@@ -22,7 +22,7 @@ def create_keypoint_recarray(rows, cols, scales=None, orientations=None,
Returns
-------
recarray : (N, 5) recarray
recarray : (N, ...) recarray
Array with the fields: `row`, `col`, `scale`, `orientation` and
`response`.
@@ -33,7 +33,7 @@ def create_keypoint_recarray(rows, cols, scales=None, orientations=None,
('scale', np.double),
('orientation', np.double),
('response', np.double)]
keypoints = np.zeros(row.shape[0], dtype=dtype)
keypoints = np.zeros(rows.shape[0], dtype=dtype)
keypoints['row'] = rows
keypoints['col'] = cols
keypoints['scale'] = scales
@@ -50,17 +50,37 @@ def _prepare_grayscale_input_2D(image):
return img_as_float(image)
def _mask_border_keypoints(image, keypoints, dist):
"""Removes keypoints that are within dist pixels from the image border."""
width = image.shape[0]
height = image.shape[1]
def _mask_border_keypoints(shape, rr, cc, distance):
"""Mask coordinates that are within certain distance from the image border.
keypoints_filtering_mask = ((dist - 1 < keypoints[:, 0]) &
(keypoints[:, 0] < width - dist + 1) &
(dist - 1 < keypoints[:, 1]) &
(keypoints[:, 1] < height - dist + 1))
Parameters
----------
shape : (2, ) array_like
Shape of the image as ``(rows, cols)``.
rr : (N, ) array
Row coordinates.
cc : (N, ) array
Column coordinates.
distance : int
Image border distance.
return keypoints_filtering_mask
Returns
-------
mask : (N, ) bool array
Mask indicating if pixels are within the image (``True``) or in the
border region of the image (``False``).
"""
rows = shape[0]
cols = shape[1]
mask = (((distance - 1) < rr)
& (rr < (rows - distance + 1))
& ((distance - 1) < cc)
& (cc < (cols - distance + 1)))
return mask
def pairwise_hamming_distance(array1, array2):