From bcacd06f9c64fe580b031bd780f1ad14cb296691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 2 Jul 2013 21:59:49 +0200 Subject: [PATCH] Outsource inner brief loop into cython file --- skimage/feature/__init__.py | 4 +- skimage/feature/_brief.py | 78 ++++++++++++++++++++++------------- skimage/feature/_brief_cy.pyx | 24 +++++++++++ skimage/feature/setup.py | 3 ++ 4 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 skimage/feature/_brief_cy.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 09ac4540..3e9050c0 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -6,6 +6,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks) from .corner_cy import corner_moravec from .template import match_template +from ._brief import brief __all__ = ['daisy', @@ -21,4 +22,5 @@ __all__ = ['daisy', 'corner_subpix', 'corner_peaks', 'corner_moravec', - 'match_template'] + 'match_template', + 'brief'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index b12d827b..8cca5abc 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,20 +1,28 @@ import numpy as np -from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter from scipy.spatial.distance import hamming +from ..color import rgb2gray +from ..util import img_as_float + +from ._brief_cy import _brief_loop + def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & - (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] + keypoints = keypoints[(dist < keypoints[:, 0]) + & (keypoints[:, 0] < width - dist) + & (dist < keypoints[:, 1]) + & (keypoints[:, 1] < height - dist)] + return keypoints -def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): +def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, + sample_seed=1): """Extract BRIEF Descriptor about given keypoints for a given image. Parameters @@ -49,41 +57,55 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf """ - if np.squeeze(image).ndim == 3: - image = rgb2gray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp) + np.random.seed(sample_seed) - # Removing keypoints that are (patch_size / 2) distance from the image border + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + + # Gaussian Low pass filtering with variance 2 to alleviate noise + # sensitivity + image = gaussian_filter(image, 2) + + image = np.ascontiguousarray(image) + + keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') + + # Removing keypoints that are (patch_size / 2) distance from the image + # border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) - - # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity - image = gaussian_filter(image, 2) + descriptors = np.zeros((keypoints.shape[0], descriptor_size), + dtype=bool, order='C') # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': - np.random.seed(sample_seed) - samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) - samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] - first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) - second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) + + samples = (patch_size / 5) * np.random.randn(descriptor_size * 8) + samples = np.array(samples, dtype=np.int32) + samples = samples[(samples < (patch_size / 2)) + & (samples > - (patch_size - 1) / 2)] + + 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) + else: - np.random.seed(sample_seed) - samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) - first, second = np.split(samples, 2) - # Intensity comparison tests for building the descriptor - for i in range(len(keypoints)): - set_1 = first + keypoints[i] - set_2 = second + keypoints[i] + samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, + (descriptor_size * 2, 2)) + pos1, pos2 = np.split(samples, 2) - for j in range(descriptor_size): - if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: - descriptor[i][j] = True + pos1 = np.ascontiguousarray(pos1) + pos2 = np.ascontiguousarray(pos2) - return descriptor + _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) + + return descriptors def hamming_distance(descriptor_1, descriptor_2): diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/_brief_cy.pyx new file mode 100644 index 00000000..43d54f7a --- /dev/null +++ b/skimage/feature/_brief_cy.pyx @@ -0,0 +1,24 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +cimport numpy as cnp + + +def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors, + Py_ssize_t[:, ::1] keypoints, + int[:, ::1] pos0, int[:, ::1] pos1): + + cdef Py_ssize_t k, d, kr, kc, pr0, pr1, pc0, pc1 + + for p in range(pos0.shape[0]): + pr0 = pos0[p, 0] + 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] + if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]: + descriptors[k, p] = True diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index e769621d..f4c62957 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -13,11 +13,14 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['corner_cy.pyx'], working_path=base_path) + cython(['_brief_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) config.add_extension('corner_cy', sources=['corner_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('_brief_cy', sources=['_brief_cy.c'], + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_texture', sources=['_texture.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('_template', sources=['_template.c'],