diff --git a/skimage/feature/orb_descriptor_positions.txt b/skimage/data/orb_descriptor_positions.txt similarity index 100% rename from skimage/feature/orb_descriptor_positions.txt rename to skimage/data/orb_descriptor_positions.txt diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 286a5357..6f90c39e 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -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 diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/_brief_cy.pyx index c53d85fc..5c027c57 100644 --- a/skimage/feature/_brief_cy.pyx +++ b/skimage/feature/_brief_cy.pyx @@ -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 diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 1c712521..fb52e40e 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -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) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 939471c9..68911bb9 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -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]): diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 26b988ef..b2ddcb7e 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -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):