From ba9c985abff2d5602d60321a8a3dc1dad27529e3 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 16 Aug 2013 15:01:43 +0530 Subject: [PATCH 001/145] Quick initial implementation of ORB descriptor in Python --- skimage/feature/orb.py | 296 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 skimage/feature/orb.py diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py new file mode 100644 index 00000000..3cc3366c --- /dev/null +++ b/skimage/feature/orb.py @@ -0,0 +1,296 @@ +import numpy as np + +from ..util import img_as_float +from .util import _mask_border_keypoints + + +def descriptor_orb(image, keypoints, keypoints_angle): + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + + mask = _mask_border_keypoints(image, keypoints, 13) + keypoints = keypoints[mask] + keypoints_angle = keypoints_angle[mask] + + pos1 = binary_tests[:, :2] + pos2 = binary_tests[:, 2:] + + descriptors = np.zeros((keypoints.shape[0], 256), dtype=bool) + + for i in range(keypoints.shape[0]): + angle = keypoints_angle[i] + a = np.sin(angle * np.pi / 180.) + b = np.cos(angle * np.pi / 180.) + rotation_matrix = [[b, a], [-a, b]] + steered_pos1 = pos1 * rotation_matrix + steered_pos2 = pos2 * rotation_matrix + for j in range(256): + pr1 = steered_pos1[j][0] + pc1 = steered_pos1[j][1] + pr2 = steered_pos2[j][0] + pc2 = steered_pos2[j][1] + descriptors[i, j] = (image[pr1, pc1] < image[pr2, pc2]) + return descriptors + + +# Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. +binary_tests = np.asarray([[8,-3, 9,5], + [4,2, 7,-12], + [-11,9, -8,2], + [7,-12, 12,-13], + [2,-13, 2,12], + [1,-7, 1,6], + [-2,-10, -2,-4], + [-13,-13, -11,-8], + [-13,-3, -12,-9], + [10,4, 11,9], + [-13,-8, -8,-9], + [-11,7, -9,12], + [7,7, 12,6], + [-4,-5, -3,0], + [-13,2, -12,-3], + [-9,0, -7,5], + [12,-6, 12,-1], + [-3,6, -2,12], + [-6,-13, -4,-8], + [11,-13, 12,-8], + [4,7, 5,1], + [5,-3, 10,-3], + [3,-7, 6,12], + [-8,-7, -6,-2], + [-2,11, -1,-10], + [-13,12, -8,10], + [-7,3, -5,-3], + [-4,2, -3,7], + [-10,-12, -6,11], + [5,-12, 6,-7], + [5,-6, 7,-1], + [1,0, 4,-5], + [9,11, 11,-13], + [4,7, 4,12], + [2,-1, 4,4], + [-4,-12, -2,7], + [-8,-5, -7,-10], + [4,11, 9,12], + [0,-8, 1,-13], + [-13,-2, -8,2], + [-3,-2, -2,3], + [-6,9, -4,-9], + [8,12, 10,7], + [0,9, 1,3], + [7,-5, 11,-10], + [-13,-6, -11,0], + [10,7, 12,1], + [-6,-3, -6,12], + [10,-9, 12,-4], + [-13,8, -8,-12], + [-13,0, -8,-4], + [3,3, 7,8], + [5,7, 10,-7], + [-1,7, 1,-12], + [3,-10, 5,6], + [2,-4, 3,-10], + [-13,0, -13,5], + [-13,-7, -12,12], + [-13,3, -11,8], + [-7,12, -4,7], + [6,-10, 12,8], + [-9,-1, -7,-6], + [-2,-5, 0,12], + [-12,5, -7,5], + [3,-10, 8,-13], + [-7,-7, -4,5], + [-3,-2, -1,-7], + [2,9, 5,-11], + [-11,-13, -5,-13], + [-1,6, 0,-1], + [5,-3, 5,2], + [-4,-13, -4,12], + [-9,-6, -9,6], + [-12,-10, -8,-4], + [10,2, 12,-3], + [7,12, 12,12], + [-7,-13, -6,5], + [-4,9, -3,4], + [7,-1, 12,2], + [-7,6, -5,1], + [-13,11, -12,5], + [-3,7, -2,-6], + [7,-8, 12,-7], + [-13,-7, -11,-12], + [1,-3, 12,12], + [2,-6, 3,0], + [-4,3, -2,-13], + [-1,-13, 1,9], + [7,1, 8,-6], + [1,-1, 3,12], + [9,1, 12,6], + [-1,-9, -1,3], + [-13,-13, -10,5], + [7,7, 10,12], + [12,-5, 12,9], + [6,3, 7,11], + [5,-13, 6,10], + [2,-12, 2,3], + [3,8, 4,-6], + [2,6, 12,-13], + [9,-12, 10,3], + [-8,4, -7,9], + [-11,12, -4,-6], + [1,12, 2,-8], + [6,-9, 7,-4], + [2,3, 3,-2], + [6,3, 11,0], + [3,-3, 8,-8], + [7,8, 9,3], + [-11,-5, -6,-4], + [-10,11, -5,10], + [-5,-8, -3,12], + [-10,5, -9,0], + [8,-1, 12,-6], + [4,-6, 6,-11], + [-10,12, -8,7], + [4,-2, 6,7], + [-2,0, -2,12], + [-5,-8, -5,2], + [7,-6, 10,12], + [-9,-13, -8,-8], + [-5,-13, -5,-2], + [8,-8, 9,-13], + [-9,-11, -9,0], + [1,-8, 1,-2], + [7,-4, 9,1], + [-2,1, -1,-4], + [11,-6, 12,-11], + [-12,-9, -6,4], + [3,7, 7,12], + [5,5, 10,8], + [0,-4, 2,8], + [-9,12, -5,-13], + [0,7, 2,12], + [-1,2, 1,7], + [5,11, 7,-9], + [3,5, 6,-8], + [-13,-4, -8,9], + [-5,9, -3,-3], + [-4,-7, -3,-12], + [6,5, 8,0], + [-7,6, -6,12], + [-13,6, -5,-2], + [1,-10, 3,10], + [4,1, 8,-4], + [-2,-2, 2,-13], + [2,-12, 12,12], + [-2,-13, 0,-6], + [4,1, 9,3], + [-6,-10, -3,-5], + [-3,-13, -1,1], + [7,5, 12,-11], + [4,-2, 5,-7], + [-13,9, -9,-5], + [7,1, 8,6], + [7,-8, 7,6], + [-7,-4, -7,1], + [-8,11, -7,-8], + [-13,6, -12,-8], + [2,4, 3,9], + [10,-5, 12,3], + [-6,-5, -6,7], + [8,-3, 9,-8], + [2,-12, 2,8], + [-11,-2, -10,3], + [-12,-13, -7,-9], + [-11,0, -10,-5], + [5,-3, 11,8], + [-2,-13, -1,12], + [-1,-8, 0,9], + [-13,-11, -12,-5], + [-10,-2, -10,11], + [-3,9, -2,-13], + [2,-3, 3,2], + [-9,-13, -4,0], + [-4,6, -3,-10], + [-4,12, -2,-7], + [-6,-11, -4,9], + [6,-3, 6,11], + [-13,11, -5,5], + [11,11, 12,6], + [7,-5, 12,-2], + [-1,12, 0,7], + [-4,-8, -3,-2], + [-7,1, -6,7], + [-13,-12, -8,-13], + [-7,-2, -6,-8], + [-8,5, -6,-9], + [-5,-1, -4,5], + [-13,7, -8,10], + [1,5, 5,-13], + [1,0, 10,-13], + [9,12, 10,-1], + [5,-8, 10,-9], + [-1,11, 1,-13], + [-9,-3, -6,2], + [-1,-10, 1,12], + [-13,1, -8,-10], + [8,-11, 10,-6], + [2,-13, 3,-6], + [7,-13, 12,-9], + [-10,-10, -5,-7], + [-10,-8, -8,-13], + [4,-6, 8,5], + [3,12, 8,-13], + [-4,2, -3,-3], + [5,-13, 10,-12], + [4,-13, 5,-1], + [-9,9, -4,3], + [0,3, 3,-9], + [-12,1, -6,1], + [3,2, 4,-8], + [-10,-10, -10,9], + [8,-13, 12,12], + [-8,-12, -6,-5], + [2,2, 3,7], + [10,6, 11,-8], + [6,8, 8,-12], + [-7,10, -6,5], + [-3,-9, -3,9], + [-1,-13, -1,5], + [-3,-7, -3,4], + [-8,-2, -8,3], + [4,2, 12,12], + [2,-5, 3,11], + [6,-9, 11,-13], + [3,-1, 7,12], + [11,-1, 12,4], + [-3,0, -3,6], + [4,-11, 4,12], + [2,-4, 2,1], + [-10,-6, -8,1], + [-13,7, -11,1], + [-13,12, -11,-13], + [6,0, 11,-13], + [0,-1, 1,4], + [-13,3, -9,-2], + [-9,8, -6,-3], + [-13,-6, -8,-2], + [5,-9, 8,10], + [2,7, 3,-9], + [-1,-6, -1,-1], + [9,5, 11,-2], + [11,-3, 12,-8], + [3,0, 3,5], + [-1,4, 0,10], + [3,-6, 4,5], + [-13,0, -10,5], + [5,8, 12,11], + [8,9, 9,-6], + [7,-4, 8,-12], + [-10,4, -10,9], + [7,3, 12,4], + [9,-7, 10,-2], + [7,0, 12,-2], + [-1,-6, 0,-11]]) From 03980b10ee5045c1752c83fad5c13c89adf96566 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 17 Aug 2013 16:15:34 +0530 Subject: [PATCH 002/145] Quick Python implementation of FAST corner detector --- skimage/feature/fast.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skimage/feature/fast.py diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py new file mode 100644 index 00000000..6b6ae264 --- /dev/null +++ b/skimage/feature/fast.py @@ -0,0 +1,35 @@ +import numpy as np +from ..util import img_as_float + + +def corner_fast(image, n=9, threshold=0.15): + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + corner_mask = np.zeros(image.shape, dtype=bool) + + test_pixels = np.asarray([[-3, 0], [-3, 1], [-2, 2], [-1, 3], [0, 3], + [1, 3], [2, 2], [3, 1], [3, 0], [3, -1], + [2, -2], [1, -3], [0, -3], [-1, -3], + [-2, -2], [-1, -3]]) + + # TODO : Outsource to Cython + for i in range(3, image.shape[0] - 3): + for j in range(3, image.shape[1] - 3): + test_x = i + test_pixels[:, 0] + test_y = j + test_pixels[:, 1] + intensities = image[test_x, test_y] + low = intensities < image[i, j] - threshold + high = intensities > image[i, j] + threshold + low = np.concatenate(low, low) + high = np.concatenate(high, high) + # How to check if a sequence n * [True] exists in low/high ? + # if n * [True] in low or n * True in high: + # corner_mask[i, j] = True + + corner_x, corner_y = np.where(corner_mask == True) + corners = np.dstack(corner_x, corner_y) + return corners From 5f1976ace2092fda2525111f7d11d2c56b0d7645 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 00:57:12 +0530 Subject: [PATCH 003/145] Implemented FAST corner detector --- bento.info | 3 ++ skimage/feature/__init__.py | 12 +++++- skimage/feature/fast.py | 37 ++++++----------- skimage/feature/fast_cy.pyx | 82 +++++++++++++++++++++++++++++++++++++ skimage/feature/setup.py | 3 ++ 5 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 skimage/feature/fast_cy.pyx diff --git a/bento.info b/bento.info index 30367057..ae547267 100644 --- a/bento.info +++ b/bento.info @@ -102,6 +102,9 @@ Library: Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx + Extension: skimage.feature.fast_cy + Sources: + skimage/feature/fast_cy.pyx Extension: skimage.feature._texture Sources: skimage/feature/_texture.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 4a6518d6..4a98fe9a 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -7,7 +7,10 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_peaks) from .corner_cy import corner_moravec from .template import match_template - +from ._brief import brief, match_keypoints_brief +from .util import pairwise_hamming_distance +from .censure import keypoints_censure +from .fast import corner_fast __all__ = ['daisy', 'hog', @@ -22,4 +25,9 @@ __all__ = ['daisy', 'corner_subpix', 'corner_peaks', 'corner_moravec', - 'match_template'] + 'match_template', + 'brief', + 'pairwise_hamming_distance', + 'match_keypoints_brief', + 'keypoints_censure', + 'corner_fast'] diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 6b6ae264..8194093e 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -1,35 +1,22 @@ import numpy as np -from ..util import img_as_float +from scipy.ndimage.filters import maximum_filter + +from fast_cy import _corner_fast -def corner_fast(image, n=9, threshold=0.15): +def corner_fast(image, n=12, threshold=0.15): image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - image = img_as_float(image) - corner_mask = np.zeros(image.shape, dtype=bool) + image = np.ascontiguousarray(image, dtype=np.double) + corner = _corner_fast(image, n, threshold) - test_pixels = np.asarray([[-3, 0], [-3, 1], [-2, 2], [-1, 3], [0, 3], - [1, 3], [2, 2], [3, 1], [3, 0], [3, -1], - [2, -2], [1, -3], [0, -3], [-1, -3], - [-2, -2], [-1, -3]]) + corner_zero_mask = corner != 0 + c, d = np.nonzero(corner) - # TODO : Outsource to Cython - for i in range(3, image.shape[0] - 3): - for j in range(3, image.shape[1] - 3): - test_x = i + test_pixels[:, 0] - test_y = j + test_pixels[:, 1] - intensities = image[test_x, test_y] - low = intensities < image[i, j] - threshold - high = intensities > image[i, j] + threshold - low = np.concatenate(low, low) - high = np.concatenate(high, high) - # How to check if a sequence n * [True] exists in low/high ? - # if n * [True] in low or n * True in high: - # corner_mask[i, j] = True - - corner_x, corner_y = np.where(corner_mask == True) - corners = np.dstack(corner_x, corner_y) - return corners + maximas = (maximum_filter(corner, (3, 3)) == corner) & corner_zero_mask + x, y = np.where(maximas == True) + + return np.squeeze(np.dstack((x, y))) diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx new file mode 100644 index 00000000..a7af63c2 --- /dev/null +++ b/skimage/feature/fast_cy.pyx @@ -0,0 +1,82 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +import numpy as np + +from ..util import img_as_float + + +def _corner_fast(double[:, ::1] image, int n, double threshold): + + cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + + cdef Py_ssize_t i, j, k, l, m + + cdef char[:] bins + cdef int consecutive_count = 0 + cdef double sum_b + cdef double sum_d + cdef double[:, ::1] corner = np.zeros((rows, cols), dtype=np.double) + + cdef double circle_intensity + + for i in range(3, rows - 3): + for j in range(3, cols - 3): + + bins = np.zeros(16, dtype='S1') + sum_b = 0 + sum_d = 0 + + for k in range(16): + circle_intensity = image[i + rp[k], j + cp[k]] + if circle_intensity > image[i, j] + threshold: + bins[k] = 'b' + elif circle_intensity < image[i, j] - threshold: + bins[k] = 'd' + else: + bins[k] = 's' + + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'b': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + elif bins[m] == 'd': + sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + if sum_d > sum_b: + corner[i, j] = sum_d + else: + corner[i, j] = sum_b + break + else: + consecutive_count = 0 + + if corner[i, j] == 0: + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'd': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + elif bins[m] == 'd': + sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + if sum_d > sum_b: + corner[i, j] = sum_d + else: + corner[i, j] = sum_b + break + else: + consecutive_count = 0 + + return np.asarray(corner) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 7df64c32..039d234f 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -14,6 +14,7 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) + cython(['fast_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) @@ -22,6 +23,8 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('censure_cy', sources=['censure_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('fast_cy', sources=['fast_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'], From 5c6420d58441c9f24f3c6a97a03ac1ffbff19e4c Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 01:24:29 +0530 Subject: [PATCH 004/145] Adding docstrings and commenting the code --- skimage/feature/fast.py | 40 +++++++++++++++++++++++++++++++------ skimage/feature/fast_cy.pyx | 21 +++++++++++-------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 8194093e..36a4c7e1 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -6,17 +6,45 @@ from fast_cy import _corner_fast def corner_fast(image, n=12, threshold=0.15): + """Extract FAST corners for a given image. + + Parameters + ---------- + image : 2D ndarray + Input image. + n : integer + Number of consecutive pixels out of 16 pixels on the circle that + should be brighter or darker with respect to test pixel above the + `threshold` so as to classify the test pixel as a FAST corner. Also + stands for the n in `FAST-n` corner detector. + threshold : float + Threshold used in deciding whether the pixels on the circle are + brighter, darker or similar to the test pixel. Decrease the threshold + when more corners are desired and vice-versa. + + Returns + ------- + corners : (N, 2) ndarray + Location i.e. (row, col) of extracted FAST corners. + + References + ---------- + .. [1] Edward Rosten and Tom Drummond + "Machine Learning for high-speed corner detection", + http://www.edwardrosten.com/work/rosten_2006_machine.pdf + + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") image = np.ascontiguousarray(image, dtype=np.double) - corner = _corner_fast(image, n, threshold) + corner_response = _corner_fast(image, n, threshold) - corner_zero_mask = corner != 0 - c, d = np.nonzero(corner) - - maximas = (maximum_filter(corner, (3, 3)) == corner) & corner_zero_mask + # Non-maximal Suppression + corner_zero_mask = corner_response != 0 + maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask x, y = np.where(maximas == True) - return np.squeeze(np.dstack((x, y))) + corners = np.squeeze(np.dstack((x, y))) + return corners diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index a7af63c2..d80aa318 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -8,7 +8,7 @@ import numpy as np from ..util import img_as_float -def _corner_fast(double[:, ::1] image, int n, double threshold): +def _corner_response_fast(double[:, ::1] image, int n, double threshold): cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -22,7 +22,7 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int consecutive_count = 0 cdef double sum_b cdef double sum_d - cdef double[:, ::1] corner = np.zeros((rows, cols), dtype=np.double) + cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) cdef double circle_intensity @@ -36,10 +36,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for k in range(16): circle_intensity = image[i + rp[k], j + cp[k]] if circle_intensity > image[i, j] + threshold: + # Brighter pixel bins[k] = 'b' elif circle_intensity < image[i, j] - threshold: + # Darker pixel bins[k] = 'd' else: + # Similar pixel bins[k] = 's' consecutive_count = 0 @@ -52,15 +55,16 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold elif bins[m] == 'd': sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner if sum_d > sum_b: - corner[i, j] = sum_d + corner_response[i, j] = sum_d else: - corner[i, j] = sum_b + corner_response[i, j] = sum_b break else: consecutive_count = 0 - if corner[i, j] == 0: + if corner_response[i, j] == 0: consecutive_count = 0 for l in range(15 + n): if bins[l % 16] == 'd': @@ -71,12 +75,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold elif bins[m] == 'd': sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner if sum_d > sum_b: - corner[i, j] = sum_d + corner_response[i, j] = sum_d else: - corner[i, j] = sum_b + corner_response[i, j] = sum_b break else: consecutive_count = 0 - return np.asarray(corner) + return np.asarray(corner_response) From cfcefe192c5bd9da0ad2c9c920f8eb3d49d09b53 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 02:33:29 +0530 Subject: [PATCH 005/145] Adding high speed test for n>=12 --- skimage/feature/fast_cy.pyx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index d80aa318..43ffee08 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -8,7 +8,7 @@ import numpy as np from ..util import img_as_float -def _corner_response_fast(double[:, ::1] image, int n, double threshold): +def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -19,7 +19,8 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): cdef Py_ssize_t i, j, k, l, m cdef char[:] bins - cdef int consecutive_count = 0 + cdef int consecutive_count, speed_sum_b, speed_sum_d + cdef int sp cdef double sum_b cdef double sum_d cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) @@ -30,6 +31,8 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): for j in range(3, cols - 3): bins = np.zeros(16, dtype='S1') + speed_sum_b = 0 + speed_sum_d = 0 sum_b = 0 sum_d = 0 @@ -45,6 +48,16 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): # Similar pixel bins[k] = 's' + # High speed test for n>=12 + if n >= 12: + for k in range(4): + if bins[4 * k] == 'b': + speed_sum_b += 1 + elif bins[4 * k] == 'd': + speed_sum_d += 1 + if speed_sum_d < 3 and speed_sum_b < 3: + continue + consecutive_count = 0 for l in range(15 + n): if bins[l % 16] == 'b': From 5a886e69c33e3e1f70ca9821cb543de62b1d3eb1 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 01:19:31 +0530 Subject: [PATCH 006/145] Making various minor optimizations --- skimage/feature/fast.py | 6 +++--- skimage/feature/fast_cy.pyx | 28 ++++++++++++---------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 36a4c7e1..4a7b84e6 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -12,15 +12,15 @@ def corner_fast(image, n=12, threshold=0.15): ---------- image : 2D ndarray Input image. - n : integer + n : int Number of consecutive pixels out of 16 pixels on the circle that should be brighter or darker with respect to test pixel above the `threshold` so as to classify the test pixel as a FAST corner. Also stands for the n in `FAST-n` corner detector. threshold : float Threshold used in deciding whether the pixels on the circle are - brighter, darker or similar to the test pixel. Decrease the threshold - when more corners are desired and vice-versa. + brighter, darker or similar w.r.t. the test pixel. Decrease the + threshold when more corners are desired and vice-versa. Returns ------- diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index 43ffee08..d2292eac 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -5,11 +5,8 @@ import numpy as np -from ..util import img_as_float - def _corner_fast(double[:, ::1] image, int n, double threshold): - cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -18,11 +15,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef Py_ssize_t i, j, k, l, m - cdef char[:] bins + cdef char[:] bins = np.zeros(16, dtype=np.uint8) cdef int consecutive_count, speed_sum_b, speed_sum_d cdef int sp - cdef double sum_b - cdef double sum_d + cdef double sum_b, sum_d, current_pixel cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) cdef double circle_intensity @@ -30,7 +26,7 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for i in range(3, rows - 3): for j in range(3, cols - 3): - bins = np.zeros(16, dtype='S1') + current_pixel = image[i, j] speed_sum_b = 0 speed_sum_d = 0 sum_b = 0 @@ -38,10 +34,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for k in range(16): circle_intensity = image[i + rp[k], j + cp[k]] - if circle_intensity > image[i, j] + threshold: + if circle_intensity > current_pixel + threshold: # Brighter pixel bins[k] = 'b' - elif circle_intensity < image[i, j] - threshold: + elif circle_intensity < current_pixel - threshold: # Darker pixel bins[k] = 'd' else: @@ -50,10 +46,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): # High speed test for n>=12 if n >= 12: - for k in range(4): - if bins[4 * k] == 'b': + for k in range(0, 16, 4): + if bins[k] == 'b': speed_sum_b += 1 - elif bins[4 * k] == 'd': + elif bins[k] == 'd': speed_sum_d += 1 if speed_sum_d < 3 and speed_sum_b < 3: continue @@ -65,9 +61,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold elif bins[m] == 'd': - sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold # Finding the response of the corner if sum_d > sum_b: corner_response[i, j] = sum_d @@ -85,9 +81,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold elif bins[m] == 'd': - sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold # Finding the response of the corner if sum_d > sum_b: corner_response[i, j] = sum_d From b8958ccee08403abf48da76f8cca9831eaccc6da Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 01:44:04 +0530 Subject: [PATCH 007/145] Transferring all the FAST code to corner.py and corner_cy.pyx --- bento.info | 3 -- skimage/feature/__init__.py | 3 +- skimage/feature/corner.py | 49 +++++++++++++++++++ skimage/feature/corner_cy.pyx | 90 +++++++++++++++++++++++++++++++++++ skimage/feature/setup.py | 3 -- 5 files changed, 140 insertions(+), 8 deletions(-) diff --git a/bento.info b/bento.info index ae547267..30367057 100644 --- a/bento.info +++ b/bento.info @@ -102,9 +102,6 @@ Library: Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx - Extension: skimage.feature.fast_cy - Sources: - skimage/feature/fast_cy.pyx Extension: skimage.feature._texture Sources: skimage/feature/_texture.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 4a98fe9a..c16edb87 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -4,13 +4,12 @@ from .texture import greycomatrix, greycoprops, local_binary_pattern from .peak import peak_local_max from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, - corner_peaks) + corner_peaks, corner_fast) from .corner_cy import corner_moravec from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance from .censure import keypoints_censure -from .fast import corner_fast __all__ = ['daisy', 'hog', diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index a025a7e4..bbb7fde5 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -1,10 +1,13 @@ import numpy as np from scipy import ndimage from scipy import stats +from scipy.ndimage.filters import maximum_filter from skimage.color import rgb2grey from skimage.util import img_as_float, pad from skimage.feature import peak_local_max +from corner_cy import _corner_fast + def _compute_derivatives(image): """Compute derivatives in x and y direction using the Sobel operator. @@ -542,3 +545,49 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return np.transpose(peaks.nonzero()) else: return peaks + + +def corner_fast(image, n=12, threshold=0.15): + + """Extract FAST corners for a given image. + + Parameters + ---------- + image : 2D ndarray + Input image. + n : int + Number of consecutive pixels out of 16 pixels on the circle that + should be brighter or darker with respect to test pixel above the + `threshold` so as to classify the test pixel as a FAST corner. Also + stands for the n in `FAST-n` corner detector. + threshold : float + Threshold used in deciding whether the pixels on the circle are + brighter, darker or similar w.r.t. the test pixel. Decrease the + threshold when more corners are desired and vice-versa. + + Returns + ------- + corners : (N, 2) ndarray + Location i.e. (row, col) of extracted FAST corners. + + References + ---------- + .. [1] Edward Rosten and Tom Drummond + "Machine Learning for high-speed corner detection", + http://www.edwardrosten.com/work/rosten_2006_machine.pdf + + """ + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = np.ascontiguousarray(image, dtype=np.double) + corner_response = _corner_fast(image, n, threshold) + + # Non-maximal Suppression + corner_zero_mask = corner_response != 0 + maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask + x, y = np.where(maximas == True) + + corners = np.squeeze(np.dstack((x, y))) + return corners diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index c459ee92..56112211 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -80,3 +80,93 @@ def corner_moravec(image, Py_ssize_t window_size=1): out[r, c] = min_msum return np.asarray(out) + + +def _corner_fast(double[:, ::1] image, int n, double threshold): + cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + + cdef Py_ssize_t i, j, k, l, m + + cdef char[:] bins = np.zeros(16, dtype=np.uint8) + cdef int consecutive_count, speed_sum_b, speed_sum_d + cdef int sp + cdef double sum_b, sum_d, current_pixel + cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) + + cdef double circle_intensity + + for i in range(3, rows - 3): + for j in range(3, cols - 3): + + current_pixel = image[i, j] + speed_sum_b = 0 + speed_sum_d = 0 + sum_b = 0 + sum_d = 0 + + for k in range(16): + circle_intensity = image[i + rp[k], j + cp[k]] + if circle_intensity > current_pixel + threshold: + # Brighter pixel + bins[k] = 'b' + elif circle_intensity < current_pixel - threshold: + # Darker pixel + bins[k] = 'd' + else: + # Similar pixel + bins[k] = 's' + + # High speed test for n>=12 + if n >= 12: + for k in range(0, 16, 4): + if bins[k] == 'b': + speed_sum_b += 1 + elif bins[k] == 'd': + speed_sum_d += 1 + if speed_sum_d < 3 and speed_sum_b < 3: + continue + + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'b': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold + elif bins[m] == 'd': + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner + if sum_d > sum_b: + corner_response[i, j] = sum_d + else: + corner_response[i, j] = sum_b + break + else: + consecutive_count = 0 + + if corner_response[i, j] == 0: + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'd': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold + elif bins[m] == 'd': + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner + if sum_d > sum_b: + corner_response[i, j] = sum_d + else: + corner_response[i, j] = sum_b + break + else: + consecutive_count = 0 + + return np.asarray(corner_response) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 039d234f..7df64c32 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -14,7 +14,6 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) - cython(['fast_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) @@ -23,8 +22,6 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('censure_cy', sources=['censure_cy.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('fast_cy', sources=['fast_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'], From 1fe124102ddbac22a5f1cb949489ba66049697f6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 18:59:17 +0530 Subject: [PATCH 008/145] Using inline function in corner_cy._corner_fast --- skimage/feature/corner_cy.pyx | 76 +++++++++++++++-------------------- skimage/feature/orb.py | 2 +- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 56112211..96588eeb 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -82,9 +82,35 @@ def corner_moravec(image, Py_ssize_t window_size=1): return np.asarray(out) +cdef int[:] RP = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) +cdef int[:] CP = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + + +cdef inline _get_corner_response(double[:, ::1] image, int i, int j, char[:] bins, char check_state, int n, double threshold, double[:, ::1] corner_response): + cdef int consecutive_count = 0 + cdef double sum_b = 0 + cdef double sum_d = 0 + cdef double curr_pixel = image[i, j] + cdef Py_ssize_t l, m + for l in range(15 + n): + if bins[l % 16] == check_state: + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + RP[m], j + CP[m]] - curr_pixel - threshold + elif bins[m] == 'd': + sum_d += curr_pixel - image[i + RP[m], j + CP[m]] - threshold + # Finding the response of the corner + if sum_d > sum_b: + corner_response[i, j] = sum_d + else: + corner_response[i, j] = sum_b + else: + consecutive_count = 0 + + def _corner_fast(double[:, ::1] image, int n, double threshold): - cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef Py_ssize_t rows = image.shape[0] cdef Py_ssize_t cols = image.shape[1] @@ -92,9 +118,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef Py_ssize_t i, j, k, l, m cdef char[:] bins = np.zeros(16, dtype=np.uint8) - cdef int consecutive_count, speed_sum_b, speed_sum_d + cdef int speed_sum_b, speed_sum_d cdef int sp - cdef double sum_b, sum_d, current_pixel + cdef double current_pixel cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) cdef double circle_intensity @@ -105,11 +131,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): current_pixel = image[i, j] speed_sum_b = 0 speed_sum_d = 0 - sum_b = 0 - sum_d = 0 for k in range(16): - circle_intensity = image[i + rp[k], j + cp[k]] + circle_intensity = image[i + RP[k], j + CP[k]] if circle_intensity > current_pixel + threshold: # Brighter pixel bins[k] = 'b' @@ -130,43 +154,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if speed_sum_d < 3 and speed_sum_b < 3: continue - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'b': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 + _get_corner_response(image, i, j, bins, 'b', n, threshold, corner_response) if corner_response[i, j] == 0: - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'd': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 + _get_corner_response(image, i, j, bins, 'd', n, threshold, corner_response) return np.asarray(corner_response) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 3cc3366c..d5189a32 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -34,7 +34,7 @@ def descriptor_orb(image, keypoints, keypoints_angle): pr2 = steered_pos2[j][0] pc2 = steered_pos2[j][1] descriptors[i, j] = (image[pr1, pc1] < image[pr2, pc2]) - return descriptors + return descriptors # Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. From 68fa6b015bff1726f59d93e765ef68d1f16ccaac Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 23:02:41 +0530 Subject: [PATCH 009/145] Deleting FAST files --- skimage/feature/fast.py | 50 ------------------- skimage/feature/fast_cy.pyx | 96 ------------------------------------- 2 files changed, 146 deletions(-) delete mode 100644 skimage/feature/fast.py delete mode 100644 skimage/feature/fast_cy.pyx diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py deleted file mode 100644 index 4a7b84e6..00000000 --- a/skimage/feature/fast.py +++ /dev/null @@ -1,50 +0,0 @@ -import numpy as np -from scipy.ndimage.filters import maximum_filter - -from fast_cy import _corner_fast - - -def corner_fast(image, n=12, threshold=0.15): - - """Extract FAST corners for a given image. - - Parameters - ---------- - image : 2D ndarray - Input image. - n : int - Number of consecutive pixels out of 16 pixels on the circle that - should be brighter or darker with respect to test pixel above the - `threshold` so as to classify the test pixel as a FAST corner. Also - stands for the n in `FAST-n` corner detector. - threshold : float - Threshold used in deciding whether the pixels on the circle are - brighter, darker or similar w.r.t. the test pixel. Decrease the - threshold when more corners are desired and vice-versa. - - Returns - ------- - corners : (N, 2) ndarray - Location i.e. (row, col) of extracted FAST corners. - - References - ---------- - .. [1] Edward Rosten and Tom Drummond - "Machine Learning for high-speed corner detection", - http://www.edwardrosten.com/work/rosten_2006_machine.pdf - - """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") - - image = np.ascontiguousarray(image, dtype=np.double) - corner_response = _corner_fast(image, n, threshold) - - # Non-maximal Suppression - corner_zero_mask = corner_response != 0 - maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask - x, y = np.where(maximas == True) - - corners = np.squeeze(np.dstack((x, y))) - return corners diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx deleted file mode 100644 index d2292eac..00000000 --- a/skimage/feature/fast_cy.pyx +++ /dev/null @@ -1,96 +0,0 @@ -#cython: cdivision=True -#cython: boundscheck=False -#cython: nonecheck=False -#cython: wraparound=False - -import numpy as np - - -def _corner_fast(double[:, ::1] image, int n, double threshold): - cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - - cdef Py_ssize_t rows = image.shape[0] - cdef Py_ssize_t cols = image.shape[1] - - cdef Py_ssize_t i, j, k, l, m - - cdef char[:] bins = np.zeros(16, dtype=np.uint8) - cdef int consecutive_count, speed_sum_b, speed_sum_d - cdef int sp - cdef double sum_b, sum_d, current_pixel - cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) - - cdef double circle_intensity - - for i in range(3, rows - 3): - for j in range(3, cols - 3): - - current_pixel = image[i, j] - speed_sum_b = 0 - speed_sum_d = 0 - sum_b = 0 - sum_d = 0 - - for k in range(16): - circle_intensity = image[i + rp[k], j + cp[k]] - if circle_intensity > current_pixel + threshold: - # Brighter pixel - bins[k] = 'b' - elif circle_intensity < current_pixel - threshold: - # Darker pixel - bins[k] = 'd' - else: - # Similar pixel - bins[k] = 's' - - # High speed test for n>=12 - if n >= 12: - for k in range(0, 16, 4): - if bins[k] == 'b': - speed_sum_b += 1 - elif bins[k] == 'd': - speed_sum_d += 1 - if speed_sum_d < 3 and speed_sum_b < 3: - continue - - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'b': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 - - if corner_response[i, j] == 0: - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'd': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 - - return np.asarray(corner_response) From df43c5cfc798ec297635c45dec3c107f0059ff6e Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 00:01:15 +0530 Subject: [PATCH 010/145] Converting image to float --- skimage/feature/corner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index bbb7fde5..f2864d8b 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -581,7 +581,8 @@ def corner_fast(image, n=12, threshold=0.15): if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - image = np.ascontiguousarray(image, dtype=np.double) + image = img_as_float(image) + image = np.ascontiguousarray(image) corner_response = _corner_fast(image, n, threshold) # Non-maximal Suppression From 53c9e3e6e4f77eb84ffc710958fa656330f31fcb Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 00:37:34 +0530 Subject: [PATCH 011/145] Adding additional parameter for setting shape for Non-max suppression --- skimage/feature/corner.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index f2864d8b..5525a8b3 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -547,7 +547,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return peaks -def corner_fast(image, n=12, threshold=0.15): +def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): """Extract FAST corners for a given image. @@ -564,6 +564,9 @@ def corner_fast(image, n=12, threshold=0.15): Threshold used in deciding whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. + non_max_shape : tuple of two odd int + Represents the area in which Non-maximal suppression is applied. + By default, a 3 x 3 area is used. Returns ------- @@ -587,7 +590,7 @@ def corner_fast(image, n=12, threshold=0.15): # Non-maximal Suppression corner_zero_mask = corner_response != 0 - maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask + maximas = (maximum_filter(corner_response, non_max_shape) == corner_response) & corner_zero_mask x, y = np.where(maximas == True) corners = np.squeeze(np.dstack((x, y))) From fbc15c96a82e5397937b851c47e53b456931bd2f Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 04:06:56 +0530 Subject: [PATCH 012/145] Wrapping lines; returning corner_response at a pixel --- skimage/feature/corner_cy.pyx | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 96588eeb..37b26a8c 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -86,7 +86,10 @@ cdef int[:] RP = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) cdef int[:] CP = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) -cdef inline _get_corner_response(double[:, ::1] image, int i, int j, char[:] bins, char check_state, int n, double threshold, double[:, ::1] corner_response): +cdef inline _get_fast_corner_response(double[:, ::1] image, Py_ssize_t i, + Py_ssize_t j, char[:] bins, + char check_state, int n, + double threshold): cdef int consecutive_count = 0 cdef double sum_b = 0 cdef double sum_d = 0 @@ -98,16 +101,19 @@ cdef inline _get_corner_response(double[:, ::1] image, int i, int j, char[:] bin if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b += image[i + RP[m], j + CP[m]] - curr_pixel - threshold + sum_b = (sum_b + image[i + RP[m], j + CP[m]] + - curr_pixel - threshold) elif bins[m] == 'd': - sum_d += curr_pixel - image[i + RP[m], j + CP[m]] - threshold + sum_d = (sum_d + curr_pixel - image[i + RP[m], j + + CP[m]] - threshold) # Finding the response of the corner if sum_d > sum_b: - corner_response[i, j] = sum_d + return sum_d else: - corner_response[i, j] = sum_b + return sum_b else: consecutive_count = 0 + return 0 def _corner_fast(double[:, ::1] image, int n, double threshold): @@ -121,7 +127,8 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int speed_sum_b, speed_sum_d cdef int sp cdef double current_pixel - cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) + cdef double[:, ::1] corner_response = np.zeros((rows, cols), + dtype=np.double) cdef double circle_intensity @@ -154,9 +161,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if speed_sum_d < 3 and speed_sum_b < 3: continue - _get_corner_response(image, i, j, bins, 'b', n, threshold, corner_response) + corner_response[i, j] = _get_fast_corner_response(image, i, j, + bins, 'b', n, + threshold) if corner_response[i, j] == 0: - _get_corner_response(image, i, j, bins, 'd', n, threshold, corner_response) + corner_response[i, j] = _get_fast_corner_response(image, i, j, + bins, 'd', + n, threshold) return np.asarray(corner_response) From e06415929b3ff122b193d1c70c278878a8e82133 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 17:57:30 +0530 Subject: [PATCH 013/145] Returning the corner response --- skimage/feature/corner.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 5525a8b3..0156dcb4 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -547,7 +547,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return peaks -def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): +def corner_fast(image, n=12, threshold=0.15): """Extract FAST corners for a given image. @@ -564,9 +564,6 @@ def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): Threshold used in deciding whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. - non_max_shape : tuple of two odd int - Represents the area in which Non-maximal suppression is applied. - By default, a 3 x 3 area is used. Returns ------- @@ -586,12 +583,4 @@ def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): image = img_as_float(image) image = np.ascontiguousarray(image) - corner_response = _corner_fast(image, n, threshold) - - # Non-maximal Suppression - corner_zero_mask = corner_response != 0 - maximas = (maximum_filter(corner_response, non_max_shape) == corner_response) & corner_zero_mask - x, y = np.where(maximas == True) - - corners = np.squeeze(np.dstack((x, y))) - return corners + return _corner_fast(image, n, threshold) From 3a37505d228bed337698e97c433dea96052cd388 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 20:06:11 +0530 Subject: [PATCH 014/145] More optimizations --- skimage/feature/corner_cy.pyx | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 37b26a8c..1da25907 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -89,7 +89,8 @@ cdef int[:] CP = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) cdef inline _get_fast_corner_response(double[:, ::1] image, Py_ssize_t i, Py_ssize_t j, char[:] bins, char check_state, int n, - double threshold): + double threshold, + double[:] circle_intensities): cdef int consecutive_count = 0 cdef double sum_b = 0 cdef double sum_d = 0 @@ -101,11 +102,11 @@ cdef inline _get_fast_corner_response(double[:, ::1] image, Py_ssize_t i, if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b = (sum_b + image[i + RP[m], j + CP[m]] + sum_b = (sum_b + circle_intensities[m] - curr_pixel - threshold) elif bins[m] == 'd': - sum_d = (sum_d + curr_pixel - image[i + RP[m], j - + CP[m]] - threshold) + sum_d = (sum_d + curr_pixel - circle_intensities[m] + - threshold) # Finding the response of the corner if sum_d > sum_b: return sum_d @@ -127,10 +128,11 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int speed_sum_b, speed_sum_d cdef int sp cdef double current_pixel + cdef double lower_threshold, upper_threshold cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) - cdef double circle_intensity + cdef double[:] circle_intensities = np.zeros(16, dtype=np.double) for i in range(3, rows - 3): for j in range(3, cols - 3): @@ -138,13 +140,15 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): current_pixel = image[i, j] speed_sum_b = 0 speed_sum_d = 0 + lower_threshold = current_pixel - threshold + upper_threshold = current_pixel + threshold for k in range(16): - circle_intensity = image[i + RP[k], j + CP[k]] - if circle_intensity > current_pixel + threshold: + circle_intensities[k] = image[i + RP[k], j + CP[k]] + if circle_intensities[k] > upper_threshold: # Brighter pixel bins[k] = 'b' - elif circle_intensity < current_pixel - threshold: + elif circle_intensities[k] < lower_threshold: # Darker pixel bins[k] = 'd' else: @@ -163,11 +167,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): corner_response[i, j] = _get_fast_corner_response(image, i, j, bins, 'b', n, - threshold) + threshold, + circle_intensities) if corner_response[i, j] == 0: corner_response[i, j] = _get_fast_corner_response(image, i, j, bins, 'd', - n, threshold) + n, threshold, + circle_intensities) return np.asarray(corner_response) From 97f0a3886c0b2ee0b79ea6defe4b4c0ba5a6c349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 23 Aug 2013 22:39:11 +0200 Subject: [PATCH 015/145] Fix some performance regressions in Cython implementation of FAST --- skimage/feature/corner_cy.pyx | 36 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 1da25907..60e4cdbf 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -82,22 +82,18 @@ def corner_moravec(image, Py_ssize_t window_size=1): return np.asarray(out) -cdef int[:] RP = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) -cdef int[:] CP = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - - -cdef inline _get_fast_corner_response(double[:, ::1] image, Py_ssize_t i, - Py_ssize_t j, char[:] bins, - char check_state, int n, - double threshold, - double[:] circle_intensities): +cdef inline double _get_fast_corner_response(double[:, ::1] image, + Py_ssize_t i, Py_ssize_t j, + char[:] bins, char state, int n, + double threshold, + double[:] circle_intensities): cdef int consecutive_count = 0 cdef double sum_b = 0 cdef double sum_d = 0 cdef double curr_pixel = image[i, j] cdef Py_ssize_t l, m for l in range(15 + n): - if bins[l % 16] == check_state: + if bins[l % 16] == state: consecutive_count += 1 if consecutive_count == n: for m in range(16): @@ -132,6 +128,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) + cdef char[:] rp = np.array([-3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 2, 1, 0, + -1, -2, -1, -3], dtype=np.int8) + cdef char[:] cp = np.array([0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, + -3, -3, -2, -3], dtype=np.int8) cdef double[:] circle_intensities = np.zeros(16, dtype=np.double) for i in range(3, rows - 3): @@ -144,7 +144,7 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): upper_threshold = current_pixel + threshold for k in range(16): - circle_intensities[k] = image[i + RP[k], j + CP[k]] + circle_intensities[k] = image[i + rp[k], j + cp[k]] if circle_intensities[k] > upper_threshold: # Brighter pixel bins[k] = 'b' @@ -165,15 +165,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if speed_sum_d < 3 and speed_sum_b < 3: continue - corner_response[i, j] = _get_fast_corner_response(image, i, j, - bins, 'b', n, - threshold, - circle_intensities) + corner_response[i, j] = \ + _get_fast_corner_response(image, i, j, bins, 'b', n, + threshold, circle_intensities) if corner_response[i, j] == 0: - corner_response[i, j] = _get_fast_corner_response(image, i, j, - bins, 'd', - n, threshold, - circle_intensities) + corner_response[i, j] = \ + _get_fast_corner_response(image, i, j, bins, 'd', n, + threshold, circle_intensities) return np.asarray(corner_response) From 2e0882994acd4ea429218c970bbcd2d8bc670f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 24 Aug 2013 08:51:09 +0200 Subject: [PATCH 016/145] Fix some performance regressions in Cython implementation of FAST --- skimage/feature/corner_cy.pyx | 80 +++++++++++++++-------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 60e4cdbf..248a3165 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -82,66 +82,51 @@ def corner_moravec(image, Py_ssize_t window_size=1): return np.asarray(out) -cdef inline double _get_fast_corner_response(double[:, ::1] image, - Py_ssize_t i, Py_ssize_t j, - char[:] bins, char state, int n, - double threshold, - double[:] circle_intensities): - cdef int consecutive_count = 0 - cdef double sum_b = 0 - cdef double sum_d = 0 - cdef double curr_pixel = image[i, j] +cdef inline double _corner_fast_response(double curr_pixel, + double* circle_intensities, + char* bins, char state, char n): + cdef char consecutive_count = 0 + cdef double curr_response cdef Py_ssize_t l, m for l in range(15 + n): if bins[l % 16] == state: consecutive_count += 1 if consecutive_count == n: + curr_response = 0 for m in range(16): - if bins[m] == 'b': - sum_b = (sum_b + circle_intensities[m] - - curr_pixel - threshold) - elif bins[m] == 'd': - sum_d = (sum_d + curr_pixel - circle_intensities[m] - - threshold) - # Finding the response of the corner - if sum_d > sum_b: - return sum_d - else: - return sum_b + curr_response += abs(circle_intensities[m] - curr_pixel) + return curr_response else: consecutive_count = 0 return 0 -def _corner_fast(double[:, ::1] image, int n, double threshold): +def _corner_fast(double[:, ::1] image, char n, double threshold): cdef Py_ssize_t rows = image.shape[0] cdef Py_ssize_t cols = image.shape[1] - cdef Py_ssize_t i, j, k, l, m + cdef Py_ssize_t i, j, k - cdef char[:] bins = np.zeros(16, dtype=np.uint8) - cdef int speed_sum_b, speed_sum_d - cdef int sp - cdef double current_pixel + cdef char speed_sum_b, speed_sum_d + cdef double curr_pixel cdef double lower_threshold, upper_threshold - cdef double[:, ::1] corner_response = np.zeros((rows, cols), - dtype=np.double) + cdef double[:, ::1] corner_response = np.empty((rows, cols), + dtype=np.double) - cdef char[:] rp = np.array([-3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 2, 1, 0, - -1, -2, -1, -3], dtype=np.int8) - cdef char[:] cp = np.array([0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, - -3, -3, -2, -3], dtype=np.int8) - cdef double[:] circle_intensities = np.zeros(16, dtype=np.double) + cdef char *rp = [-3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -1, -3] + cdef char *cp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -3] + cdef char bins[16] + cdef double circle_intensities[16] + + cdef double curr_response for i in range(3, rows - 3): for j in range(3, cols - 3): - current_pixel = image[i, j] - speed_sum_b = 0 - speed_sum_d = 0 - lower_threshold = current_pixel - threshold - upper_threshold = current_pixel + threshold + curr_pixel = image[i, j] + lower_threshold = curr_pixel - threshold + upper_threshold = curr_pixel + threshold for k in range(16): circle_intensities[k] = image[i + rp[k], j + cp[k]] @@ -157,21 +142,26 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): # High speed test for n>=12 if n >= 12: + speed_sum_b = 0 + speed_sum_d = 0 for k in range(0, 16, 4): if bins[k] == 'b': speed_sum_b += 1 elif bins[k] == 'd': speed_sum_d += 1 if speed_sum_d < 3 and speed_sum_b < 3: + corner_response[i, j] = 0 continue - corner_response[i, j] = \ - _get_fast_corner_response(image, i, j, bins, 'b', n, - threshold, circle_intensities) + curr_response = \ + _corner_fast_response(curr_pixel, circle_intensities, + bins, 'b', n) - if corner_response[i, j] == 0: - corner_response[i, j] = \ - _get_fast_corner_response(image, i, j, bins, 'd', n, - threshold, circle_intensities) + if curr_response == 0: + curr_response = \ + _corner_fast_response(curr_pixel, circle_intensities, + bins, 'd', n) + + corner_response[i, j] = curr_response return np.asarray(corner_response) From aeef8ef3e7225ec777215b23d7bacf8856ff86bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 24 Aug 2013 17:27:28 +0200 Subject: [PATCH 017/145] Fix invalid circle coordinates --- skimage/feature/corner_cy.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 248a3165..e2f2506f 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -114,8 +114,8 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef double[:, ::1] corner_response = np.empty((rows, cols), dtype=np.double) - cdef char *rp = [-3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -1, -3] - cdef char *cp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -3] + cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] + cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] cdef char bins[16] cdef double circle_intensities[16] From 63b47a7cb6d6aec233e96e92d57c0165f1f58888 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 16 Aug 2013 15:01:43 +0530 Subject: [PATCH 018/145] Quick initial implementation of ORB descriptor in Python --- skimage/feature/orb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index d5189a32..009d1276 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -34,6 +34,7 @@ def descriptor_orb(image, keypoints, keypoints_angle): pr2 = steered_pos2[j][0] pc2 = steered_pos2[j][1] descriptors[i, j] = (image[pr1, pc1] < image[pr2, pc2]) + return descriptors From 8fd9a77a5afb1ccaf1cef281b56c3dd6ac4c19dd Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 17 Aug 2013 16:15:34 +0530 Subject: [PATCH 019/145] Quick Python implementation of FAST corner detector --- skimage/feature/fast.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skimage/feature/fast.py diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py new file mode 100644 index 00000000..6b6ae264 --- /dev/null +++ b/skimage/feature/fast.py @@ -0,0 +1,35 @@ +import numpy as np +from ..util import img_as_float + + +def corner_fast(image, n=9, threshold=0.15): + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + corner_mask = np.zeros(image.shape, dtype=bool) + + test_pixels = np.asarray([[-3, 0], [-3, 1], [-2, 2], [-1, 3], [0, 3], + [1, 3], [2, 2], [3, 1], [3, 0], [3, -1], + [2, -2], [1, -3], [0, -3], [-1, -3], + [-2, -2], [-1, -3]]) + + # TODO : Outsource to Cython + for i in range(3, image.shape[0] - 3): + for j in range(3, image.shape[1] - 3): + test_x = i + test_pixels[:, 0] + test_y = j + test_pixels[:, 1] + intensities = image[test_x, test_y] + low = intensities < image[i, j] - threshold + high = intensities > image[i, j] + threshold + low = np.concatenate(low, low) + high = np.concatenate(high, high) + # How to check if a sequence n * [True] exists in low/high ? + # if n * [True] in low or n * True in high: + # corner_mask[i, j] = True + + corner_x, corner_y = np.where(corner_mask == True) + corners = np.dstack(corner_x, corner_y) + return corners From 6b3751a52f9b580592b03c6874819e565b5728c0 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 00:57:12 +0530 Subject: [PATCH 020/145] Implemented FAST corner detector --- bento.info | 3 ++ skimage/feature/__init__.py | 1 + skimage/feature/fast.py | 37 ++++++----------- skimage/feature/fast_cy.pyx | 82 +++++++++++++++++++++++++++++++++++++ skimage/feature/setup.py | 3 ++ 5 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 skimage/feature/fast_cy.pyx diff --git a/bento.info b/bento.info index 30367057..ae547267 100644 --- a/bento.info +++ b/bento.info @@ -102,6 +102,9 @@ Library: Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx + Extension: skimage.feature.fast_cy + Sources: + skimage/feature/fast_cy.pyx Extension: skimage.feature._texture Sources: skimage/feature/_texture.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c16edb87..c0adb50d 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -10,6 +10,7 @@ from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance from .censure import keypoints_censure +from .fast import corner_fast __all__ = ['daisy', 'hog', diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 6b6ae264..8194093e 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -1,35 +1,22 @@ import numpy as np -from ..util import img_as_float +from scipy.ndimage.filters import maximum_filter + +from fast_cy import _corner_fast -def corner_fast(image, n=9, threshold=0.15): +def corner_fast(image, n=12, threshold=0.15): image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - image = img_as_float(image) - corner_mask = np.zeros(image.shape, dtype=bool) + image = np.ascontiguousarray(image, dtype=np.double) + corner = _corner_fast(image, n, threshold) - test_pixels = np.asarray([[-3, 0], [-3, 1], [-2, 2], [-1, 3], [0, 3], - [1, 3], [2, 2], [3, 1], [3, 0], [3, -1], - [2, -2], [1, -3], [0, -3], [-1, -3], - [-2, -2], [-1, -3]]) + corner_zero_mask = corner != 0 + c, d = np.nonzero(corner) - # TODO : Outsource to Cython - for i in range(3, image.shape[0] - 3): - for j in range(3, image.shape[1] - 3): - test_x = i + test_pixels[:, 0] - test_y = j + test_pixels[:, 1] - intensities = image[test_x, test_y] - low = intensities < image[i, j] - threshold - high = intensities > image[i, j] + threshold - low = np.concatenate(low, low) - high = np.concatenate(high, high) - # How to check if a sequence n * [True] exists in low/high ? - # if n * [True] in low or n * True in high: - # corner_mask[i, j] = True - - corner_x, corner_y = np.where(corner_mask == True) - corners = np.dstack(corner_x, corner_y) - return corners + maximas = (maximum_filter(corner, (3, 3)) == corner) & corner_zero_mask + x, y = np.where(maximas == True) + + return np.squeeze(np.dstack((x, y))) diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx new file mode 100644 index 00000000..a7af63c2 --- /dev/null +++ b/skimage/feature/fast_cy.pyx @@ -0,0 +1,82 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +import numpy as np + +from ..util import img_as_float + + +def _corner_fast(double[:, ::1] image, int n, double threshold): + + cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) + + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + + cdef Py_ssize_t i, j, k, l, m + + cdef char[:] bins + cdef int consecutive_count = 0 + cdef double sum_b + cdef double sum_d + cdef double[:, ::1] corner = np.zeros((rows, cols), dtype=np.double) + + cdef double circle_intensity + + for i in range(3, rows - 3): + for j in range(3, cols - 3): + + bins = np.zeros(16, dtype='S1') + sum_b = 0 + sum_d = 0 + + for k in range(16): + circle_intensity = image[i + rp[k], j + cp[k]] + if circle_intensity > image[i, j] + threshold: + bins[k] = 'b' + elif circle_intensity < image[i, j] - threshold: + bins[k] = 'd' + else: + bins[k] = 's' + + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'b': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + elif bins[m] == 'd': + sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + if sum_d > sum_b: + corner[i, j] = sum_d + else: + corner[i, j] = sum_b + break + else: + consecutive_count = 0 + + if corner[i, j] == 0: + consecutive_count = 0 + for l in range(15 + n): + if bins[l % 16] == 'd': + consecutive_count += 1 + if consecutive_count == n: + for m in range(16): + if bins[m] == 'b': + sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + elif bins[m] == 'd': + sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + if sum_d > sum_b: + corner[i, j] = sum_d + else: + corner[i, j] = sum_b + break + else: + consecutive_count = 0 + + return np.asarray(corner) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 7df64c32..039d234f 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -14,6 +14,7 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) + cython(['fast_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) @@ -22,6 +23,8 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('censure_cy', sources=['censure_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('fast_cy', sources=['fast_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'], From 94e4a88712bb006eeffc464dbe8501bbce91f4df Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 01:24:29 +0530 Subject: [PATCH 021/145] Adding docstrings and commenting the code --- skimage/feature/fast.py | 40 +++++++++++++++++++++++++++++++------ skimage/feature/fast_cy.pyx | 21 +++++++++++-------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 8194093e..36a4c7e1 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -6,17 +6,45 @@ from fast_cy import _corner_fast def corner_fast(image, n=12, threshold=0.15): + """Extract FAST corners for a given image. + + Parameters + ---------- + image : 2D ndarray + Input image. + n : integer + Number of consecutive pixels out of 16 pixels on the circle that + should be brighter or darker with respect to test pixel above the + `threshold` so as to classify the test pixel as a FAST corner. Also + stands for the n in `FAST-n` corner detector. + threshold : float + Threshold used in deciding whether the pixels on the circle are + brighter, darker or similar to the test pixel. Decrease the threshold + when more corners are desired and vice-versa. + + Returns + ------- + corners : (N, 2) ndarray + Location i.e. (row, col) of extracted FAST corners. + + References + ---------- + .. [1] Edward Rosten and Tom Drummond + "Machine Learning for high-speed corner detection", + http://www.edwardrosten.com/work/rosten_2006_machine.pdf + + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") image = np.ascontiguousarray(image, dtype=np.double) - corner = _corner_fast(image, n, threshold) + corner_response = _corner_fast(image, n, threshold) - corner_zero_mask = corner != 0 - c, d = np.nonzero(corner) - - maximas = (maximum_filter(corner, (3, 3)) == corner) & corner_zero_mask + # Non-maximal Suppression + corner_zero_mask = corner_response != 0 + maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask x, y = np.where(maximas == True) - return np.squeeze(np.dstack((x, y))) + corners = np.squeeze(np.dstack((x, y))) + return corners diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index a7af63c2..d80aa318 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -8,7 +8,7 @@ import numpy as np from ..util import img_as_float -def _corner_fast(double[:, ::1] image, int n, double threshold): +def _corner_response_fast(double[:, ::1] image, int n, double threshold): cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -22,7 +22,7 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int consecutive_count = 0 cdef double sum_b cdef double sum_d - cdef double[:, ::1] corner = np.zeros((rows, cols), dtype=np.double) + cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) cdef double circle_intensity @@ -36,10 +36,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for k in range(16): circle_intensity = image[i + rp[k], j + cp[k]] if circle_intensity > image[i, j] + threshold: + # Brighter pixel bins[k] = 'b' elif circle_intensity < image[i, j] - threshold: + # Darker pixel bins[k] = 'd' else: + # Similar pixel bins[k] = 's' consecutive_count = 0 @@ -52,15 +55,16 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold elif bins[m] == 'd': sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner if sum_d > sum_b: - corner[i, j] = sum_d + corner_response[i, j] = sum_d else: - corner[i, j] = sum_b + corner_response[i, j] = sum_b break else: consecutive_count = 0 - if corner[i, j] == 0: + if corner_response[i, j] == 0: consecutive_count = 0 for l in range(15 + n): if bins[l % 16] == 'd': @@ -71,12 +75,13 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold elif bins[m] == 'd': sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + # Finding the response of the corner if sum_d > sum_b: - corner[i, j] = sum_d + corner_response[i, j] = sum_d else: - corner[i, j] = sum_b + corner_response[i, j] = sum_b break else: consecutive_count = 0 - return np.asarray(corner) + return np.asarray(corner_response) From 462a8f84d56c76406a994b9766c657c52cbdd7d0 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 20 Aug 2013 02:33:29 +0530 Subject: [PATCH 022/145] Adding high speed test for n>=12 --- skimage/feature/fast_cy.pyx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index d80aa318..43ffee08 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -8,7 +8,7 @@ import numpy as np from ..util import img_as_float -def _corner_response_fast(double[:, ::1] image, int n, double threshold): +def _corner_fast(double[:, ::1] image, int n, double threshold): cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -19,7 +19,8 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): cdef Py_ssize_t i, j, k, l, m cdef char[:] bins - cdef int consecutive_count = 0 + cdef int consecutive_count, speed_sum_b, speed_sum_d + cdef int sp cdef double sum_b cdef double sum_d cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) @@ -30,6 +31,8 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): for j in range(3, cols - 3): bins = np.zeros(16, dtype='S1') + speed_sum_b = 0 + speed_sum_d = 0 sum_b = 0 sum_d = 0 @@ -45,6 +48,16 @@ def _corner_response_fast(double[:, ::1] image, int n, double threshold): # Similar pixel bins[k] = 's' + # High speed test for n>=12 + if n >= 12: + for k in range(4): + if bins[4 * k] == 'b': + speed_sum_b += 1 + elif bins[4 * k] == 'd': + speed_sum_d += 1 + if speed_sum_d < 3 and speed_sum_b < 3: + continue + consecutive_count = 0 for l in range(15 + n): if bins[l % 16] == 'b': From 5160c3ca61d1f5987c6b93d3136ab2ad96c0e857 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 01:19:31 +0530 Subject: [PATCH 023/145] Making various minor optimizations --- skimage/feature/fast.py | 6 +++--- skimage/feature/fast_cy.pyx | 28 ++++++++++++---------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py index 36a4c7e1..4a7b84e6 100644 --- a/skimage/feature/fast.py +++ b/skimage/feature/fast.py @@ -12,15 +12,15 @@ def corner_fast(image, n=12, threshold=0.15): ---------- image : 2D ndarray Input image. - n : integer + n : int Number of consecutive pixels out of 16 pixels on the circle that should be brighter or darker with respect to test pixel above the `threshold` so as to classify the test pixel as a FAST corner. Also stands for the n in `FAST-n` corner detector. threshold : float Threshold used in deciding whether the pixels on the circle are - brighter, darker or similar to the test pixel. Decrease the threshold - when more corners are desired and vice-versa. + brighter, darker or similar w.r.t. the test pixel. Decrease the + threshold when more corners are desired and vice-versa. Returns ------- diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx index 43ffee08..d2292eac 100644 --- a/skimage/feature/fast_cy.pyx +++ b/skimage/feature/fast_cy.pyx @@ -5,11 +5,8 @@ import numpy as np -from ..util import img_as_float - def _corner_fast(double[:, ::1] image, int n, double threshold): - cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) @@ -18,11 +15,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): cdef Py_ssize_t i, j, k, l, m - cdef char[:] bins + cdef char[:] bins = np.zeros(16, dtype=np.uint8) cdef int consecutive_count, speed_sum_b, speed_sum_d cdef int sp - cdef double sum_b - cdef double sum_d + cdef double sum_b, sum_d, current_pixel cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) cdef double circle_intensity @@ -30,7 +26,7 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for i in range(3, rows - 3): for j in range(3, cols - 3): - bins = np.zeros(16, dtype='S1') + current_pixel = image[i, j] speed_sum_b = 0 speed_sum_d = 0 sum_b = 0 @@ -38,10 +34,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): for k in range(16): circle_intensity = image[i + rp[k], j + cp[k]] - if circle_intensity > image[i, j] + threshold: + if circle_intensity > current_pixel + threshold: # Brighter pixel bins[k] = 'b' - elif circle_intensity < image[i, j] - threshold: + elif circle_intensity < current_pixel - threshold: # Darker pixel bins[k] = 'd' else: @@ -50,10 +46,10 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): # High speed test for n>=12 if n >= 12: - for k in range(4): - if bins[4 * k] == 'b': + for k in range(0, 16, 4): + if bins[k] == 'b': speed_sum_b += 1 - elif bins[4 * k] == 'd': + elif bins[k] == 'd': speed_sum_d += 1 if speed_sum_d < 3 and speed_sum_b < 3: continue @@ -65,9 +61,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold elif bins[m] == 'd': - sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold # Finding the response of the corner if sum_d > sum_b: corner_response[i, j] = sum_d @@ -85,9 +81,9 @@ def _corner_fast(double[:, ::1] image, int n, double threshold): if consecutive_count == n: for m in range(16): if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - image[i, j] - threshold + sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold elif bins[m] == 'd': - sum_d += image[i, j] - image[i + rp[m], j + cp[m]] - threshold + sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold # Finding the response of the corner if sum_d > sum_b: corner_response[i, j] = sum_d From b03053a159831635496d17176b6d17ac68782981 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 01:44:04 +0530 Subject: [PATCH 024/145] Transferring all the FAST code to corner.py and corner_cy.pyx --- bento.info | 3 --- skimage/feature/__init__.py | 1 - skimage/feature/setup.py | 3 --- 3 files changed, 7 deletions(-) diff --git a/bento.info b/bento.info index ae547267..30367057 100644 --- a/bento.info +++ b/bento.info @@ -102,9 +102,6 @@ Library: Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx - Extension: skimage.feature.fast_cy - Sources: - skimage/feature/fast_cy.pyx Extension: skimage.feature._texture Sources: skimage/feature/_texture.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c0adb50d..c16edb87 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -10,7 +10,6 @@ from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance from .censure import keypoints_censure -from .fast import corner_fast __all__ = ['daisy', 'hog', diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 039d234f..7df64c32 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -14,7 +14,6 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) - cython(['fast_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) @@ -23,8 +22,6 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('censure_cy', sources=['censure_cy.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('fast_cy', sources=['fast_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'], From 0b3414f0575a873b308d721b9d967ab06e20b6d1 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 18:59:17 +0530 Subject: [PATCH 025/145] Using inline function in corner_cy._corner_fast --- skimage/feature/corner_cy.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index e2f2506f..3c4f2f19 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -82,6 +82,7 @@ def corner_moravec(image, Py_ssize_t window_size=1): return np.asarray(out) +<<<<<<< HEAD cdef inline double _corner_fast_response(double curr_pixel, double* circle_intensities, char* bins, char state, char n): @@ -113,7 +114,6 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef double lower_threshold, upper_threshold cdef double[:, ::1] corner_response = np.empty((rows, cols), dtype=np.double) - cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] cdef char bins[16] From 967524b71e241b97d8ce18c5441b68911b2a061c Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 21 Aug 2013 23:02:41 +0530 Subject: [PATCH 026/145] Deleting FAST files --- skimage/feature/fast.py | 50 ------------------- skimage/feature/fast_cy.pyx | 96 ------------------------------------- 2 files changed, 146 deletions(-) delete mode 100644 skimage/feature/fast.py delete mode 100644 skimage/feature/fast_cy.pyx diff --git a/skimage/feature/fast.py b/skimage/feature/fast.py deleted file mode 100644 index 4a7b84e6..00000000 --- a/skimage/feature/fast.py +++ /dev/null @@ -1,50 +0,0 @@ -import numpy as np -from scipy.ndimage.filters import maximum_filter - -from fast_cy import _corner_fast - - -def corner_fast(image, n=12, threshold=0.15): - - """Extract FAST corners for a given image. - - Parameters - ---------- - image : 2D ndarray - Input image. - n : int - Number of consecutive pixels out of 16 pixels on the circle that - should be brighter or darker with respect to test pixel above the - `threshold` so as to classify the test pixel as a FAST corner. Also - stands for the n in `FAST-n` corner detector. - threshold : float - Threshold used in deciding whether the pixels on the circle are - brighter, darker or similar w.r.t. the test pixel. Decrease the - threshold when more corners are desired and vice-versa. - - Returns - ------- - corners : (N, 2) ndarray - Location i.e. (row, col) of extracted FAST corners. - - References - ---------- - .. [1] Edward Rosten and Tom Drummond - "Machine Learning for high-speed corner detection", - http://www.edwardrosten.com/work/rosten_2006_machine.pdf - - """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") - - image = np.ascontiguousarray(image, dtype=np.double) - corner_response = _corner_fast(image, n, threshold) - - # Non-maximal Suppression - corner_zero_mask = corner_response != 0 - maximas = (maximum_filter(corner_response, (3, 3)) == corner_response) & corner_zero_mask - x, y = np.where(maximas == True) - - corners = np.squeeze(np.dstack((x, y))) - return corners diff --git a/skimage/feature/fast_cy.pyx b/skimage/feature/fast_cy.pyx deleted file mode 100644 index d2292eac..00000000 --- a/skimage/feature/fast_cy.pyx +++ /dev/null @@ -1,96 +0,0 @@ -#cython: cdivision=True -#cython: boundscheck=False -#cython: nonecheck=False -#cython: wraparound=False - -import numpy as np - - -def _corner_fast(double[:, ::1] image, int n, double threshold): - cdef int[:] rp = (np.round(3 * np.sin(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - cdef int[:] cp = (np.round(3 * np.cos(2 * np.pi * np.arange(16, dtype=np.double) / 16))).astype(np.int32) - - cdef Py_ssize_t rows = image.shape[0] - cdef Py_ssize_t cols = image.shape[1] - - cdef Py_ssize_t i, j, k, l, m - - cdef char[:] bins = np.zeros(16, dtype=np.uint8) - cdef int consecutive_count, speed_sum_b, speed_sum_d - cdef int sp - cdef double sum_b, sum_d, current_pixel - cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) - - cdef double circle_intensity - - for i in range(3, rows - 3): - for j in range(3, cols - 3): - - current_pixel = image[i, j] - speed_sum_b = 0 - speed_sum_d = 0 - sum_b = 0 - sum_d = 0 - - for k in range(16): - circle_intensity = image[i + rp[k], j + cp[k]] - if circle_intensity > current_pixel + threshold: - # Brighter pixel - bins[k] = 'b' - elif circle_intensity < current_pixel - threshold: - # Darker pixel - bins[k] = 'd' - else: - # Similar pixel - bins[k] = 's' - - # High speed test for n>=12 - if n >= 12: - for k in range(0, 16, 4): - if bins[k] == 'b': - speed_sum_b += 1 - elif bins[k] == 'd': - speed_sum_d += 1 - if speed_sum_d < 3 and speed_sum_b < 3: - continue - - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'b': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 - - if corner_response[i, j] == 0: - consecutive_count = 0 - for l in range(15 + n): - if bins[l % 16] == 'd': - consecutive_count += 1 - if consecutive_count == n: - for m in range(16): - if bins[m] == 'b': - sum_b += image[i + rp[m], j + cp[m]] - current_pixel - threshold - elif bins[m] == 'd': - sum_d += current_pixel - image[i + rp[m], j + cp[m]] - threshold - # Finding the response of the corner - if sum_d > sum_b: - corner_response[i, j] = sum_d - else: - corner_response[i, j] = sum_b - break - else: - consecutive_count = 0 - - return np.asarray(corner_response) From fc1d02e38af43b69a4db54480f6a7008dfbd1c3d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 00:01:15 +0530 Subject: [PATCH 027/145] Converting image to float --- skimage/feature/corner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 0156dcb4..81db1c29 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -583,4 +583,5 @@ def corner_fast(image, n=12, threshold=0.15): image = img_as_float(image) image = np.ascontiguousarray(image) + return _corner_fast(image, n, threshold) From 2c47860c34eafdd686b3a939c51076cb016493e9 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 00:37:34 +0530 Subject: [PATCH 028/145] Adding additional parameter for setting shape for Non-max suppression --- skimage/feature/corner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 81db1c29..019b3174 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -547,7 +547,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return peaks -def corner_fast(image, n=12, threshold=0.15): +def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): """Extract FAST corners for a given image. @@ -564,6 +564,9 @@ def corner_fast(image, n=12, threshold=0.15): Threshold used in deciding whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. + non_max_shape : tuple of two odd int + Represents the area in which Non-maximal suppression is applied. + By default, a 3 x 3 area is used. Returns ------- @@ -583,5 +586,4 @@ def corner_fast(image, n=12, threshold=0.15): image = img_as_float(image) image = np.ascontiguousarray(image) - return _corner_fast(image, n, threshold) From 4f99aafa969056a3f51ff4fad9486b866870102e Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 04:06:56 +0530 Subject: [PATCH 029/145] Wrapping lines; returning corner_response at a pixel --- skimage/feature/corner_cy.pyx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 3c4f2f19..d8c97f25 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -82,7 +82,6 @@ def corner_moravec(image, Py_ssize_t window_size=1): return np.asarray(out) -<<<<<<< HEAD cdef inline double _corner_fast_response(double curr_pixel, double* circle_intensities, char* bins, char state, char n): @@ -112,8 +111,9 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef char speed_sum_b, speed_sum_d cdef double curr_pixel cdef double lower_threshold, upper_threshold - cdef double[:, ::1] corner_response = np.empty((rows, cols), + cdef double[:, ::1] corner_response = np.zeros((rows, cols), dtype=np.double) + cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] cdef char bins[16] @@ -150,7 +150,6 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): elif bins[k] == 'd': speed_sum_d += 1 if speed_sum_d < 3 and speed_sum_b < 3: - corner_response[i, j] = 0 continue curr_response = \ From 4840dacfcd7e5c1172e6d124b92893c47740ede6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 17:57:30 +0530 Subject: [PATCH 030/145] Returning the corner response --- skimage/feature/corner.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 019b3174..0156dcb4 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -547,7 +547,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return peaks -def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): +def corner_fast(image, n=12, threshold=0.15): """Extract FAST corners for a given image. @@ -564,9 +564,6 @@ def corner_fast(image, n=12, threshold=0.15, non_max_shape=(3, 3)): Threshold used in deciding whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. - non_max_shape : tuple of two odd int - Represents the area in which Non-maximal suppression is applied. - By default, a 3 x 3 area is used. Returns ------- From da30925dd506ab4bb5e99d628a84025ad9daa62c Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 22 Aug 2013 20:06:11 +0530 Subject: [PATCH 031/145] More optimizations --- skimage/feature/corner_cy.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index d8c97f25..154c4987 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -116,6 +116,7 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] + cdef char bins[16] cdef double circle_intensities[16] From c81fd76905f9ba9ebd42fc0c4dc12fdccd0fa216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 23 Aug 2013 22:39:11 +0200 Subject: [PATCH 032/145] Fix some performance regressions in Cython implementation of FAST --- skimage/feature/corner_cy.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 154c4987..d8c97f25 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -116,7 +116,6 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] - cdef char bins[16] cdef double circle_intensities[16] From 6576baae7d44d25a1259caed496d17d8c215eb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 24 Aug 2013 08:51:09 +0200 Subject: [PATCH 033/145] Fix some performance regressions in Cython implementation of FAST --- skimage/feature/corner_cy.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index d8c97f25..154c4987 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -116,6 +116,7 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] + cdef char bins[16] cdef double circle_intensities[16] From 985067d4d4f05553bef3042bea2ca34645bb83a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 24 Aug 2013 17:27:28 +0200 Subject: [PATCH 034/145] Fix invalid circle coordinates --- skimage/feature/corner_cy.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 154c4987..d8c97f25 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -116,7 +116,6 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1] cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3] - cdef char bins[16] cdef double circle_intensities[16] From 91a51f909b263c2165ca8986cb7fddfcc67003e7 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 25 Aug 2013 03:02:24 +0530 Subject: [PATCH 035/145] More clear docs; Wiki reference; removing rebase leftover --- skimage/feature/corner.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 0156dcb4..7add4924 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -556,9 +556,10 @@ def corner_fast(image, n=12, threshold=0.15): image : 2D ndarray Input image. n : int - Number of consecutive pixels out of 16 pixels on the circle that - should be brighter or darker with respect to test pixel above the - `threshold` so as to classify the test pixel as a FAST corner. Also + Minimum number of consecutive pixels out of 16 pixels on the circle + that should all be either brighter or darker w.r.t testpixel. + A point c on the circle is darker w.r.t test pixel p if + `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also stands for the n in `FAST-n` corner detector. threshold : float Threshold used in deciding whether the pixels on the circle are @@ -575,6 +576,8 @@ def corner_fast(image, n=12, threshold=0.15): .. [1] Edward Rosten and Tom Drummond "Machine Learning for high-speed corner detection", http://www.edwardrosten.com/work/rosten_2006_machine.pdf + .. [2] Wikipedia, "Features from accelerated segment test", + https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test """ image = np.squeeze(image) From 8304824455a7fc5aa9d44c60af2787ee34461779 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 25 Aug 2013 12:10:30 +0530 Subject: [PATCH 036/145] Implemented corner_fast_orientation function --- skimage/feature/__init__.py | 5 +-- skimage/feature/corner_cy.pyx | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c16edb87..d12049a3 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -5,7 +5,7 @@ from .peak import peak_local_max from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks, corner_fast) -from .corner_cy import corner_moravec +from .corner_cy import corner_moravec, corner_fast_orientation from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance @@ -29,4 +29,5 @@ __all__ = ['daisy', 'pairwise_hamming_distance', 'match_keypoints_brief', 'keypoints_censure', - 'corner_fast'] + 'corner_fast', + 'corner_fast_orientation'] diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index d8c97f25..8c5d1002 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -5,6 +5,7 @@ import numpy as np cimport numpy as cnp from libc.float cimport DBL_MAX +from libc.math cimport atan2 from skimage.color import rgb2grey from skimage.util import img_as_float @@ -164,3 +165,68 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): corner_response[i, j] = curr_response return np.asarray(corner_response) + + +def corner_fast_orientation(image, fast_corners): + """Compute the orientation of FAST corners using the first order central + moment i.e. the center of mass approach. The corner orientation is the + angle of the vector from the keypoint to the intensity centroid calculated + using first order central moment. + + Parameters + ---------- + image : 2D array + Input grayscale image. + fast_corners : (N, 2) array + FAST corners extracted from the corresponding image. + + Returns + ------- + orientation : (N, 1) array + Orientation of the input FAST corners in the range [-pi, pi]. + + 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 + ..[2] Paul L. Rosin, "Measuring Corner Properties" + http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf + + """ + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + # Essentially skimage.morphology.octagon(3, 2) + circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) + + cdef int[:, ::1] cfast_corners = np.ascontiguousarray(fast_corners, dtype=np.int32) + + cdef Py_ssize_t n_fast_corners = fast_corners.shape[0] + cdef Py_ssize_t i, p, q, r, c, x, y + cdef double[:, ::1] kp_circular_patch, mu + cdef double[:] kp_orientation = np.zeros(fast_corners.shape[0], dtype=np.double) + + for i in range(n_fast_corners): + x = cfast_corners[i, 0] + y = cfast_corners[i, 1] + + kp_circular_patch = image[x - 3:x + 4, y - 3:y + 4] * circular_mask + mu = np.zeros((2, 2), dtype=np.double) + for p in range(2): + for q in range(2): + for r in range(7): + for c in range(7): + mu[p, q] += kp_circular_patch[r, c] * (r - 3) ** q * (c - 3) ** p + + kp_orientation[i] = atan2(mu[1, 0] / mu[0, 0], mu[0, 1] / mu[0, 0]) + + return np.asarray(kp_orientation) From 04e9cef567405c647ff4da5cfa026245e45817d6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Mon, 26 Aug 2013 16:41:46 +0530 Subject: [PATCH 037/145] Using Py_ssize_t and np.intp for cfast_corners --- skimage/feature/corner_cy.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 8c5d1002..7d760c25 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -208,7 +208,7 @@ def corner_fast_orientation(image, fast_corners): [0, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) - cdef int[:, ::1] cfast_corners = np.ascontiguousarray(fast_corners, dtype=np.int32) + cdef Py_ssize_t[:, :] cfast_corners = np.ascontiguousarray(fast_corners, dtype=np.intp) cdef Py_ssize_t n_fast_corners = fast_corners.shape[0] cdef Py_ssize_t i, p, q, r, c, x, y From 3b76623a684af5b6b1d8d1b80959b74159e5b494 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Mon, 26 Aug 2013 17:28:41 +0530 Subject: [PATCH 038/145] Adding tests for corner_fast and corner_fast_orientation --- skimage/feature/corner.py | 1 - skimage/feature/tests/test_corner.py | 43 ++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 7add4924..12c2ea89 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -1,7 +1,6 @@ import numpy as np from scipy import ndimage from scipy import stats -from scipy.ndimage.filters import maximum_filter from skimage.color import rgb2grey from skimage.util import img_as_float, pad from skimage.feature import peak_local_max diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 9c07c006..56985613 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -3,10 +3,12 @@ from numpy.testing import assert_array_equal, assert_almost_equal from skimage import data from skimage import img_as_float +from skimage.color import rgb2gray from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi, corner_subpix, peak_local_max, corner_peaks, - corner_kitchen_rosenfeld, corner_foerstner) + corner_kitchen_rosenfeld, corner_foerstner, + corner_fast, corner_fast_orientation) def test_square_image(): @@ -94,8 +96,8 @@ def test_rotated_lena(): def test_subpix(): img = np.zeros((50, 50)) - img[:25,:25] = 255 - img[25:,25:] = 255 + img[:25, :25] = 255 + img[25:, 25:] = 255 corner = peak_local_max(corner_harris(img), num_peaks=1) subpix = corner_subpix(img, corner) assert_array_equal(subpix[0], (24.5, 24.5)) @@ -156,6 +158,41 @@ def test_blank_image_nans(): assert np.all(np.isfinite(response)) +def test_corner_fast_image_unsupported_error(): + img = np.zeros((20, 20, 3)) + assert_raises(ValueError, corner_fast, img) + + +def test_corner_fast_lena(): + img = rgb2gray(data.lena()) + expected = np.array([[ 67, 157], + [204, 261], + [247, 146], + [269, 111], + [318, 158], + [386, 73], + [413, 70], + [435, 180], + [455, 177], + [461, 160]]) + actual = corner_peaks(corner_fast(img, 12, 0.3)) + assert_array_equal(actual, expected) + + +def test_corner_fast_orientation_image_unsupported_error(): + img = np.zeros((20, 20, 3)) + assert_raises(ValueError, corner_fast_orientation, img, [[7, 7]]) + + +def test_corner_fast_orientation_lena(): + img = rgb2gray(data.lena()) + corners = corner_peaks(corner_fast(img, 11, 0.35)) + expected = np.array([-2.79279928, -1.68079274, 2.63070795, -1.81665159, + -2.09631254, -1.41580527]) + actual = corner_fast_orientation(img, corners) + assert_array_equal(actual, expected) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From b2bf0baad8cb4532904094dc97ce42e26452f707 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Mon, 26 Aug 2013 18:51:08 +0530 Subject: [PATCH 039/145] Speeding up corner_fast_orientation --- skimage/feature/corner_cy.pyx | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 7d760c25..3ef290a1 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -200,13 +200,13 @@ def corner_fast_orientation(image, fast_corners): raise ValueError("Only 2-D gray-scale images supported.") # Essentially skimage.morphology.octagon(3, 2) - circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) + cdef char[:, :] circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) cdef Py_ssize_t[:, :] cfast_corners = np.ascontiguousarray(fast_corners, dtype=np.intp) @@ -219,13 +219,14 @@ def corner_fast_orientation(image, fast_corners): x = cfast_corners[i, 0] y = cfast_corners[i, 1] - kp_circular_patch = image[x - 3:x + 4, y - 3:y + 4] * circular_mask + kp_circular_patch = np.ascontiguousarray(image[x - 3:x + 4, y - 3:y + 4]) mu = np.zeros((2, 2), dtype=np.double) - for p in range(2): - for q in range(2): - for r in range(7): - for c in range(7): - mu[p, q] += kp_circular_patch[r, c] * (r - 3) ** q * (c - 3) ** p + for r in range(7): + for c in range(7): + if circular_mask[r, c]: + for p in range(2): + for q in range(2): + mu[p, q] += kp_circular_patch[r, c] * (r - 3) ** q * (c - 3) ** p kp_orientation[i] = atan2(mu[1, 0] / mu[0, 0], mu[0, 1] / mu[0, 0]) From 84317b6a2f95c16522d2f2c64185d9d22a5b9cda Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Mon, 26 Aug 2013 22:29:36 +0530 Subject: [PATCH 040/145] Speeding up corner_fast_implementation : 2 --- skimage/feature/corner_cy.pyx | 49 +++++++++++++++++----------- skimage/feature/tests/test_corner.py | 12 ++++--- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 3ef290a1..2f916d4d 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -167,7 +167,7 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): return np.asarray(corner_response) -def corner_fast_orientation(image, fast_corners): +def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): """Compute the orientation of FAST corners using the first order central moment i.e. the center of mass approach. The corner orientation is the angle of the vector from the keypoint to the intensity centroid calculated @@ -199,35 +199,46 @@ def corner_fast_orientation(image, fast_corners): if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") + cdef double[:, :] cimage = np.ascontiguousarray(img_as_float(image)) # Essentially skimage.morphology.octagon(3, 2) - cdef char[:, :] circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) - - cdef Py_ssize_t[:, :] cfast_corners = np.ascontiguousarray(fast_corners, dtype=np.intp) + cdef char[:, ::1] circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) cdef Py_ssize_t n_fast_corners = fast_corners.shape[0] - cdef Py_ssize_t i, p, q, r, c, x, y + cdef Py_ssize_t i, r, c, x_top, y_left cdef double[:, ::1] kp_circular_patch, mu cdef double[:] kp_orientation = np.zeros(fast_corners.shape[0], dtype=np.double) + cdef double m00, m01, m10 for i in range(n_fast_corners): - x = cfast_corners[i, 0] - y = cfast_corners[i, 1] + x_top = fast_corners[i, 0] - 3 + y_left = fast_corners[i, 1] - 3 - kp_circular_patch = np.ascontiguousarray(image[x - 3:x + 4, y - 3:y + 4]) - mu = np.zeros((2, 2), dtype=np.double) + m00 = 0 + m01 = 0 + m10 = 0 + #kp_circular_patch = np.ascontiguousarray(image[x - 3:x + 4, y - 3:y + 4]) + #mu = np.zeros((2, 2), dtype=np.double) for r in range(7): for c in range(7): if circular_mask[r, c]: - for p in range(2): - for q in range(2): - mu[p, q] += kp_circular_patch[r, c] * (r - 3) ** q * (c - 3) ** p + m00 += cimage[x_top + r, y_left + c] - kp_orientation[i] = atan2(mu[1, 0] / mu[0, 0], mu[0, 1] / mu[0, 0]) + for r in range(7): + for c in range(7): + if circular_mask[r, c]: + m01 += cimage[x_top + r, y_left + c] * (c - 3) + + for r in range(7): + for c in range(7): + if circular_mask[r, c]: + m10 += cimage[x_top + r, y_left + c] * (r - 3) + + kp_orientation[i] = atan2(m10 / m00, m01 / m00) return np.asarray(kp_orientation) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 56985613..1a520ceb 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -1,5 +1,6 @@ import numpy as np -from numpy.testing import assert_array_equal, assert_almost_equal +from numpy.testing import (assert_array_equal, assert_raises, + assert_almost_equal) from skimage import data from skimage import img_as_float @@ -181,16 +182,17 @@ def test_corner_fast_lena(): def test_corner_fast_orientation_image_unsupported_error(): img = np.zeros((20, 20, 3)) - assert_raises(ValueError, corner_fast_orientation, img, [[7, 7]]) + assert_raises(ValueError, corner_fast_orientation, img, + np.asarray([[7, 7]])) def test_corner_fast_orientation_lena(): img = rgb2gray(data.lena()) corners = corner_peaks(corner_fast(img, 11, 0.35)) - expected = np.array([-2.79279928, -1.68079274, 2.63070795, -1.81665159, - -2.09631254, -1.41580527]) + expected = np.array([-1.9195897 , -3.03159624, -1.05991162, -2.89573739, + -2.61607644, 2.98660159]) actual = corner_fast_orientation(img, corners) - assert_array_equal(actual, expected) + assert_almost_equal(actual, expected) if __name__ == '__main__': From 1aef50ca8fc726d63e1b9ae5fd0424698e6b655b Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 27 Aug 2013 03:14:51 +0530 Subject: [PATCH 041/145] Adding an example; removing redundant stuff and more --- skimage/feature/corner.py | 37 ++++++++++++++++++++++++---- skimage/feature/corner_cy.pyx | 29 ++++++++-------------- skimage/feature/tests/test_corner.py | 2 +- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 12c2ea89..8a7944e6 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -53,8 +53,9 @@ def _compute_auto_correlation(image, sigma): """ - if image.ndim == 3: - image = img_as_float(rgb2grey(image)) + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") imx, imy = _compute_derivatives(image) @@ -567,8 +568,8 @@ def corner_fast(image, n=12, threshold=0.15): Returns ------- - corners : (N, 2) ndarray - Location i.e. (row, col) of extracted FAST corners. + response : ndarray + FAST corner response image. References ---------- @@ -578,6 +579,31 @@ def corner_fast(image, n=12, threshold=0.15): .. [2] Wikipedia, "Features from accelerated segment test", https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test + Examples + -------- + >>> import numpy as np + >>> from skimage.feature import corner_fast, corner_peaks + >>> square = np.zeros((12, 12)) + >>> square[3:9, 3:9] = 1 + >>> square + array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + >>> corner_peaks(corner_fast(square, 9), min_distance=1) + array([[3, 3], + [3, 8], + [8, 3], + [8, 8]]) + """ image = np.squeeze(image) if image.ndim != 2: @@ -585,4 +611,5 @@ def corner_fast(image, n=12, threshold=0.15): image = img_as_float(image) image = np.ascontiguousarray(image) - return _corner_fast(image, n, threshold) + response = _corner_fast(image, n, threshold) + return response diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 2f916d4d..66908472 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -168,7 +168,9 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): - """Compute the orientation of FAST corners using the first order central + """Compute the orientation of FAST corners. + + The orientation of corners is computed using the first order central moment i.e. the center of mass approach. The corner orientation is the angle of the vector from the keypoint to the intensity centroid calculated using first order central moment. @@ -199,7 +201,7 @@ def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - cdef double[:, :] cimage = np.ascontiguousarray(img_as_float(image)) + cdef double[:, :] cimage = img_as_float(image) # Essentially skimage.morphology.octagon(3, 2) cdef char[:, ::1] circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 0], @@ -210,34 +212,23 @@ def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) cdef Py_ssize_t n_fast_corners = fast_corners.shape[0] - cdef Py_ssize_t i, r, c, x_top, y_left - cdef double[:, ::1] kp_circular_patch, mu + cdef Py_ssize_t i, r, c, y_top, x_left cdef double[:] kp_orientation = np.zeros(fast_corners.shape[0], dtype=np.double) cdef double m00, m01, m10 for i in range(n_fast_corners): - x_top = fast_corners[i, 0] - 3 - y_left = fast_corners[i, 1] - 3 + y_top = fast_corners[i, 0] - 3 + x_left = fast_corners[i, 1] - 3 m00 = 0 m01 = 0 m10 = 0 - #kp_circular_patch = np.ascontiguousarray(image[x - 3:x + 4, y - 3:y + 4]) - #mu = np.zeros((2, 2), dtype=np.double) for r in range(7): for c in range(7): if circular_mask[r, c]: - m00 += cimage[x_top + r, y_left + c] - - for r in range(7): - for c in range(7): - if circular_mask[r, c]: - m01 += cimage[x_top + r, y_left + c] * (c - 3) - - for r in range(7): - for c in range(7): - if circular_mask[r, c]: - m10 += cimage[x_top + r, y_left + c] * (r - 3) + m00 += cimage[y_top + r, x_left + c] + m01 += cimage[y_top + r, x_left + c] * (c - 3) + m10 += cimage[y_top + r, x_left + c] * (r - 3) kp_orientation[i] = atan2(m10 / m00, m01 / m00) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 1a520ceb..b0abcaed 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -125,7 +125,7 @@ def test_num_peaks(): peak_local_max returns exactly the right amount of peaks. Test is run on Lena in order to produce a sufficient number of corners""" - lena_corners = corner_harris(data.lena()) + lena_corners = corner_harris(rgb2gray(data.lena())) for i in range(20): n = np.random.random_integers(20) From 07623e1965272edc21ed311831385c04c1b63b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 01:18:42 +0200 Subject: [PATCH 042/145] Transform corner_fast_orientations for application to arbitrary corner types --- skimage/feature/__init__.py | 4 +- skimage/feature/corner.py | 1 - skimage/feature/corner_cy.pyx | 101 +++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index d12049a3..d821174f 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -5,7 +5,7 @@ from .peak import peak_local_max from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks, corner_fast) -from .corner_cy import corner_moravec, corner_fast_orientation +from .corner_cy import corner_moravec, corner_orientations from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance @@ -30,4 +30,4 @@ __all__ = ['daisy', 'match_keypoints_brief', 'keypoints_censure', 'corner_fast', - 'corner_fast_orientation'] + 'corner_orientations'] diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 8a7944e6..618bd2d9 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -581,7 +581,6 @@ def corner_fast(image, n=12, threshold=0.15): Examples -------- - >>> import numpy as np >>> from skimage.feature import corner_fast, corner_peaks >>> square = np.zeros((12, 12)) >>> square[3:9, 3:9] = 1 diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 66908472..031d909d 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -167,25 +167,29 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): return np.asarray(corner_response) -def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): - """Compute the orientation of FAST corners. +def corner_orientations(image, Py_ssize_t[:, :] corners, mask): + """Compute the orientation of corners. - The orientation of corners is computed using the first order central - moment i.e. the center of mass approach. The corner orientation is the - angle of the vector from the keypoint to the intensity centroid calculated - using first order central moment. + The orientation of corners is computed using the first order central moment + i.e. the center of mass approach. The corner orientation is the angle of + the vector from the corner coordinate to the intensity centroid in the + local neighborhood around the corner calculated using first order central + moment. Parameters ---------- image : 2D array Input grayscale image. - fast_corners : (N, 2) array - FAST corners extracted from the corresponding image. + corners : (N, 2) array + Corner coordinates as ``(row, col)``. + mask : 2D array + Mask defining the local neighborhood of the corner used for the + calculation of the central moment. Returns ------- - orientation : (N, 1) array - Orientation of the input FAST corners in the range [-pi, pi]. + orientations : (N, 1) array + Orientations of corners in the range [-pi, pi]. References ---------- @@ -195,41 +199,72 @@ def corner_fast_orientation(image, Py_ssize_t[:, :] fast_corners): ..[2] Paul L. Rosin, "Measuring Corner Properties" http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf + Examples + -------- + >>> from skimage.morphology import octagon + >>> from skimage.feature import corner_fast, corner_peaks, \ + ... corner_orientations + >>> square = np.zeros((12, 12)) + >>> square[3:9, 3:9] = 1 + >>> square + array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + >>> corner_peaks(corner_fast(square, 9), min_distance=1) + array([[3, 3], + [3, 8], + [8, 3], + [8, 8]]) + >>> orientations = corner_orientations(square, corners, octagon(3, 2)) + >>> np.rad2deg(orientations) + array([ 45., 135., -45., -135.]) + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - cdef double[:, :] cimage = img_as_float(image) - # Essentially skimage.morphology.octagon(3, 2) - cdef char[:, ::1] circular_mask = np.array([[0, 0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) + if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1: + raise ValueError("Size of mask must be uneven.") - cdef Py_ssize_t n_fast_corners = fast_corners.shape[0] - cdef Py_ssize_t i, r, c, y_top, x_left - cdef double[:] kp_orientation = np.zeros(fast_corners.shape[0], dtype=np.double) + cdef double[:, :] cimage = img_as_float(image) + cdef char[:, ::1] cmask = np.ascontiguousarray(mask != 0, dtype=np.uint8) + + cdef Py_ssize_t i, r, c, r0, c0 + cdef Py_ssize_t mrows = mask.shape[0] + cdef Py_ssize_t mcols = mask.shape[1] + cdef Py_ssize_t mrows2 = (mrows - 1) / 2 + cdef Py_ssize_t mcols2 = (mcols - 1) / 2 + cdef double[:] orientations = np.zeros(corners.shape[0], dtype=np.double) + cdef double curr_pixel cdef double m00, m01, m10 - for i in range(n_fast_corners): - y_top = fast_corners[i, 0] - 3 - x_left = fast_corners[i, 1] - 3 + for i in range(corners.shape[0]): + r0 = corners[i, 0] - mrows2 + c0 = corners[i, 1] - mcols2 m00 = 0 m01 = 0 m10 = 0 - for r in range(7): - for c in range(7): - if circular_mask[r, c]: - m00 += cimage[y_top + r, x_left + c] - m01 += cimage[y_top + r, x_left + c] * (c - 3) - m10 += cimage[y_top + r, x_left + c] * (r - 3) - kp_orientation[i] = atan2(m10 / m00, m01 / m00) + for r in range(mrows): + for c in range(mcols): + if cmask[r, c]: + curr_pixel = cimage[r0 + r, c0 + c] + m00 += curr_pixel + m01 += curr_pixel * (c - mcols2) + m10 += curr_pixel * (r - mrows2) - return np.asarray(kp_orientation) + orientations[i] = atan2(m10 / m00, m01 / m00) + + return np.asarray(orientations) From a1c1a80d3457f466ba9b84f1957cc0d99d4fd8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 01:19:18 +0200 Subject: [PATCH 043/145] Move FAST corner function to other corner functions --- skimage/feature/corner.py | 134 +++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 618bd2d9..e0133097 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -329,6 +329,73 @@ def corner_foerstner(image, sigma=1): return w, q +def corner_fast(image, n=12, threshold=0.15): + + """Extract FAST corners for a given image. + + Parameters + ---------- + image : 2D ndarray + Input image. + n : int + Minimum number of consecutive pixels out of 16 pixels on the circle + that should all be either brighter or darker w.r.t testpixel. + A point c on the circle is darker w.r.t test pixel p if + `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also + stands for the n in `FAST-n` corner detector. + threshold : float + Threshold used in deciding whether the pixels on the circle are + brighter, darker or similar w.r.t. the test pixel. Decrease the + threshold when more corners are desired and vice-versa. + + Returns + ------- + response : ndarray + FAST corner response image. + + References + ---------- + .. [1] Edward Rosten and Tom Drummond + "Machine Learning for high-speed corner detection", + http://www.edwardrosten.com/work/rosten_2006_machine.pdf + .. [2] Wikipedia, "Features from accelerated segment test", + https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test + + Examples + -------- + >>> from skimage.feature import corner_fast, corner_peaks + >>> square = np.zeros((12, 12)) + >>> square[3:9, 3:9] = 1 + >>> square + array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], + [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + >>> corner_peaks(corner_fast(square, 9), min_distance=1) + array([[3, 3], + [3, 8], + [8, 3], + [8, 8]]) + + """ + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + image = np.ascontiguousarray(image) + response = _corner_fast(image, n, threshold) + return response + + def corner_subpix(image, corners, window_size=11, alpha=0.99): """Determine subpixel position of corners. @@ -545,70 +612,3 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, return np.transpose(peaks.nonzero()) else: return peaks - - -def corner_fast(image, n=12, threshold=0.15): - - """Extract FAST corners for a given image. - - Parameters - ---------- - image : 2D ndarray - Input image. - n : int - Minimum number of consecutive pixels out of 16 pixels on the circle - that should all be either brighter or darker w.r.t testpixel. - A point c on the circle is darker w.r.t test pixel p if - `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also - stands for the n in `FAST-n` corner detector. - threshold : float - Threshold used in deciding whether the pixels on the circle are - brighter, darker or similar w.r.t. the test pixel. Decrease the - threshold when more corners are desired and vice-versa. - - Returns - ------- - response : ndarray - FAST corner response image. - - References - ---------- - .. [1] Edward Rosten and Tom Drummond - "Machine Learning for high-speed corner detection", - http://www.edwardrosten.com/work/rosten_2006_machine.pdf - .. [2] Wikipedia, "Features from accelerated segment test", - https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test - - Examples - -------- - >>> from skimage.feature import corner_fast, corner_peaks - >>> square = np.zeros((12, 12)) - >>> square[3:9, 3:9] = 1 - >>> square - array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) - >>> corner_peaks(corner_fast(square, 9), min_distance=1) - array([[3, 3], - [3, 8], - [8, 3], - [8, 8]]) - - """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") - - image = img_as_float(image) - image = np.ascontiguousarray(image) - response = _corner_fast(image, n, threshold) - return response From 0e920315f1e66197e18a648d72d28137d2fdb375 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 27 Aug 2013 08:00:42 +0530 Subject: [PATCH 044/145] Making changes in test_corner --- skimage/feature/tests/test_corner.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index b0abcaed..47c6d016 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -5,11 +5,12 @@ from numpy.testing import (assert_array_equal, assert_raises, from skimage import data from skimage import img_as_float from skimage.color import rgb2gray +from skimage.morphology import octagon from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi, corner_subpix, peak_local_max, corner_peaks, corner_kitchen_rosenfeld, corner_foerstner, - corner_fast, corner_fast_orientation) + corner_fast, corner_orientations) def test_square_image(): @@ -180,18 +181,24 @@ def test_corner_fast_lena(): assert_array_equal(actual, expected) -def test_corner_fast_orientation_image_unsupported_error(): +def test_corner_orientations_image_unsupported_error(): img = np.zeros((20, 20, 3)) - assert_raises(ValueError, corner_fast_orientation, img, - np.asarray([[7, 7]])) + assert_raises(ValueError, corner_orientations, img, + np.asarray([[7, 7]]), np.ones((3, 3))) -def test_corner_fast_orientation_lena(): +def test_corner_orientations_even_shape_error(): + img = np.zeros((20, 20)) + assert_raises(ValueError, corner_orientations, img, + np.asarray([[7, 7]]), np.ones((4, 4))) + + +def test_corner_orientations_lena(): img = rgb2gray(data.lena()) corners = corner_peaks(corner_fast(img, 11, 0.35)) expected = np.array([-1.9195897 , -3.03159624, -1.05991162, -2.89573739, -2.61607644, 2.98660159]) - actual = corner_fast_orientation(img, corners) + actual = corner_orientations(img, corners, octagon(3, 2)) assert_almost_equal(actual, expected) From b0814a527f8a0332dbd3ef7e66a8b0b8d3cfd808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 15:09:04 +0200 Subject: [PATCH 045/145] Make structure tensor function public and add function to compute hessian matrix --- skimage/feature/__init__.py | 3 +- skimage/feature/censure.py | 4 +- skimage/feature/corner.py | 134 +++++++++++++++++++++++++++++------- 3 files changed, 112 insertions(+), 29 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index d821174f..ea5ace67 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -4,7 +4,8 @@ from .texture import greycomatrix, greycoprops, local_binary_pattern from .peak import peak_local_max from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, - corner_peaks, corner_fast) + corner_peaks, corner_fast, structure_tensor, + hessian_matrix) from .corner_cy import corner_moravec, corner_orientations from .template import match_template from ._brief import brief, match_keypoints_brief diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index 4bb7fdda..d852d15d 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -2,7 +2,7 @@ import numpy as np from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve from skimage.transform import integral_image -from skimage.feature.corner import _compute_auto_correlation +from skimage.feature import structure_tensor from skimage.util import img_as_float from skimage.morphology import octagon, star from skimage.feature.util import _mask_border_keypoints @@ -104,7 +104,7 @@ def _star_filter_kernel(m, n): def _suppress_lines(feature_mask, image, sigma, line_threshold): - Axx, Axy, Ayy = _compute_auto_correlation(image, sigma) + Axx, Axy, Ayy = structure_tensor(image, sigma) feature_mask[(Axx + Ayy) * (Axx + Ayy) > line_threshold * (Axx * Ayy - Axy * Axy)] = False diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index e0133097..66f9bc21 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -8,13 +8,18 @@ from skimage.feature import peak_local_max from corner_cy import _corner_fast -def _compute_derivatives(image): +def _compute_derivatives(image, mode='constant', cval=0): """Compute derivatives in x and y direction using the Sobel operator. Parameters ---------- image : ndarray Input image. + mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional + How to handle values outside the image borders. + cval : float, optional + Used in conjunction with mode 'constant', the value outside + the image boundaries. Returns ------- @@ -25,14 +30,71 @@ def _compute_derivatives(image): """ - imy = ndimage.sobel(image, axis=0, mode='constant', cval=0) - imx = ndimage.sobel(image, axis=1, mode='constant', cval=0) + imy = ndimage.sobel(image, axis=0, mode=mode, cval=cval) + imx = ndimage.sobel(image, axis=1, mode=mode, cval=cval) return imx, imy -def _compute_auto_correlation(image, sigma): - """Compute auto-correlation matrix using sum of squared differences. +def structure_tensor(image, sigma=1, mode='constant', cval=0): + """Compute structure tensor using sum of squared differences. + + The structure tensor A is defined as:: + + A = [Axx Axy] + [Axy Ayy] + + which is approximated by the weighted sum of squared differences in a local + window around each pixel in the image. + + Parameters + ---------- + image : ndarray + Input image. + sigma : float + Standard deviation used for the Gaussian kernel, which is used as a + weighting function for the local summation of squared differences. + mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional + How to handle values outside the image borders. + cval : float, optional + Used in conjunction with mode 'constant', the value outside + the image boundaries. + + Returns + ------- + Axx : ndarray + Element of the structure tensor for each pixel in the input image. + Axy : ndarray + Element of the structure tensor for each pixel in the input image. + Ayy : ndarray + Element of the structure tensor for each pixel in the input image. + + """ + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + imx, imy = _compute_derivatives(image, mode=mode, cval=cval) + + # structure tensore + Axx = ndimage.gaussian_filter(imx * imx, sigma, mode=mode, cval=cval) + Axy = ndimage.gaussian_filter(imx * imy, sigma, mode=mode, cval=cval) + Ayy = ndimage.gaussian_filter(imy * imy, sigma, mode=mode, cval=cval) + + return Axx, Axy, Ayy + + +def hessian_matrix(image, sigma=1, mode='constant', cval=0): + """Compute Hessian matrix. + + The Hessian matrix is defined as:: + + H = [Hxx Hxy] + [Hxy Hyy] + + which is computed by convolving the image with the second derivatives + of the Gaussian kernel in the respective x- and y-directions. Parameters ---------- @@ -41,15 +103,20 @@ def _compute_auto_correlation(image, sigma): sigma : float Standard deviation used for the Gaussian kernel, which is used as weighting function for the auto-correlation matrix. + mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional + How to handle values outside the image borders. + cval : float, optional + Used in conjunction with mode 'constant', the value outside + the image boundaries. Returns ------- - Axx : ndarray - Element of the auto-correlation matrix for each pixel in input image. - Axy : ndarray - Element of the auto-correlation matrix for each pixel in input image. - Ayy : ndarray - Element of the auto-correlation matrix for each pixel in input image. + Hxx : ndarray + Element of the Hessian matrix for each pixel in the input image. + Hxy : ndarray + Element of the Hessian matrix for each pixel in the input image. + Hyy : ndarray + Element of the Hessian matrix for each pixel in the input image. """ @@ -57,17 +124,28 @@ def _compute_auto_correlation(image, sigma): if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - imx, imy = _compute_derivatives(image) + # window extent to the left and right, which covers > 99% of the normal + # distribution + window_ext = np.ceil(3 * sigma) - # structure tensore - Axx = ndimage.gaussian_filter(imx * imx, sigma, mode='constant', cval=0) - Axy = ndimage.gaussian_filter(imx * imy, sigma, mode='constant', cval=0) - Ayy = ndimage.gaussian_filter(imy * imy, sigma, mode='constant', cval=0) + ky, kx = np.mgrid[-window_ext:window_ext + 1, -window_ext:window_ext + 1] - return Axx, Axy, Ayy + # second derivative Gaussian kernels + gaussian_exp = np.exp(-(kx ** 2 + ky ** 2) / (2 * sigma ** 2)) + kernel_xx = 1 / (2 * np.pi * sigma ** 4) * (kx ** 2 / sigma ** 2 - 1) + kernel_xx *= gaussian_exp + kernel_xy = 1 / (2 * np.pi * sigma ** 6) * (kx * ky) + kernel_xy *= gaussian_exp + kernel_yy = kernel_xx.transpose() + + Hxx = ndimage.convolve(image, kernel_xx, mode=mode, cval=cval) + Hxy = ndimage.convolve(image, kernel_xy, mode=mode, cval=cval) + Hyy = ndimage.convolve(image, kernel_yy, mode=mode, cval=cval) + + return Hxx, Hxy, Hyy -def corner_kitchen_rosenfeld(image): +def corner_kitchen_rosenfeld(image, mode='constant', cval=0): """Compute Kitchen and Rosenfeld corner measure response image. The corner measure is calculated as follows:: @@ -82,6 +160,11 @@ def corner_kitchen_rosenfeld(image): ---------- image : ndarray Input image. + mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional + How to handle values outside the image borders. + cval : float, optional + Used in conjunction with mode 'constant', the value outside + the image boundaries. Returns ------- @@ -90,9 +173,9 @@ def corner_kitchen_rosenfeld(image): """ - imx, imy = _compute_derivatives(image) - imxx, imxy = _compute_derivatives(imx) - imyx, imyy = _compute_derivatives(imy) + imx, imy = _compute_derivatives(image, mode=mode, cval=cval) + imxx, imxy = _compute_derivatives(imx, mode=mode, cval=cval) + imyx, imyy = _compute_derivatives(imy, mode=mode, cval=cval) numerator = (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy) denominator = (imx**2 + imy**2) @@ -171,7 +254,7 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1): """ - Axx, Axy, Ayy = _compute_auto_correlation(image, sigma) + Axx, Axy, Ayy = structure_tensor(image, sigma) # determinant detA = Axx * Ayy - Axy**2 @@ -241,7 +324,7 @@ def corner_shi_tomasi(image, sigma=1): """ - Axx, Axy, Ayy = _compute_auto_correlation(image, sigma) + Axx, Axy, Ayy = structure_tensor(image, sigma) # minimum eigenvalue of A response = ((Axx + Ayy) - np.sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2 @@ -311,7 +394,7 @@ def corner_foerstner(image, sigma=1): """ - Axx, Axy, Ayy = _compute_auto_correlation(image, sigma) + Axx, Axy, Ayy = structure_tensor(image, sigma) # determinant detA = Axx * Ayy - Axy**2 @@ -330,7 +413,6 @@ def corner_foerstner(image, sigma=1): def corner_fast(image, n=12, threshold=0.15): - """Extract FAST corners for a given image. Parameters @@ -478,7 +560,7 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): maxx = x0 + wext + 2 window = image[miny:maxy, minx:maxx] - winx, winy = _compute_derivatives(window) + winx, winy = _compute_derivatives(window, mode='constant', cval=0) # compute gradient suares and remove border winx_winx = (winx * winx)[1:-1, 1:-1] From 02e81acec8805c66f157827054fb89f3026ea3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 15:10:48 +0200 Subject: [PATCH 046/145] Add missing imports --- skimage/feature/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index ea5ace67..c534fb12 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -31,4 +31,6 @@ __all__ = ['daisy', 'match_keypoints_brief', 'keypoints_censure', 'corner_fast', - 'corner_orientations'] + 'corner_orientations', + 'structure_tensor', + 'hessian_matrix'] From 3e2ca1493e15dba21f5632d44e2caa065280dce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 15:37:58 +0200 Subject: [PATCH 047/145] Add test cases for structure tensor and hessian matrix functions --- skimage/feature/corner.py | 4 ++- skimage/feature/tests/test_corner.py | 45 +++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 66f9bc21..3d665946 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -126,7 +126,7 @@ def hessian_matrix(image, sigma=1, mode='constant', cval=0): # window extent to the left and right, which covers > 99% of the normal # distribution - window_ext = np.ceil(3 * sigma) + window_ext = max(1, np.ceil(3 * sigma)) ky, kx = np.mgrid[-window_ext:window_ext + 1, -window_ext:window_ext + 1] @@ -134,8 +134,10 @@ def hessian_matrix(image, sigma=1, mode='constant', cval=0): gaussian_exp = np.exp(-(kx ** 2 + ky ** 2) / (2 * sigma ** 2)) kernel_xx = 1 / (2 * np.pi * sigma ** 4) * (kx ** 2 / sigma ** 2 - 1) kernel_xx *= gaussian_exp + kernel_xx /= kernel_xx.sum() kernel_xy = 1 / (2 * np.pi * sigma ** 6) * (kx * ky) kernel_xy *= gaussian_exp + kernel_xy /= kernel_xx.sum() kernel_yy = kernel_xx.transpose() Hxx = ndimage.convolve(image, kernel_xx, mode=mode, cval=cval) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 47c6d016..48833f92 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -10,7 +10,50 @@ from skimage.morphology import octagon from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi, corner_subpix, peak_local_max, corner_peaks, corner_kitchen_rosenfeld, corner_foerstner, - corner_fast, corner_orientations) + corner_fast, corner_orientations, + structure_tensor, hessian_matrix) + + +def test_structure_tensor(): + square = np.zeros((5, 5)) + square[2, 2] = 1 + Axx, Axy, Ayy = structure_tensor(square, sigma=0.1) + assert_array_equal(Axx, np.array([[ 0, 0, 0, 0, 0], + [ 0, 1, 0, 1, 0], + [ 0, 4, 0, 4, 0], + [ 0, 1, 0, 1, 0], + [ 0, 0, 0, 0, 0]])) + assert_array_equal(Axy, np.array([[ 0, 0, 0, 0, 0], + [ 0, 1, 0, -1, 0], + [ 0, 0, 0, -0, 0], + [ 0, -1, -0, 1, 0], + [ 0, 0, 0, 0, 0]])) + assert_array_equal(Ayy, np.array([[ 0, 0, 0, 0, 0], + [ 0, 1, 4, 1, 0], + [ 0, 0, 0, 0, 0], + [ 0, 1, 4, 1, 0], + [ 0, 0, 0, 0, 0]])) + + +def test_structure_tensor(): + square = np.zeros((5, 5)) + square[2, 2] = 1 + Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) + assert_array_equal(Hxx, np.array([[ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 1, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0]])) + assert_array_equal(Hxy, np.array([[ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0]])) + assert_array_equal(Hyy, np.array([[ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 1, 0, 0], + [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0]])) def test_square_image(): From aee31e91a7b620b2d9cdfd0ace2410aeaf86994c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 15:57:10 +0200 Subject: [PATCH 048/145] Add functions to compute eigen values of structure tensor and hessian matrix --- skimage/feature/__init__.py | 7 +++- skimage/feature/corner.py | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c534fb12..1f7c960b 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -5,7 +5,8 @@ from .peak import peak_local_max from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks, corner_fast, structure_tensor, - hessian_matrix) + structure_tensor_eigvals, hessian_matrix, + hessian_matrix_eigvals) from .corner_cy import corner_moravec, corner_orientations from .template import match_template from ._brief import brief, match_keypoints_brief @@ -33,4 +34,6 @@ __all__ = ['daisy', 'corner_fast', 'corner_orientations', 'structure_tensor', - 'hessian_matrix'] + 'structure_tensor_eigvals', + 'hessian_matrix', + 'hessian_matrix_eigvals'] diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 3d665946..887de425 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -147,6 +147,86 @@ def hessian_matrix(image, sigma=1, mode='constant', cval=0): return Hxx, Hxy, Hyy +def _image_orthogonal_matrix22_eigvals(M00, M01, M11): + l1 = (M00 + M11) / 2 + np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2 + l2 = (M00 + M11) / 2 - np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2 + return l1, l2 + + +def structure_tensor_eigvals(Axx, Axy, Ayy): + """Compute Eigen values of structure tensor. + + Parameters + ---------- + Axx : ndarray + Element of the structure tensor for each pixel in the input image. + Axy : ndarray + Element of the structure tensor for each pixel in the input image. + Ayy : ndarray + Element of the structure tensor for each pixel in the input image. + + Returns + ------- + l1 : ndarray + Larger eigen value for each input matrix. + l2 : ndarray + Smaller eigen value for each input matrix. + + Examples + -------- + >>> from skimage.feature import structure_tensor, structure_tensor_eigvals + >>> square = np.zeros((5, 5)) + >>> square[2, 2] = 1 + >>> Axx, Axy, Ayy = structure_tensor(square, sigma=0.1) + >>> structure_tensor_eigvals(Axx, Axy, Ayy)[0] + array([[ 0., 0., 0., 0., 0.], + [ 0., 2., 4., 2., 0.], + [ 0., 4., 0., 4., 0.], + [ 0., 2., 4., 2., 0.], + [ 0., 0., 0., 0., 0.]]) + + """ + + return _image_orthogonal_matrix22_eigvals(Axx, Axy, Ayy) + + +def hessian_matrix_eigvals(Hxx, Hxy, Hyy): + """Compute Eigen values of Hessian matrix. + + Parameters + ---------- + Hxx : ndarray + Element of the Hessian matrix for each pixel in the input image. + Hxy : ndarray + Element of the Hessian matrix for each pixel in the input image. + Hyy : ndarray + Element of the Hessian matrix for each pixel in the input image. + + Returns + ------- + l1 : ndarray + Larger eigen value for each input matrix. + l2 : ndarray + Smaller eigen value for each input matrix. + + Examples + -------- + >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals + >>> square = np.zeros((5, 5)) + >>> square[2, 2] = 1 + >>> Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) + >>> hessian_matrix_eigvals(Hxx, Hxy, Hyy)[0] + array([[ 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0.], + [ 0., 0., 1., 0., 0.], + [ 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0.]]) + + """ + + return _image_orthogonal_matrix22_eigvals(Hyy, Hxy, Hyy) + + def corner_kitchen_rosenfeld(image, mode='constant', cval=0): """Compute Kitchen and Rosenfeld corner measure response image. From 53a339e4f4fcc0fba56d5898f5f6c934575c1301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 16:09:13 +0200 Subject: [PATCH 049/145] Add examples for structure tensor and hessian matrix functions --- skimage/feature/corner.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 887de425..5628972d 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -69,6 +69,19 @@ def structure_tensor(image, sigma=1, mode='constant', cval=0): Ayy : ndarray Element of the structure tensor for each pixel in the input image. + Examples + -------- + >>> from skimage.feature import structure_tensor + >>> square = np.zeros((5, 5)) + >>> square[2, 2] = 1 + >>> Axx, Axy, Ayy = structure_tensor(square, sigma=0.1) + >>> Axx + array([[ 0., 0., 0., 0., 0.], + [ 0., 1., 0., 1., 0.], + [ 0., 4., 0., 4., 0.], + [ 0., 1., 0., 1., 0.], + [ 0., 0., 0., 0., 0.]]) + """ image = np.squeeze(image) @@ -118,6 +131,19 @@ def hessian_matrix(image, sigma=1, mode='constant', cval=0): Hyy : ndarray Element of the Hessian matrix for each pixel in the input image. + Examples + -------- + >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals + >>> square = np.zeros((5, 5)) + >>> square[2, 2] = 1 + >>> Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) + >>> Hxx + array([[ 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0.], + [ 0., 0., 1., 0., 0.], + [ 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0.]]) + """ image = np.squeeze(image) From 186e447c25adb9ae9a6f89875a73cf670bef8127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 27 Aug 2013 16:15:15 +0200 Subject: [PATCH 050/145] Add test cases for eigen value functions of structure tensor and hessian matrix --- skimage/feature/tests/test_corner.py | 67 +++++++++++++++++++++------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 48833f92..16566589 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -11,7 +11,8 @@ from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi, corner_subpix, peak_local_max, corner_peaks, corner_kitchen_rosenfeld, corner_foerstner, corner_fast, corner_orientations, - structure_tensor, hessian_matrix) + structure_tensor, structure_tensor_eigvals, + hessian_matrix, hessian_matrix_eigvals) def test_structure_tensor(): @@ -39,21 +40,55 @@ def test_structure_tensor(): square = np.zeros((5, 5)) square[2, 2] = 1 Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) - assert_array_equal(Hxx, np.array([[ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 1, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]])) - assert_array_equal(Hxy, np.array([[ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]])) - assert_array_equal(Hyy, np.array([[ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 1, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]])) + assert_array_equal(Hxx, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) + assert_array_equal(Hxy, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) + assert_array_equal(Hyy, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) + + +def test_structure_tensor_eigvals(): + square = np.zeros((5, 5)) + square[2, 2] = 1 + Axx, Axy, Ayy = structure_tensor(square, sigma=0.1) + l1, l2 = structure_tensor_eigvals(Axx, Axy, Ayy) + assert_array_equal(l1, np.array([[0, 0, 0, 0, 0], + [0, 2, 4, 2, 0], + [0, 4, 0, 4, 0], + [0, 2, 4, 2, 0], + [0, 0, 0, 0, 0]])) + assert_array_equal(l2, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) + + +def test_structure_tensor_eigvals(): + square = np.zeros((5, 5)) + square[2, 2] = 1 + Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) + l1, l2 = hessian_matrix_eigvals(Hxx, Hxy, Hyy) + assert_array_equal(l1, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) + assert_array_equal(l2, np.array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]])) def test_square_image(): From 3e1b6c2899590332ed8c1a47f104820d9f321333 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 28 Aug 2013 10:04:02 +0530 Subject: [PATCH 051/145] Making the example more explicit --- skimage/feature/corner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 5628972d..a2ab0a18 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -569,7 +569,8 @@ def corner_fast(image, n=12, threshold=0.15): [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) - >>> corner_peaks(corner_fast(square, 9), min_distance=1) + >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1) + >>> corners array([[3, 3], [3, 8], [8, 3], From f9b6e1ba8fe6ca5ca3325e886ef241699d147624 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 28 Aug 2013 10:20:15 +0530 Subject: [PATCH 052/145] Inserting corners in the corner_orientaions example --- skimage/feature/corner.py | 3 +-- skimage/feature/corner_cy.pyx | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index a2ab0a18..5628972d 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -569,8 +569,7 @@ def corner_fast(image, n=12, threshold=0.15): [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) - >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1) - >>> corners + >>> corner_peaks(corner_fast(square, 9), min_distance=1) array([[3, 3], [3, 8], [8, 3], diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 031d909d..c0046a08 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -219,7 +219,8 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) - >>> corner_peaks(corner_fast(square, 9), min_distance=1) + >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1) + >>> corners array([[3, 3], [3, 8], [8, 3], From 7d8c59135f944a02d016d2c455b4e62a1a59205d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 1 Sep 2013 09:03:09 +0530 Subject: [PATCH 053/145] Implementing keypoints_orb without using Harris response --- skimage/feature/__init__.py | 5 ++++- skimage/feature/orb.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 1f7c960b..8b33d6c7 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -12,6 +12,7 @@ from .template import match_template from ._brief import brief, match_keypoints_brief from .util import pairwise_hamming_distance from .censure import keypoints_censure +from .orb import keypoints_orb, descriptor_orb __all__ = ['daisy', 'hog', @@ -36,4 +37,6 @@ __all__ = ['daisy', 'structure_tensor', 'structure_tensor_eigvals', 'hessian_matrix', - 'hessian_matrix_eigvals'] + 'hessian_matrix_eigvals', + 'keypoints_orb', + 'descriptor_orb'] diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 009d1276..f6b4e01c 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -3,6 +3,39 @@ import numpy as np from ..util import img_as_float from .util import _mask_border_keypoints +from skimage.feature import corner_fast, corner_orientations, corner_peaks +from skimage.transform import pyramid_gaussian + + +def keypoints_orb(image, n=9, threshold=0.20, downscale_factor=1.414, + n_scales=5): + + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale_factor)) + + ofast_mask = np.array([[0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) + + keypoints = np.empty((0, 2), dtype=np.intp) + orientations = np.empty((0), dtype=np.double) + scales = np.empty((0), dtype=np.intp) + + for i in range(n_scales): + corners = corner_peaks(corner_fast(pyramid[i], n, threshold), min_distance=1) + keypoints = np.vstack((keypoints, corners)) + orientations = np.hstack((orientations, corner_orientations(pyramid[i], corners, ofast_mask))) + scales = np.hstack((scales, i * np.ones((corners.shape[0]), dtype=np.intp))) + + return keypoints, orientations, scales + def descriptor_orb(image, keypoints, keypoints_angle): From f94bf8628c96a1307c0d307fc28a96fef781094d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 4 Sep 2013 04:02:43 +0530 Subject: [PATCH 054/145] Returning best_keypoints --- skimage/feature/orb.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index f6b4e01c..74859d30 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -3,12 +3,13 @@ import numpy as np from ..util import img_as_float from .util import _mask_border_keypoints -from skimage.feature import corner_fast, corner_orientations, corner_peaks +from skimage.feature import (corner_fast, corner_orientations, corner_peaks, + corner_harris) from skimage.transform import pyramid_gaussian -def keypoints_orb(image, n=9, threshold=0.20, downscale_factor=1.414, - n_scales=5): +def keypoints_orb(image, fast_n=9, fast_threshold=0.20, n_keypoints=200, + harris_k=0.05, downscale_factor=1.414, n_scales=5): image = np.squeeze(image) if image.ndim != 2: @@ -27,14 +28,21 @@ def keypoints_orb(image, n=9, threshold=0.20, downscale_factor=1.414, keypoints = np.empty((0, 2), dtype=np.intp) orientations = np.empty((0), dtype=np.double) scales = np.empty((0), dtype=np.intp) + harris_measure = np.empty((0), dtype=np.double) for i in range(n_scales): - corners = corner_peaks(corner_fast(pyramid[i], n, threshold), min_distance=1) + harris_response = corner_harris(pyramid[i], method='k', k=harris_k) + corners = corner_peaks(corner_fast(pyramid[i], fast_n, fast_threshold), min_distance=1) keypoints = np.vstack((keypoints, corners)) orientations = np.hstack((orientations, corner_orientations(pyramid[i], corners, ofast_mask))) scales = np.hstack((scales, i * np.ones((corners.shape[0]), dtype=np.intp))) + harris_measure = np.hstack((harris_measure, harris_response[corners[:, 0], corners[:, 1]])) - return keypoints, orientations, scales + if keypoints.shape[0] < n_keypoints: + return keypoints, orientations, scales + else: + best_indices = harris_measure.argsort()[::-1][:n_keypoints] + return keypoints[best_indices], orientations[best_indices], scales[best_indices] def descriptor_orb(image, keypoints, keypoints_angle): From 3110916d868902fd9ec33f691be833e3f556cdbd Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 4 Sep 2013 18:29:51 +0530 Subject: [PATCH 055/145] Docstrings; Stacking lists; removing redundancy in corner_orientations --- skimage/feature/corner_cy.pyx | 6 +-- skimage/feature/orb.py | 74 ++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index c0046a08..929e3d4f 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -248,13 +248,12 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): cdef Py_ssize_t mcols2 = (mcols - 1) / 2 cdef double[:] orientations = np.zeros(corners.shape[0], dtype=np.double) cdef double curr_pixel - cdef double m00, m01, m10 + cdef double m01, m10 for i in range(corners.shape[0]): r0 = corners[i, 0] - mrows2 c0 = corners[i, 1] - mcols2 - m00 = 0 m01 = 0 m10 = 0 @@ -262,10 +261,9 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): for c in range(mcols): if cmask[r, c]: curr_pixel = cimage[r0 + r, c0 + c] - m00 += curr_pixel m01 += curr_pixel * (c - mcols2) m10 += curr_pixel * (r - mrows2) - orientations[i] = atan2(m10 / m00, m01 / m00) + orientations[i] = atan2(m10, m01) return np.asarray(orientations) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 74859d30..6f094295 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -8,9 +8,58 @@ from skimage.feature import (corner_fast, corner_orientations, corner_peaks, from skimage.transform import pyramid_gaussian -def keypoints_orb(image, fast_n=9, fast_threshold=0.20, n_keypoints=200, - harris_k=0.05, downscale_factor=1.414, n_scales=5): +def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, + harris_k=0.05, downscale_factor=np.sqrt(2), n_scales=5): + """Compute Oriented Fast keypoints. + + Parameters + ---------- + image : 2D ndarray + Input grayscale image. + n_keypoints : int + Number of keypoints to be returned from this function. The function + will return best `n_keypoints` if more than n_keypoints are detected + based on the values of other parameters. If not, then all the detected + keypoints are returned. + fast_n : int + The `n` parameter in `feature.corner_fast`. Minimum number of + consecutive pixels out of 16 pixels on the circle that should all be + either brighter or darker w.r.t testpixel. A point c on the circle is + darker w.r.t test pixel p if `Ic < Ip - threshold` and brighter if + `Ic > Ip + threshold`. Also stands for the n in `FAST-n` corner + detector. + fast_threshold : float + The `threshold` parameter in `feature.corner_fast`. Threshold used to + decide whether the pixels on the circle are brighter, darker or + similar w.r.t. the test pixel. Decrease the threshold when more + corners are desired and vice-versa. + harris_k : float + The `k` parameter in `feature.corner_harris`. Sensitivity factor to + separate corners from edges, typically in range `[0, 0.2]`. Small + values of k result in detection of sharp corners. + downscale_factor : float + Downscale factor for the image pyramid. + n_scales : int + Number of scales from the bottom of the image pyramid to extract + the features from. + + Returns + ------- + keypoints : (N, 2) ndarray + The oFAST keypoints. + orientations : (N,) ndarray + The orientations of the N extracted keypoints. + scales : (N,) ndarray + The scales of the N extracted keypoints. + + 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 + + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") @@ -25,18 +74,23 @@ def keypoints_orb(image, fast_n=9, fast_threshold=0.20, n_keypoints=200, [0, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) - keypoints = np.empty((0, 2), dtype=np.intp) - orientations = np.empty((0), dtype=np.double) - scales = np.empty((0), dtype=np.intp) - harris_measure = np.empty((0), dtype=np.double) + keypoints_list = [] + orientations_list = [] + scales_list = [] + harris_measure_list = [] for i in range(n_scales): harris_response = corner_harris(pyramid[i], method='k', k=harris_k) corners = corner_peaks(corner_fast(pyramid[i], fast_n, fast_threshold), min_distance=1) - keypoints = np.vstack((keypoints, corners)) - orientations = np.hstack((orientations, corner_orientations(pyramid[i], corners, ofast_mask))) - scales = np.hstack((scales, i * np.ones((corners.shape[0]), dtype=np.intp))) - harris_measure = np.hstack((harris_measure, harris_response[corners[:, 0], corners[:, 1]])) + keypoints_list.append(corners) + orientations_list.append(corner_orientations(pyramid[i], corners, ofast_mask)) + scales_list.append(i * np.ones((corners.shape[0]), dtype=np.intp)) + harris_measure_list.append(harris_response[corners[:, 0], corners[:, 1]]) + + keypoints = np.vstack(keypoints_list) + orientations = np.hstack(orientations_list) + scales = np.hstack(scales_list) + harris_measure = np.hstack(harris_measure_list) if keypoints.shape[0] < n_keypoints: return keypoints, orientations, scales From e8eb2de6e993f7925ca5cc19370b508ee3fcd203 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 4 Sep 2013 19:08:57 +0530 Subject: [PATCH 056/145] Adding explicit test for corner_orientations --- skimage/feature/tests/test_corner.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 16566589..200c9e2a 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -280,6 +280,17 @@ def test_corner_orientations_lena(): assert_almost_equal(actual, expected) +def test_corner_orientations_square(): + square = np.zeros((12, 12)) + square[3:9, 3:9] = 1 + corners = corner_peaks(corner_fast(square, 9), min_distance=1) + actual_orientations = corner_orientations(square, corners, octagon(3, 2)) + actual_orientations_degrees = np.rad2deg(actual_orientations) + expected_orientations_degree = np.array([ 45., 135., -45., -135.]) + assert_array_equal(actual_orientations_degrees, + expected_orientations_degree) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 07fdfdb5fec90ea8148a7a6c0d64e6b499eac1c6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 5 Sep 2013 17:45:02 +0530 Subject: [PATCH 057/145] Implementing descriptor_orb; for loop in Cython --- bento.info | 3 +++ skimage/feature/orb.py | 39 +++++++++++++++++++++++++++++--------- skimage/feature/orb_cy.pyx | 33 ++++++++++++++++++++++++++++++++ skimage/feature/setup.py | 3 +++ 4 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 skimage/feature/orb_cy.pyx diff --git a/bento.info b/bento.info index 30367057..725d1c06 100644 --- a/bento.info +++ b/bento.info @@ -96,6 +96,9 @@ Library: Extension: skimage.feature.censure_cy Sources: skimage/feature/censure_cy.pyx + Extension: skimage.feature.orb_cy + Sources: + skimage/feature/orb_cy.pyx Extension: skimage.feature._brief_cy Sources: skimage/feature/_brief_cy.pyx diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 6f094295..83d779cd 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -7,6 +7,7 @@ from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) from skimage.transform import pyramid_gaussian +from .orb_cy import _orb_loop def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, harris_k=0.05, downscale_factor=np.sqrt(2), n_scales=5): @@ -42,7 +43,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, Downscale factor for the image pyramid. n_scales : int Number of scales from the bottom of the image pyramid to extract - the features from. + the features from. Returns ------- @@ -59,7 +60,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, "ORB : An efficient alternative to SIFT and SURF" http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf - """ + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") @@ -99,7 +100,8 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, return keypoints[best_indices], orientations[best_indices], scales[best_indices] -def descriptor_orb(image, keypoints, keypoints_angle): +def descriptor_orb(image, keypoints, keypoints_orientations, + keypoints_scales, downscale_factor=np.sqrt(2), n_scales=5): image = np.squeeze(image) if image.ndim != 2: @@ -107,14 +109,33 @@ def descriptor_orb(image, keypoints, keypoints_angle): image = img_as_float(image) - mask = _mask_border_keypoints(image, keypoints, 13) - keypoints = keypoints[mask] - keypoints_angle = keypoints_angle[mask] + pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale_factor)) - pos1 = binary_tests[:, :2] - pos2 = binary_tests[:, 2:] + pos0 = binary_tests[:, :2].astype(np.int32) + pos1 = binary_tests[:, 2:].astype(np.int32) - descriptors = np.zeros((keypoints.shape[0], 256), dtype=bool) + pos0 = np.ascontiguousarray(pos0) + pos1 = np.ascontiguousarray(pos1) + + descriptors = np.empty((0, 256), dtype=np.bool) + + for k in range(n_scales): + curr_image = np.ascontiguousarray(pyramid[k]) + + curr_scale_mask = keypoints_scales == k + curr_scale_kpts = keypoints[curr_scale_mask] + curr_scale_kpts_orientation = keypoints_orientations[curr_scale_mask] + + border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, 13) + curr_scale_kpts = curr_scale_kpts[border_mask] + curr_scale_kpts_orientation = curr_scale_kpts_orientation[border_mask] + + curr_scale_descriptors = np.zeros((curr_scale_kpts.shape[0], 256), dtype=np.bool, order='C') + curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) + curr_scale_kpts_orientation = np.ascontiguousarray(curr_scale_kpts_orientation) + _orb_loop(curr_image, curr_scale_descriptors.view(np.uint8), curr_scale_kpts, curr_scale_kpts_orientation, pos0, pos1) + + descriptors = np.vstack((descriptors, curr_scale_descriptors)) for i in range(keypoints.shape[0]): angle = keypoints_angle[i] diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx new file mode 100644 index 00000000..15805bd7 --- /dev/null +++ b/skimage/feature/orb_cy.pyx @@ -0,0 +1,33 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +cimport numpy as cnp + +import numpy as np + + +def _orb_loop(double[:, ::1] image, char[:, ::1] descriptors, Py_ssize_t[:, ::1] keypoints, + double[:] orientations, int[:, ::1] pos0, int[:, ::1] pos1): + + cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1 + cdef int[:, ::1] steered_pos0, steered_pos1 + + for i in range(keypoints.shape[0]): + angle = orientations[i] + a = np.sin(angle * np.pi / 180.) + b = np.cos(angle * np.pi / 180.) + rotation_matrix = np.asarray([[b, a], [-a, b]]) + steered_pos0 = np.dot(pos0, rotation_matrix).astype(np.int32) + steered_pos1 = np.dot(pos1, rotation_matrix).astype(np.int32) + kr = keypoints[i, 0] + kc = keypoints[i, 1] + for j in range(256): + pr0 = steered_pos0[j][0] + pc0 = steered_pos0[j][1] + pr1 = steered_pos1[j][0] + pc1 = steered_pos1[j][1] + + if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]: + descriptors[i, j] = True diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 7df64c32..4f54faeb 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -14,6 +14,7 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) + cython(['orb_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) @@ -22,6 +23,8 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('censure_cy', sources=['censure_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('orb_cy', sources=['orb_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'], From deb1f70610a4e1a8f65ae0bfc8c003de86e71867 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 5 Sep 2013 18:03:26 +0530 Subject: [PATCH 058/145] Returning keypoints; Adding docstrings --- skimage/feature/orb.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 83d779cd..c6085e88 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -9,6 +9,7 @@ from skimage.transform import pyramid_gaussian from .orb_cy import _orb_loop + def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, harris_k=0.05, downscale_factor=np.sqrt(2), n_scales=5): @@ -102,7 +103,42 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, def descriptor_orb(image, keypoints, keypoints_orientations, keypoints_scales, downscale_factor=np.sqrt(2), n_scales=5): + """Compute Oriented Fast keypoints. + Parameters + ---------- + image : 2D ndarray + Input grayscale image. + keypoints : (N, 2) ndarray + Array of N keypoint locations in the format (row, col). + keypoints_orientations : (N,) ndarray + The orientations of the corresponding N keypoints. + keypoints_scales : (N,) ndarray + The scales of the corresponding N keypoints. + downscale_factor : 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. + + 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 + Location i.e. (row, col) of P keypoints after removing out those that + are near border. + + 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 + + """ image = np.squeeze(image) if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") @@ -136,6 +172,7 @@ def descriptor_orb(image, keypoints, keypoints_orientations, _orb_loop(curr_image, curr_scale_descriptors.view(np.uint8), curr_scale_kpts, curr_scale_kpts_orientation, pos0, pos1) descriptors = np.vstack((descriptors, curr_scale_descriptors)) + filtered_keypoints = np.vstack((filtered_keypoints, curr_scale_kpts)) for i in range(keypoints.shape[0]): angle = keypoints_angle[i] From 4ef1221cfd6f9b3936ce705331197a38e5cf7860 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 5 Sep 2013 18:04:57 +0530 Subject: [PATCH 059/145] Minor doc change --- skimage/feature/orb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index c6085e88..d2cc5022 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -103,14 +103,14 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, def descriptor_orb(image, keypoints, keypoints_orientations, keypoints_scales, downscale_factor=np.sqrt(2), n_scales=5): - """Compute Oriented Fast keypoints. + """Compute rBRIEF descriptors of input keypoints. Parameters ---------- image : 2D ndarray Input grayscale image. keypoints : (N, 2) ndarray - Array of N keypoint locations in the format (row, col). + Array of N input keypoint locations in the format (row, col). keypoints_orientations : (N,) ndarray The orientations of the corresponding N keypoints. keypoints_scales : (N,) ndarray From 914ab05fb64592454b3a932644c02fe1f67dea75 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 5 Sep 2013 19:06:09 +0530 Subject: [PATCH 060/145] Better naming of arguments --- skimage/feature/orb.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index d2cc5022..a92c0b2b 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -11,7 +11,7 @@ from .orb_cy import _orb_loop def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, - harris_k=0.05, downscale_factor=np.sqrt(2), n_scales=5): + harris_k=0.05, downscale=np.sqrt(2), n_scales=5): """Compute Oriented Fast keypoints. @@ -40,7 +40,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, The `k` parameter in `feature.corner_harris`. Sensitivity factor to separate corners from edges, typically in range `[0, 0.2]`. Small values of k result in detection of sharp corners. - downscale_factor : float + downscale : float Downscale factor for the image pyramid. n_scales : int Number of scales from the bottom of the image pyramid to extract @@ -66,7 +66,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, if image.ndim != 2: raise ValueError("Only 2-D gray-scale images supported.") - pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale_factor)) + pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) ofast_mask = np.array([[0, 0, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 0], @@ -101,8 +101,8 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, return keypoints[best_indices], orientations[best_indices], scales[best_indices] -def descriptor_orb(image, keypoints, keypoints_orientations, - keypoints_scales, downscale_factor=np.sqrt(2), n_scales=5): +def descriptor_orb(image, keypoints, orientations, scales, + downscale=np.sqrt(2), n_scales=5): """Compute rBRIEF descriptors of input keypoints. Parameters @@ -111,11 +111,11 @@ def descriptor_orb(image, keypoints, keypoints_orientations, Input grayscale image. keypoints : (N, 2) ndarray Array of N input keypoint locations in the format (row, col). - keypoints_orientations : (N,) ndarray + orientations : (N,) ndarray The orientations of the corresponding N keypoints. - keypoints_scales : (N,) ndarray + scales : (N,) ndarray The scales of the corresponding N keypoints. - downscale_factor : float + downscale : float Downscale factor for the image pyramid. Should be the same as that used in `keypoints_orb`. n_scales : int @@ -145,7 +145,7 @@ def descriptor_orb(image, keypoints, keypoints_orientations, image = img_as_float(image) - pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale_factor)) + pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) pos0 = binary_tests[:, :2].astype(np.int32) pos1 = binary_tests[:, 2:].astype(np.int32) @@ -158,11 +158,11 @@ def descriptor_orb(image, keypoints, keypoints_orientations, for k in range(n_scales): curr_image = np.ascontiguousarray(pyramid[k]) - curr_scale_mask = keypoints_scales == k + curr_scale_mask = scales == k curr_scale_kpts = keypoints[curr_scale_mask] - curr_scale_kpts_orientation = keypoints_orientations[curr_scale_mask] + curr_scale_kpts_orientation = orientations[curr_scale_mask] - border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, 13) + border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, dist=13) curr_scale_kpts = curr_scale_kpts[border_mask] curr_scale_kpts_orientation = curr_scale_kpts_orientation[border_mask] From 0bbc1d79338a33f56f7e1a4d4c1c1a2466f31719 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 6 Sep 2013 01:26:50 +0530 Subject: [PATCH 061/145] Writing better Cython code --- skimage/feature/corner_cy.pyx | 6 +- skimage/feature/orb.py | 10 +- skimage/feature/orb_cy.pyx | 295 ++++++++++++++++++++++++++++++++-- 3 files changed, 286 insertions(+), 25 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 929e3d4f..f4507b64 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -261,9 +261,9 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): for c in range(mcols): if cmask[r, c]: curr_pixel = cimage[r0 + r, c0 + c] - m01 += curr_pixel * (c - mcols2) - m10 += curr_pixel * (r - mrows2) + m10 += curr_pixel * (c - mcols2) + m01 += curr_pixel * (r - mrows2) - orientations[i] = atan2(m10, m01) + orientations[i] = atan2(m01, m10) return np.asarray(orientations) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index a92c0b2b..8713822b 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -147,12 +147,6 @@ def descriptor_orb(image, keypoints, orientations, scales, pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) - pos0 = binary_tests[:, :2].astype(np.int32) - pos1 = binary_tests[:, 2:].astype(np.int32) - - pos0 = np.ascontiguousarray(pos0) - pos1 = np.ascontiguousarray(pos1) - descriptors = np.empty((0, 256), dtype=np.bool) for k in range(n_scales): @@ -166,10 +160,10 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_kpts = curr_scale_kpts[border_mask] curr_scale_kpts_orientation = curr_scale_kpts_orientation[border_mask] - curr_scale_descriptors = np.zeros((curr_scale_kpts.shape[0], 256), dtype=np.bool, order='C') + curr_scale_descriptors = np.zeros((curr_scale_kpts.shape[0], 256), dtype=np.bool) curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) curr_scale_kpts_orientation = np.ascontiguousarray(curr_scale_kpts_orientation) - _orb_loop(curr_image, curr_scale_descriptors.view(np.uint8), curr_scale_kpts, curr_scale_kpts_orientation, pos0, pos1) + _orb_loop(curr_image, curr_scale_descriptors.view(np.uint8), curr_scale_kpts, curr_scale_kpts_orientation) descriptors = np.vstack((descriptors, curr_scale_descriptors)) filtered_keypoints = np.vstack((filtered_keypoints, curr_scale_kpts)) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 15805bd7..0a7f3044 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -4,30 +4,297 @@ #cython: wraparound=False cimport numpy as cnp - import numpy as np +from libc.math cimport sin, cos, M_PI, round + def _orb_loop(double[:, ::1] image, char[:, ::1] descriptors, Py_ssize_t[:, ::1] keypoints, - double[:] orientations, int[:, ::1] pos0, int[:, ::1] pos1): + double[:] orientations): - cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1 + cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1, spr0, spc0, spr1, spc1 + cdef char[:, ::1] pos0 = binary_tests[:, :2] + cdef char[:, ::1] pos1 = binary_tests[:, 2:] cdef int[:, ::1] steered_pos0, steered_pos1 + cdef double angle for i in range(keypoints.shape[0]): angle = orientations[i] - a = np.sin(angle * np.pi / 180.) - b = np.cos(angle * np.pi / 180.) - rotation_matrix = np.asarray([[b, a], [-a, b]]) - steered_pos0 = np.dot(pos0, rotation_matrix).astype(np.int32) - steered_pos1 = np.dot(pos1, rotation_matrix).astype(np.int32) + sin_a = sin(angle * M_PI / 180.) + cos_a = cos(angle * M_PI / 180.) + kr = keypoints[i, 0] kc = keypoints[i, 1] - for j in range(256): - pr0 = steered_pos0[j][0] - pc0 = steered_pos0[j][1] - pr1 = steered_pos1[j][0] - pc1 = steered_pos1[j][1] - if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]: + for j in range(256): + pr0 = pos0[j][0] + pc0 = pos0[j][1] + pr1 = pos1[j][0] + pc1 = pos1[j][1] + + spr0 = round(sin_a * pr0 + cos_a * pc0) + spc0 = round(cos_a * pr0 - sin_a * pc0) + spr1 = round(sin_a * pr1 + cos_a * pc1) + spc1 = round(cos_a * pr1 - sin_a * pc1) + + if image[kr + spr0, kc + spc0] < image[kr + spr1, kc + spc1]: descriptors[i, j] = True + + +# Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. +cdef char[:, ::1] binary_tests = np.ascontiguousarray(np.asarray([[8,-3, 9,5], + [4,2, 7,-12], + [-11,9, -8,2], + [7,-12, 12,-13], + [2,-13, 2,12], + [1,-7, 1,6], + [-2,-10, -2,-4], + [-13,-13, -11,-8], + [-13,-3, -12,-9], + [10,4, 11,9], + [-13,-8, -8,-9], + [-11,7, -9,12], + [7,7, 12,6], + [-4,-5, -3,0], + [-13,2, -12,-3], + [-9,0, -7,5], + [12,-6, 12,-1], + [-3,6, -2,12], + [-6,-13, -4,-8], + [11,-13, 12,-8], + [4,7, 5,1], + [5,-3, 10,-3], + [3,-7, 6,12], + [-8,-7, -6,-2], + [-2,11, -1,-10], + [-13,12, -8,10], + [-7,3, -5,-3], + [-4,2, -3,7], + [-10,-12, -6,11], + [5,-12, 6,-7], + [5,-6, 7,-1], + [1,0, 4,-5], + [9,11, 11,-13], + [4,7, 4,12], + [2,-1, 4,4], + [-4,-12, -2,7], + [-8,-5, -7,-10], + [4,11, 9,12], + [0,-8, 1,-13], + [-13,-2, -8,2], + [-3,-2, -2,3], + [-6,9, -4,-9], + [8,12, 10,7], + [0,9, 1,3], + [7,-5, 11,-10], + [-13,-6, -11,0], + [10,7, 12,1], + [-6,-3, -6,12], + [10,-9, 12,-4], + [-13,8, -8,-12], + [-13,0, -8,-4], + [3,3, 7,8], + [5,7, 10,-7], + [-1,7, 1,-12], + [3,-10, 5,6], + [2,-4, 3,-10], + [-13,0, -13,5], + [-13,-7, -12,12], + [-13,3, -11,8], + [-7,12, -4,7], + [6,-10, 12,8], + [-9,-1, -7,-6], + [-2,-5, 0,12], + [-12,5, -7,5], + [3,-10, 8,-13], + [-7,-7, -4,5], + [-3,-2, -1,-7], + [2,9, 5,-11], + [-11,-13, -5,-13], + [-1,6, 0,-1], + [5,-3, 5,2], + [-4,-13, -4,12], + [-9,-6, -9,6], + [-12,-10, -8,-4], + [10,2, 12,-3], + [7,12, 12,12], + [-7,-13, -6,5], + [-4,9, -3,4], + [7,-1, 12,2], + [-7,6, -5,1], + [-13,11, -12,5], + [-3,7, -2,-6], + [7,-8, 12,-7], + [-13,-7, -11,-12], + [1,-3, 12,12], + [2,-6, 3,0], + [-4,3, -2,-13], + [-1,-13, 1,9], + [7,1, 8,-6], + [1,-1, 3,12], + [9,1, 12,6], + [-1,-9, -1,3], + [-13,-13, -10,5], + [7,7, 10,12], + [12,-5, 12,9], + [6,3, 7,11], + [5,-13, 6,10], + [2,-12, 2,3], + [3,8, 4,-6], + [2,6, 12,-13], + [9,-12, 10,3], + [-8,4, -7,9], + [-11,12, -4,-6], + [1,12, 2,-8], + [6,-9, 7,-4], + [2,3, 3,-2], + [6,3, 11,0], + [3,-3, 8,-8], + [7,8, 9,3], + [-11,-5, -6,-4], + [-10,11, -5,10], + [-5,-8, -3,12], + [-10,5, -9,0], + [8,-1, 12,-6], + [4,-6, 6,-11], + [-10,12, -8,7], + [4,-2, 6,7], + [-2,0, -2,12], + [-5,-8, -5,2], + [7,-6, 10,12], + [-9,-13, -8,-8], + [-5,-13, -5,-2], + [8,-8, 9,-13], + [-9,-11, -9,0], + [1,-8, 1,-2], + [7,-4, 9,1], + [-2,1, -1,-4], + [11,-6, 12,-11], + [-12,-9, -6,4], + [3,7, 7,12], + [5,5, 10,8], + [0,-4, 2,8], + [-9,12, -5,-13], + [0,7, 2,12], + [-1,2, 1,7], + [5,11, 7,-9], + [3,5, 6,-8], + [-13,-4, -8,9], + [-5,9, -3,-3], + [-4,-7, -3,-12], + [6,5, 8,0], + [-7,6, -6,12], + [-13,6, -5,-2], + [1,-10, 3,10], + [4,1, 8,-4], + [-2,-2, 2,-13], + [2,-12, 12,12], + [-2,-13, 0,-6], + [4,1, 9,3], + [-6,-10, -3,-5], + [-3,-13, -1,1], + [7,5, 12,-11], + [4,-2, 5,-7], + [-13,9, -9,-5], + [7,1, 8,6], + [7,-8, 7,6], + [-7,-4, -7,1], + [-8,11, -7,-8], + [-13,6, -12,-8], + [2,4, 3,9], + [10,-5, 12,3], + [-6,-5, -6,7], + [8,-3, 9,-8], + [2,-12, 2,8], + [-11,-2, -10,3], + [-12,-13, -7,-9], + [-11,0, -10,-5], + [5,-3, 11,8], + [-2,-13, -1,12], + [-1,-8, 0,9], + [-13,-11, -12,-5], + [-10,-2, -10,11], + [-3,9, -2,-13], + [2,-3, 3,2], + [-9,-13, -4,0], + [-4,6, -3,-10], + [-4,12, -2,-7], + [-6,-11, -4,9], + [6,-3, 6,11], + [-13,11, -5,5], + [11,11, 12,6], + [7,-5, 12,-2], + [-1,12, 0,7], + [-4,-8, -3,-2], + [-7,1, -6,7], + [-13,-12, -8,-13], + [-7,-2, -6,-8], + [-8,5, -6,-9], + [-5,-1, -4,5], + [-13,7, -8,10], + [1,5, 5,-13], + [1,0, 10,-13], + [9,12, 10,-1], + [5,-8, 10,-9], + [-1,11, 1,-13], + [-9,-3, -6,2], + [-1,-10, 1,12], + [-13,1, -8,-10], + [8,-11, 10,-6], + [2,-13, 3,-6], + [7,-13, 12,-9], + [-10,-10, -5,-7], + [-10,-8, -8,-13], + [4,-6, 8,5], + [3,12, 8,-13], + [-4,2, -3,-3], + [5,-13, 10,-12], + [4,-13, 5,-1], + [-9,9, -4,3], + [0,3, 3,-9], + [-12,1, -6,1], + [3,2, 4,-8], + [-10,-10, -10,9], + [8,-13, 12,12], + [-8,-12, -6,-5], + [2,2, 3,7], + [10,6, 11,-8], + [6,8, 8,-12], + [-7,10, -6,5], + [-3,-9, -3,9], + [-1,-13, -1,5], + [-3,-7, -3,4], + [-8,-2, -8,3], + [4,2, 12,12], + [2,-5, 3,11], + [6,-9, 11,-13], + [3,-1, 7,12], + [11,-1, 12,4], + [-3,0, -3,6], + [4,-11, 4,12], + [2,-4, 2,1], + [-10,-6, -8,1], + [-13,7, -11,1], + [-13,12, -11,-13], + [6,0, 11,-13], + [0,-1, 1,4], + [-13,3, -9,-2], + [-9,8, -6,-3], + [-13,-6, -8,-2], + [5,-9, 8,10], + [2,7, 3,-9], + [-1,-6, -1,-1], + [9,5, 11,-2], + [11,-3, 12,-8], + [3,0, 3,5], + [-1,4, 0,10], + [3,-6, 4,5], + [-13,0, -10,5], + [5,8, 12,11], + [8,9, 9,-6], + [7,-4, 8,-12], + [-10,4, -10,9], + [7,3, 12,4], + [9,-7, 10,-2], + [7,0, 12,-2], + [-1,-6, 0,-11]], dtype=np.int8)) From c060df20f48471d5b5deb5f1ae63b5bf33e7ef73 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 6 Sep 2013 02:45:32 +0530 Subject: [PATCH 062/145] Stacking lists in descriptos_orb --- skimage/feature/orb.py | 286 ++----------------------------------- skimage/feature/orb_cy.pyx | 5 +- 2 files changed, 12 insertions(+), 279 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 8713822b..81bf4867 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -147,6 +147,8 @@ def descriptor_orb(image, keypoints, orientations, scales, pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) + descriptors_list = [] + filtered_keypoints_list = [] descriptors = np.empty((0, 256), dtype=np.bool) for k in range(n_scales): @@ -160,285 +162,13 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_kpts = curr_scale_kpts[border_mask] curr_scale_kpts_orientation = curr_scale_kpts_orientation[border_mask] - curr_scale_descriptors = np.zeros((curr_scale_kpts.shape[0], 256), dtype=np.bool) curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) curr_scale_kpts_orientation = np.ascontiguousarray(curr_scale_kpts_orientation) - _orb_loop(curr_image, curr_scale_descriptors.view(np.uint8), curr_scale_kpts, curr_scale_kpts_orientation) + curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, curr_scale_kpts_orientation) - descriptors = np.vstack((descriptors, curr_scale_descriptors)) - filtered_keypoints = np.vstack((filtered_keypoints, curr_scale_kpts)) + descriptors_list.append(curr_scale_descriptors) + filtered_keypoints_list.append(curr_scale_kpts) - for i in range(keypoints.shape[0]): - angle = keypoints_angle[i] - a = np.sin(angle * np.pi / 180.) - b = np.cos(angle * np.pi / 180.) - rotation_matrix = [[b, a], [-a, b]] - steered_pos1 = pos1 * rotation_matrix - steered_pos2 = pos2 * rotation_matrix - for j in range(256): - pr1 = steered_pos1[j][0] - pc1 = steered_pos1[j][1] - pr2 = steered_pos2[j][0] - pc2 = steered_pos2[j][1] - descriptors[i, j] = (image[pr1, pc1] < image[pr2, pc2]) - - return descriptors - - -# Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. -binary_tests = np.asarray([[8,-3, 9,5], - [4,2, 7,-12], - [-11,9, -8,2], - [7,-12, 12,-13], - [2,-13, 2,12], - [1,-7, 1,6], - [-2,-10, -2,-4], - [-13,-13, -11,-8], - [-13,-3, -12,-9], - [10,4, 11,9], - [-13,-8, -8,-9], - [-11,7, -9,12], - [7,7, 12,6], - [-4,-5, -3,0], - [-13,2, -12,-3], - [-9,0, -7,5], - [12,-6, 12,-1], - [-3,6, -2,12], - [-6,-13, -4,-8], - [11,-13, 12,-8], - [4,7, 5,1], - [5,-3, 10,-3], - [3,-7, 6,12], - [-8,-7, -6,-2], - [-2,11, -1,-10], - [-13,12, -8,10], - [-7,3, -5,-3], - [-4,2, -3,7], - [-10,-12, -6,11], - [5,-12, 6,-7], - [5,-6, 7,-1], - [1,0, 4,-5], - [9,11, 11,-13], - [4,7, 4,12], - [2,-1, 4,4], - [-4,-12, -2,7], - [-8,-5, -7,-10], - [4,11, 9,12], - [0,-8, 1,-13], - [-13,-2, -8,2], - [-3,-2, -2,3], - [-6,9, -4,-9], - [8,12, 10,7], - [0,9, 1,3], - [7,-5, 11,-10], - [-13,-6, -11,0], - [10,7, 12,1], - [-6,-3, -6,12], - [10,-9, 12,-4], - [-13,8, -8,-12], - [-13,0, -8,-4], - [3,3, 7,8], - [5,7, 10,-7], - [-1,7, 1,-12], - [3,-10, 5,6], - [2,-4, 3,-10], - [-13,0, -13,5], - [-13,-7, -12,12], - [-13,3, -11,8], - [-7,12, -4,7], - [6,-10, 12,8], - [-9,-1, -7,-6], - [-2,-5, 0,12], - [-12,5, -7,5], - [3,-10, 8,-13], - [-7,-7, -4,5], - [-3,-2, -1,-7], - [2,9, 5,-11], - [-11,-13, -5,-13], - [-1,6, 0,-1], - [5,-3, 5,2], - [-4,-13, -4,12], - [-9,-6, -9,6], - [-12,-10, -8,-4], - [10,2, 12,-3], - [7,12, 12,12], - [-7,-13, -6,5], - [-4,9, -3,4], - [7,-1, 12,2], - [-7,6, -5,1], - [-13,11, -12,5], - [-3,7, -2,-6], - [7,-8, 12,-7], - [-13,-7, -11,-12], - [1,-3, 12,12], - [2,-6, 3,0], - [-4,3, -2,-13], - [-1,-13, 1,9], - [7,1, 8,-6], - [1,-1, 3,12], - [9,1, 12,6], - [-1,-9, -1,3], - [-13,-13, -10,5], - [7,7, 10,12], - [12,-5, 12,9], - [6,3, 7,11], - [5,-13, 6,10], - [2,-12, 2,3], - [3,8, 4,-6], - [2,6, 12,-13], - [9,-12, 10,3], - [-8,4, -7,9], - [-11,12, -4,-6], - [1,12, 2,-8], - [6,-9, 7,-4], - [2,3, 3,-2], - [6,3, 11,0], - [3,-3, 8,-8], - [7,8, 9,3], - [-11,-5, -6,-4], - [-10,11, -5,10], - [-5,-8, -3,12], - [-10,5, -9,0], - [8,-1, 12,-6], - [4,-6, 6,-11], - [-10,12, -8,7], - [4,-2, 6,7], - [-2,0, -2,12], - [-5,-8, -5,2], - [7,-6, 10,12], - [-9,-13, -8,-8], - [-5,-13, -5,-2], - [8,-8, 9,-13], - [-9,-11, -9,0], - [1,-8, 1,-2], - [7,-4, 9,1], - [-2,1, -1,-4], - [11,-6, 12,-11], - [-12,-9, -6,4], - [3,7, 7,12], - [5,5, 10,8], - [0,-4, 2,8], - [-9,12, -5,-13], - [0,7, 2,12], - [-1,2, 1,7], - [5,11, 7,-9], - [3,5, 6,-8], - [-13,-4, -8,9], - [-5,9, -3,-3], - [-4,-7, -3,-12], - [6,5, 8,0], - [-7,6, -6,12], - [-13,6, -5,-2], - [1,-10, 3,10], - [4,1, 8,-4], - [-2,-2, 2,-13], - [2,-12, 12,12], - [-2,-13, 0,-6], - [4,1, 9,3], - [-6,-10, -3,-5], - [-3,-13, -1,1], - [7,5, 12,-11], - [4,-2, 5,-7], - [-13,9, -9,-5], - [7,1, 8,6], - [7,-8, 7,6], - [-7,-4, -7,1], - [-8,11, -7,-8], - [-13,6, -12,-8], - [2,4, 3,9], - [10,-5, 12,3], - [-6,-5, -6,7], - [8,-3, 9,-8], - [2,-12, 2,8], - [-11,-2, -10,3], - [-12,-13, -7,-9], - [-11,0, -10,-5], - [5,-3, 11,8], - [-2,-13, -1,12], - [-1,-8, 0,9], - [-13,-11, -12,-5], - [-10,-2, -10,11], - [-3,9, -2,-13], - [2,-3, 3,2], - [-9,-13, -4,0], - [-4,6, -3,-10], - [-4,12, -2,-7], - [-6,-11, -4,9], - [6,-3, 6,11], - [-13,11, -5,5], - [11,11, 12,6], - [7,-5, 12,-2], - [-1,12, 0,7], - [-4,-8, -3,-2], - [-7,1, -6,7], - [-13,-12, -8,-13], - [-7,-2, -6,-8], - [-8,5, -6,-9], - [-5,-1, -4,5], - [-13,7, -8,10], - [1,5, 5,-13], - [1,0, 10,-13], - [9,12, 10,-1], - [5,-8, 10,-9], - [-1,11, 1,-13], - [-9,-3, -6,2], - [-1,-10, 1,12], - [-13,1, -8,-10], - [8,-11, 10,-6], - [2,-13, 3,-6], - [7,-13, 12,-9], - [-10,-10, -5,-7], - [-10,-8, -8,-13], - [4,-6, 8,5], - [3,12, 8,-13], - [-4,2, -3,-3], - [5,-13, 10,-12], - [4,-13, 5,-1], - [-9,9, -4,3], - [0,3, 3,-9], - [-12,1, -6,1], - [3,2, 4,-8], - [-10,-10, -10,9], - [8,-13, 12,12], - [-8,-12, -6,-5], - [2,2, 3,7], - [10,6, 11,-8], - [6,8, 8,-12], - [-7,10, -6,5], - [-3,-9, -3,9], - [-1,-13, -1,5], - [-3,-7, -3,4], - [-8,-2, -8,3], - [4,2, 12,12], - [2,-5, 3,11], - [6,-9, 11,-13], - [3,-1, 7,12], - [11,-1, 12,4], - [-3,0, -3,6], - [4,-11, 4,12], - [2,-4, 2,1], - [-10,-6, -8,1], - [-13,7, -11,1], - [-13,12, -11,-13], - [6,0, 11,-13], - [0,-1, 1,4], - [-13,3, -9,-2], - [-9,8, -6,-3], - [-13,-6, -8,-2], - [5,-9, 8,10], - [2,7, 3,-9], - [-1,-6, -1,-1], - [9,5, 11,-2], - [11,-3, 12,-8], - [3,0, 3,5], - [-1,4, 0,10], - [3,-6, 4,5], - [-13,0, -10,5], - [5,8, 12,11], - [8,9, 9,-6], - [7,-4, 8,-12], - [-10,4, -10,9], - [7,3, 12,4], - [9,-7, 10,-2], - [7,0, 12,-2], - [-1,-6, 0,-11]]) + descriptors = np.vstack(descriptors_list) + filtered_keypoints = np.vstack(filtered_keypoints_list) + return descriptors, filtered_keypoints diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 0a7f3044..48c5d963 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,7 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round -def _orb_loop(double[:, ::1] image, char[:, ::1] descriptors, Py_ssize_t[:, ::1] keypoints, +def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, double[:] orientations): cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1, spr0, spc0, spr1, spc1 @@ -17,6 +17,7 @@ def _orb_loop(double[:, ::1] image, char[:, ::1] descriptors, Py_ssize_t[:, ::1] cdef char[:, ::1] pos1 = binary_tests[:, 2:] cdef int[:, ::1] steered_pos0, steered_pos1 cdef double angle + cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0], 256), dtype=np.uint8) for i in range(keypoints.shape[0]): angle = orientations[i] @@ -40,6 +41,8 @@ def _orb_loop(double[:, ::1] image, char[:, ::1] descriptors, Py_ssize_t[:, ::1] if image[kr + spr0, kc + spc0] < image[kr + spr1, kc + spc1]: descriptors[i, j] = True + return np.asarray(descriptors) + # Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. cdef char[:, ::1] binary_tests = np.ascontiguousarray(np.asarray([[8,-3, 9,5], From fe2e214385146ef93621698bf6475d8be6e0f51a Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 6 Sep 2013 02:49:37 +0530 Subject: [PATCH 063/145] Adding example in descriptor_orb --- skimage/feature/orb.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 81bf4867..09ed18ab 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -138,6 +138,21 @@ def descriptor_orb(image, keypoints, orientations, scales, "ORB : An efficient alternative to SIFT and SURF" http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf + Examples + -------- + >>> from skimage.data import lena + >>> from skimage.color import rgb2gray + >>> from skimage.feature import keypoints_orb, descriptor_orb + >>> img = rgb2gray(lena()) + >>> keypoints, orientations, scales = keypoints_orb(img, n_keypoints=250) + >>> keypoints.shape + (250, 2) + >>> descriptors, filtered_keypoints = descriptor_orb(img, keypoints, orientations, scales) + >>> filtered_keypoints.shape + (246, 2) + >>> descriptors.shape + (246, 256) + """ image = np.squeeze(image) if image.ndim != 2: From 9e8979fe4d8e1c38c21298214f5ea75df65904de Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 6 Sep 2013 02:55:48 +0530 Subject: [PATCH 064/145] Converting descriptors dtype to bool --- skimage/feature/orb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 09ed18ab..736f7739 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -184,6 +184,6 @@ def descriptor_orb(image, keypoints, orientations, scales, descriptors_list.append(curr_scale_descriptors) filtered_keypoints_list.append(curr_scale_kpts) - descriptors = np.vstack(descriptors_list) + descriptors = np.vstack(descriptors_list).astype(np.bool) filtered_keypoints = np.vstack(filtered_keypoints_list) return descriptors, filtered_keypoints From 607d90cedfb322004955920f12bcaa6f0a510234 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 7 Sep 2013 01:43:54 +0530 Subject: [PATCH 065/145] Minor optimizations; Renaming variables; Docstring corrections --- skimage/feature/corner_cy.pyx | 8 ++++---- skimage/feature/orb.py | 34 ++++++++++++++++++++-------------- skimage/feature/orb_cy.pyx | 11 ++++++----- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index f4507b64..770b7e28 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -31,8 +31,8 @@ def corner_moravec(image, Py_ssize_t window_size=1): References ---------- - ..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/moravec.htm - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/moravec.htm + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples -------- @@ -193,10 +193,10 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): References ---------- - ..[1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski + .. [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 - ..[2] Paul L. Rosin, "Measuring Corner Properties" + .. [2] Paul L. Rosin, "Measuring Corner Properties" http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf Examples diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 736f7739..b5a6e706 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -13,7 +13,7 @@ from .orb_cy import _orb_loop def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, harris_k=0.05, downscale=np.sqrt(2), n_scales=5): - """Compute Oriented Fast keypoints. + """Detect Oriented Fast keypoints. Parameters ---------- @@ -57,7 +57,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, References ---------- - ..[1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski + .. [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 @@ -83,11 +83,14 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, for i in range(n_scales): harris_response = corner_harris(pyramid[i], method='k', k=harris_k) - corners = corner_peaks(corner_fast(pyramid[i], fast_n, fast_threshold), min_distance=1) + corners = corner_peaks(corner_fast(pyramid[i], fast_n, fast_threshold), + min_distance=1) keypoints_list.append(corners) - orientations_list.append(corner_orientations(pyramid[i], corners, ofast_mask)) - scales_list.append(i * np.ones((corners.shape[0]), dtype=np.intp)) - harris_measure_list.append(harris_response[corners[:, 0], corners[:, 1]]) + orientations_list.append(corner_orientations(pyramid[i], corners, + ofast_mask)) + scales_list.append(i * np.ones(corners.shape[0], dtype=np.intp)) + harris_measure_list.append(harris_response[corners[:, 0], + corners[:, 1]]) keypoints = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) @@ -98,7 +101,8 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, return keypoints, orientations, scales else: best_indices = harris_measure.argsort()[::-1][:n_keypoints] - return keypoints[best_indices], orientations[best_indices], scales[best_indices] + return keypoints[best_indices], orientations[best_indices], \ + scales[best_indices] def descriptor_orb(image, keypoints, orientations, scales, @@ -134,7 +138,7 @@ def descriptor_orb(image, keypoints, orientations, scales, References ---------- - ..[1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski + .. [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 @@ -171,19 +175,21 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_mask = scales == k curr_scale_kpts = keypoints[curr_scale_mask] - curr_scale_kpts_orientation = orientations[curr_scale_mask] + curr_scale_orientation = orientations[curr_scale_mask] - border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, dist=13) + border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, + dist=13) curr_scale_kpts = curr_scale_kpts[border_mask] - curr_scale_kpts_orientation = curr_scale_kpts_orientation[border_mask] + curr_scale_orientation = curr_scale_orientation[border_mask] curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) - curr_scale_kpts_orientation = np.ascontiguousarray(curr_scale_kpts_orientation) - curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, curr_scale_kpts_orientation) + curr_scale_orientation = np.ascontiguousarray(curr_scale_orientation) + curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, + curr_scale_orientation) descriptors_list.append(curr_scale_descriptors) filtered_keypoints_list.append(curr_scale_kpts) - descriptors = np.vstack(descriptors_list).astype(np.bool) + descriptors = np.vstack(descriptors_list).view(np.bool) filtered_keypoints = np.vstack(filtered_keypoints_list) return descriptors, filtered_keypoints diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 48c5d963..2a2018d8 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -17,7 +17,8 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, cdef char[:, ::1] pos1 = binary_tests[:, 2:] cdef int[:, ::1] steered_pos0, steered_pos1 cdef double angle - cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0], 256), dtype=np.uint8) + cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0], 256), + dtype=np.uint8) for i in range(keypoints.shape[0]): angle = orientations[i] @@ -28,10 +29,10 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, kc = keypoints[i, 1] for j in range(256): - pr0 = pos0[j][0] - pc0 = pos0[j][1] - pr1 = pos1[j][0] - pc1 = pos1[j][1] + pr0 = pos0[j, 0] + pc0 = pos0[j, 1] + pr1 = pos1[j, 0] + pc1 = pos1[j, 1] spr0 = round(sin_a * pr0 + cos_a * pc0) spc0 = round(cos_a * pr0 - sin_a * pc0) From 43d90d0e9d020644769c47c01526094c01573fae Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 7 Sep 2013 02:45:17 +0530 Subject: [PATCH 066/145] Adding square examples in keypoints_orb, descriptor_orb --- skimage/feature/orb.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index b5a6e706..8ca9e57e 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -61,6 +61,29 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, "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, orientations, scales = keypoints_orb(square, n_keypoints=8, n_scales=2) + >>> keypoints.shape + (8, 2) + >>> keypoints + array([[29, 29], + [29, 20], + [20, 29], + [20, 20], + [15, 15], + [15, 20], + [20, 15], + [20, 20]]) + >>> orientations + array([-2.35619449, -0.78539816, 2.35619449, 0.78539816, 0.78539816, + 2.35619449, -0.78539816, -2.35619449]) + >>> scales + array([0, 0, 0, 0, 1, 1, 1, 1]) + """ image = np.squeeze(image) if image.ndim != 2: @@ -144,18 +167,21 @@ def descriptor_orb(image, keypoints, orientations, scales, Examples -------- - >>> from skimage.data import lena - >>> from skimage.color import rgb2gray + >>> import numpy as np >>> from skimage.feature import keypoints_orb, descriptor_orb - >>> img = rgb2gray(lena()) - >>> keypoints, orientations, scales = keypoints_orb(img, n_keypoints=250) + >>> square = np.zeros((50, 50)) + >>> square[20:30, 20:30] = 1 + >>> keypoints, orientations, scales = keypoints_orb(square, n_keypoints=8, \ + n_scales=2) >>> keypoints.shape - (250, 2) - >>> descriptors, filtered_keypoints = descriptor_orb(img, keypoints, orientations, scales) + (8, 2) + >>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, \ + orientations, scales, \ + n_scales=2) >>> filtered_keypoints.shape - (246, 2) + (8, 2) >>> descriptors.shape - (246, 256) + (8, 256) """ image = np.squeeze(image) From fe9f1e75b8d3846cb6cd9392b4656fae9770d99a Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 7 Sep 2013 03:24:46 +0530 Subject: [PATCH 067/145] Introducing the _prepare_grayscale_input_2D function in feature.util --- skimage/feature/_brief.py | 10 +++------- skimage/feature/censure.py | 8 +++----- skimage/feature/corner.py | 15 +++++---------- skimage/feature/corner_cy.pyx | 9 ++++----- skimage/feature/orb.py | 11 +++-------- skimage/feature/util.py | 11 +++++++++++ 6 files changed, 29 insertions(+), 35 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 8a8d78db..9e0475d3 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,8 +1,8 @@ import numpy as np from scipy.ndimage.filters import gaussian_filter -from ..util import img_as_float -from .util import _mask_border_keypoints, pairwise_hamming_distance +from .util import (_mask_border_keypoints, pairwise_hamming_distance, + _prepare_grayscale_input_2D) from ._brief_cy import _brief_loop @@ -129,11 +129,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, np.random.seed(sample_seed) - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") - - image = img_as_float(image) + image = _prepare_grayscale_input_2D(image) # Gaussian Low pass filtering to alleviate noise # sensitivity diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index d852d15d..cf3d543a 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -1,9 +1,10 @@ import numpy as np from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve +from .util import _prepare_grayscale_input_2D + from skimage.transform import integral_image from skimage.feature import structure_tensor -from skimage.util import img_as_float from skimage.morphology import octagon, star from skimage.feature.util import _mask_border_keypoints @@ -175,9 +176,7 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', # (4) Finally, we remove the border keypoints and return the keypoints # along with its corresponding scale. - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) mode = mode.lower() if mode not in ('dob', 'octagon', 'star'): @@ -187,7 +186,6 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', raise ValueError('The scales must be >= 1 and the number of scales ' 'should be >= 3.') - image = img_as_float(image) image = np.ascontiguousarray(image) # Generating all the scales diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 5628972d..90aac771 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -5,6 +5,8 @@ from skimage.color import rgb2grey from skimage.util import img_as_float, pad from skimage.feature import peak_local_max +from .util import _prepare_grayscale_input_2D + from corner_cy import _corner_fast @@ -84,9 +86,7 @@ def structure_tensor(image, sigma=1, mode='constant', cval=0): """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) imx, imy = _compute_derivatives(image, mode=mode, cval=cval) @@ -146,9 +146,7 @@ def hessian_matrix(image, sigma=1, mode='constant', cval=0): """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) # window extent to the left and right, which covers > 99% of the normal # distribution @@ -576,11 +574,8 @@ def corner_fast(image, n=12, threshold=0.15): [8, 8]]) """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) - image = img_as_float(image) image = np.ascontiguousarray(image) response = _corner_fast(image, n, threshold) return response diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 770b7e28..c810150b 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -8,7 +8,8 @@ from libc.float cimport DBL_MAX from libc.math cimport atan2 from skimage.color import rgb2grey -from skimage.util import img_as_float + +from .util import _prepare_grayscale_input_2D def corner_moravec(image, Py_ssize_t window_size=1): @@ -231,14 +232,12 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1: raise ValueError("Size of mask must be uneven.") - cdef double[:, :] cimage = img_as_float(image) + cdef double[:, :] cimage = image cdef char[:, ::1] cmask = np.ascontiguousarray(mask != 0, dtype=np.uint8) cdef Py_ssize_t i, r, c, r0, c0 diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 8ca9e57e..e2f3752e 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -1,7 +1,6 @@ import numpy as np -from ..util import img_as_float -from .util import _mask_border_keypoints +from .util import _mask_border_keypoints, _prepare_grayscale_input_2D from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) @@ -85,9 +84,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, array([0, 0, 0, 0, 1, 1, 1, 1]) """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) @@ -184,9 +181,7 @@ def descriptor_orb(image, keypoints, orientations, scales, (8, 256) """ - image = np.squeeze(image) - if image.ndim != 2: - raise ValueError("Only 2-D gray-scale images supported.") + image = _prepare_grayscale_input_2D(image) image = img_as_float(image) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index a5267d44..178eab66 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -1,3 +1,14 @@ +import numpy as np + +from ..util import img_as_float + + +def _prepare_grayscale_input_2D(image): + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + return img_as_float(image) def _mask_border_keypoints(image, keypoints, dist): From b996776e415a8e10b1322ec31e922b8cc52718a5 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 7 Sep 2013 18:34:28 +0530 Subject: [PATCH 068/145] Making pos0 and pos1 as global variables --- skimage/feature/orb.py | 2 - skimage/feature/orb_cy.pyx | 774 ++++++++++++++++++++++++------------- 2 files changed, 515 insertions(+), 261 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index e2f3752e..6f053aba 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -183,8 +183,6 @@ def descriptor_orb(image, keypoints, orientations, scales, """ image = _prepare_grayscale_input_2D(image) - image = img_as_float(image) - pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) descriptors_list = [] diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 2a2018d8..7e157a41 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -13,13 +13,12 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, double[:] orientations): cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1, spr0, spc0, spr1, spc1 - cdef char[:, ::1] pos0 = binary_tests[:, :2] - cdef char[:, ::1] pos1 = binary_tests[:, 2:] cdef int[:, ::1] steered_pos0, steered_pos1 cdef double angle cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0], 256), dtype=np.uint8) + for i in range(keypoints.shape[0]): angle = orientations[i] sin_a = sin(angle * M_PI / 180.) @@ -45,260 +44,517 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, return np.asarray(descriptors) -# Learned 256 decision pairs for binary tests in rBRIEF. Taken from OpenCV. -cdef char[:, ::1] binary_tests = np.ascontiguousarray(np.asarray([[8,-3, 9,5], - [4,2, 7,-12], - [-11,9, -8,2], - [7,-12, 12,-13], - [2,-13, 2,12], - [1,-7, 1,6], - [-2,-10, -2,-4], - [-13,-13, -11,-8], - [-13,-3, -12,-9], - [10,4, 11,9], - [-13,-8, -8,-9], - [-11,7, -9,12], - [7,7, 12,6], - [-4,-5, -3,0], - [-13,2, -12,-3], - [-9,0, -7,5], - [12,-6, 12,-1], - [-3,6, -2,12], - [-6,-13, -4,-8], - [11,-13, 12,-8], - [4,7, 5,1], - [5,-3, 10,-3], - [3,-7, 6,12], - [-8,-7, -6,-2], - [-2,11, -1,-10], - [-13,12, -8,10], - [-7,3, -5,-3], - [-4,2, -3,7], - [-10,-12, -6,11], - [5,-12, 6,-7], - [5,-6, 7,-1], - [1,0, 4,-5], - [9,11, 11,-13], - [4,7, 4,12], - [2,-1, 4,4], - [-4,-12, -2,7], - [-8,-5, -7,-10], - [4,11, 9,12], - [0,-8, 1,-13], - [-13,-2, -8,2], - [-3,-2, -2,3], - [-6,9, -4,-9], - [8,12, 10,7], - [0,9, 1,3], - [7,-5, 11,-10], - [-13,-6, -11,0], - [10,7, 12,1], - [-6,-3, -6,12], - [10,-9, 12,-4], - [-13,8, -8,-12], - [-13,0, -8,-4], - [3,3, 7,8], - [5,7, 10,-7], - [-1,7, 1,-12], - [3,-10, 5,6], - [2,-4, 3,-10], - [-13,0, -13,5], - [-13,-7, -12,12], - [-13,3, -11,8], - [-7,12, -4,7], - [6,-10, 12,8], - [-9,-1, -7,-6], - [-2,-5, 0,12], - [-12,5, -7,5], - [3,-10, 8,-13], - [-7,-7, -4,5], - [-3,-2, -1,-7], - [2,9, 5,-11], - [-11,-13, -5,-13], - [-1,6, 0,-1], - [5,-3, 5,2], - [-4,-13, -4,12], - [-9,-6, -9,6], - [-12,-10, -8,-4], - [10,2, 12,-3], - [7,12, 12,12], - [-7,-13, -6,5], - [-4,9, -3,4], - [7,-1, 12,2], - [-7,6, -5,1], - [-13,11, -12,5], - [-3,7, -2,-6], - [7,-8, 12,-7], - [-13,-7, -11,-12], - [1,-3, 12,12], - [2,-6, 3,0], - [-4,3, -2,-13], - [-1,-13, 1,9], - [7,1, 8,-6], - [1,-1, 3,12], - [9,1, 12,6], - [-1,-9, -1,3], - [-13,-13, -10,5], - [7,7, 10,12], - [12,-5, 12,9], - [6,3, 7,11], - [5,-13, 6,10], - [2,-12, 2,3], - [3,8, 4,-6], - [2,6, 12,-13], - [9,-12, 10,3], - [-8,4, -7,9], - [-11,12, -4,-6], - [1,12, 2,-8], - [6,-9, 7,-4], - [2,3, 3,-2], - [6,3, 11,0], - [3,-3, 8,-8], - [7,8, 9,3], - [-11,-5, -6,-4], - [-10,11, -5,10], - [-5,-8, -3,12], - [-10,5, -9,0], - [8,-1, 12,-6], - [4,-6, 6,-11], - [-10,12, -8,7], - [4,-2, 6,7], - [-2,0, -2,12], - [-5,-8, -5,2], - [7,-6, 10,12], - [-9,-13, -8,-8], - [-5,-13, -5,-2], - [8,-8, 9,-13], - [-9,-11, -9,0], - [1,-8, 1,-2], - [7,-4, 9,1], - [-2,1, -1,-4], - [11,-6, 12,-11], - [-12,-9, -6,4], - [3,7, 7,12], - [5,5, 10,8], - [0,-4, 2,8], - [-9,12, -5,-13], - [0,7, 2,12], - [-1,2, 1,7], - [5,11, 7,-9], - [3,5, 6,-8], - [-13,-4, -8,9], - [-5,9, -3,-3], - [-4,-7, -3,-12], - [6,5, 8,0], - [-7,6, -6,12], - [-13,6, -5,-2], - [1,-10, 3,10], - [4,1, 8,-4], - [-2,-2, 2,-13], - [2,-12, 12,12], - [-2,-13, 0,-6], - [4,1, 9,3], - [-6,-10, -3,-5], - [-3,-13, -1,1], - [7,5, 12,-11], - [4,-2, 5,-7], - [-13,9, -9,-5], - [7,1, 8,6], - [7,-8, 7,6], - [-7,-4, -7,1], - [-8,11, -7,-8], - [-13,6, -12,-8], - [2,4, 3,9], - [10,-5, 12,3], - [-6,-5, -6,7], - [8,-3, 9,-8], - [2,-12, 2,8], - [-11,-2, -10,3], - [-12,-13, -7,-9], - [-11,0, -10,-5], - [5,-3, 11,8], - [-2,-13, -1,12], - [-1,-8, 0,9], - [-13,-11, -12,-5], - [-10,-2, -10,11], - [-3,9, -2,-13], - [2,-3, 3,2], - [-9,-13, -4,0], - [-4,6, -3,-10], - [-4,12, -2,-7], - [-6,-11, -4,9], - [6,-3, 6,11], - [-13,11, -5,5], - [11,11, 12,6], - [7,-5, 12,-2], - [-1,12, 0,7], - [-4,-8, -3,-2], - [-7,1, -6,7], - [-13,-12, -8,-13], - [-7,-2, -6,-8], - [-8,5, -6,-9], - [-5,-1, -4,5], - [-13,7, -8,10], - [1,5, 5,-13], - [1,0, 10,-13], - [9,12, 10,-1], - [5,-8, 10,-9], - [-1,11, 1,-13], - [-9,-3, -6,2], - [-1,-10, 1,12], - [-13,1, -8,-10], - [8,-11, 10,-6], - [2,-13, 3,-6], - [7,-13, 12,-9], - [-10,-10, -5,-7], - [-10,-8, -8,-13], - [4,-6, 8,5], - [3,12, 8,-13], - [-4,2, -3,-3], - [5,-13, 10,-12], - [4,-13, 5,-1], - [-9,9, -4,3], - [0,3, 3,-9], - [-12,1, -6,1], - [3,2, 4,-8], - [-10,-10, -10,9], - [8,-13, 12,12], - [-8,-12, -6,-5], - [2,2, 3,7], - [10,6, 11,-8], - [6,8, 8,-12], - [-7,10, -6,5], - [-3,-9, -3,9], - [-1,-13, -1,5], - [-3,-7, -3,4], - [-8,-2, -8,3], - [4,2, 12,12], - [2,-5, 3,11], - [6,-9, 11,-13], - [3,-1, 7,12], - [11,-1, 12,4], - [-3,0, -3,6], - [4,-11, 4,12], - [2,-4, 2,1], - [-10,-6, -8,1], - [-13,7, -11,1], - [-13,12, -11,-13], - [6,0, 11,-13], - [0,-1, 1,4], - [-13,3, -9,-2], - [-9,8, -6,-3], - [-13,-6, -8,-2], - [5,-9, 8,10], - [2,7, 3,-9], - [-1,-6, -1,-1], - [9,5, 11,-2], - [11,-3, 12,-8], - [3,0, 3,5], - [-1,4, 0,10], - [3,-6, 4,5], - [-13,0, -10,5], - [5,8, 12,11], - [8,9, 9,-6], - [7,-4, 8,-12], - [-10,4, -10,9], - [7,3, 12,4], - [9,-7, 10,-2], - [7,0, 12,-2], - [-1,-6, 0,-11]], dtype=np.int8)) +cdef char[:, ::1] pos0 = np.array([[ 8, -3], + [ 4, 2], + [-11, 9], + [ 7, -12], + [ 2, -13], + [ 1, -7], + [ -2, -10], + [-13, -13], + [-13, -3], + [ 10, 4], + [-13, -8], + [-11, 7], + [ 7, 7], + [ -4, -5], + [-13, 2], + [ -9, 0], + [ 12, -6], + [ -3, 6], + [ -6, -13], + [ 11, -13], + [ 4, 7], + [ 5, -3], + [ 3, -7], + [ -8, -7], + [ -2, 11], + [-13, 12], + [ -7, 3], + [ -4, 2], + [-10, -12], + [ 5, -12], + [ 5, -6], + [ 1, 0], + [ 9, 11], + [ 4, 7], + [ 2, -1], + [ -4, -12], + [ -8, -5], + [ 4, 11], + [ 0, -8], + [-13, -2], + [ -3, -2], + [ -6, 9], + [ 8, 12], + [ 0, 9], + [ 7, -5], + [-13, -6], + [ 10, 7], + [ -6, -3], + [ 10, -9], + [-13, 8], + [-13, 0], + [ 3, 3], + [ 5, 7], + [ -1, 7], + [ 3, -10], + [ 2, -4], + [-13, 0], + [-13, -7], + [-13, 3], + [ -7, 12], + [ 6, -10], + [ -9, -1], + [ -2, -5], + [-12, 5], + [ 3, -10], + [ -7, -7], + [ -3, -2], + [ 2, 9], + [-11, -13], + [ -1, 6], + [ 5, -3], + [ -4, -13], + [ -9, -6], + [-12, -10], + [ 10, 2], + [ 7, 12], + [ -7, -13], + [ -4, 9], + [ 7, -1], + [ -7, 6], + [-13, 11], + [ -3, 7], + [ 7, -8], + [-13, -7], + [ 1, -3], + [ 2, -6], + [ -4, 3], + [ -1, -13], + [ 7, 1], + [ 1, -1], + [ 9, 1], + [ -1, -9], + [-13, -13], + [ 7, 7], + [ 12, -5], + [ 6, 3], + [ 5, -13], + [ 2, -12], + [ 3, 8], + [ 2, 6], + [ 9, -12], + [ -8, 4], + [-11, 12], + [ 1, 12], + [ 6, -9], + [ 2, 3], + [ 6, 3], + [ 3, -3], + [ 7, 8], + [-11, -5], + [-10, 11], + [ -5, -8], + [-10, 5], + [ 8, -1], + [ 4, -6], + [-10, 12], + [ 4, -2], + [ -2, 0], + [ -5, -8], + [ 7, -6], + [ -9, -13], + [ -5, -13], + [ 8, -8], + [ -9, -11], + [ 1, -8], + [ 7, -4], + [ -2, 1], + [ 11, -6], + [-12, -9], + [ 3, 7], + [ 5, 5], + [ 0, -4], + [ -9, 12], + [ 0, 7], + [ -1, 2], + [ 5, 11], + [ 3, 5], + [-13, -4], + [ -5, 9], + [ -4, -7], + [ 6, 5], + [ -7, 6], + [-13, 6], + [ 1, -10], + [ 4, 1], + [ -2, -2], + [ 2, -12], + [ -2, -13], + [ 4, 1], + [ -6, -10], + [ -3, -13], + [ 7, 5], + [ 4, -2], + [-13, 9], + [ 7, 1], + [ 7, -8], + [ -7, -4], + [ -8, 11], + [-13, 6], + [ 2, 4], + [ 10, -5], + [ -6, -5], + [ 8, -3], + [ 2, -12], + [-11, -2], + [-12, -13], + [-11, 0], + [ 5, -3], + [ -2, -13], + [ -1, -8], + [-13, -11], + [-10, -2], + [ -3, 9], + [ 2, -3], + [ -9, -13], + [ -4, 6], + [ -4, 12], + [ -6, -11], + [ 6, -3], + [-13, 11], + [ 11, 11], + [ 7, -5], + [ -1, 12], + [ -4, -8], + [ -7, 1], + [-13, -12], + [ -7, -2], + [ -8, 5], + [ -5, -1], + [-13, 7], + [ 1, 5], + [ 1, 0], + [ 9, 12], + [ 5, -8], + [ -1, 11], + [ -9, -3], + [ -1, -10], + [-13, 1], + [ 8, -11], + [ 2, -13], + [ 7, -13], + [-10, -10], + [-10, -8], + [ 4, -6], + [ 3, 12], + [ -4, 2], + [ 5, -13], + [ 4, -13], + [ -9, 9], + [ 0, 3], + [-12, 1], + [ 3, 2], + [-10, -10], + [ 8, -13], + [ -8, -12], + [ 2, 2], + [ 10, 6], + [ 6, 8], + [ -7, 10], + [ -3, -9], + [ -1, -13], + [ -3, -7], + [ -8, -2], + [ 4, 2], + [ 2, -5], + [ 6, -9], + [ 3, -1], + [ 11, -1], + [ -3, 0], + [ 4, -11], + [ 2, -4], + [-10, -6], + [-13, 7], + [-13, 12], + [ 6, 0], + [ 0, -1], + [-13, 3], + [ -9, 8], + [-13, -6], + [ 5, -9], + [ 2, 7], + [ -1, -6], + [ 9, 5], + [ 11, -3], + [ 3, 0], + [ -1, 4], + [ 3, -6], + [-13, 0], + [ 5, 8], + [ 8, 9], + [ 7, -4], + [-10, 4], + [ 7, 3], + [ 9, -7], + [ 7, 0], + [ -1, -6]], dtype=np.int8) + + +cdef char[:, ::1] pos1 = np.array([[ 9, 5], + [ 7, -12], + [ -8, 2], + [ 12, -13], + [ 2, 12], + [ 1, 6], + [ -2, -4], + [-11, -8], + [-12, -9], + [ 11, 9], + [ -8, -9], + [ -9, 12], + [ 12, 6], + [ -3, 0], + [-12, -3], + [ -7, 5], + [ 12, -1], + [ -2, 12], + [ -4, -8], + [ 12, -8], + [ 5, 1], + [ 10, -3], + [ 6, 12], + [ -6, -2], + [ -1, -10], + [ -8, 10], + [ -5, -3], + [ -3, 7], + [ -6, 11], + [ 6, -7], + [ 7, -1], + [ 4, -5], + [ 11, -13], + [ 4, 12], + [ 4, 4], + [ -2, 7], + [ -7, -10], + [ 9, 12], + [ 1, -13], + [ -8, 2], + [ -2, 3], + [ -4, -9], + [ 10, 7], + [ 1, 3], + [ 11, -10], + [-11, 0], + [ 12, 1], + [ -6, 12], + [ 12, -4], + [ -8, -12], + [ -8, -4], + [ 7, 8], + [ 10, -7], + [ 1, -12], + [ 5, 6], + [ 3, -10], + [-13, 5], + [-12, 12], + [-11, 8], + [ -4, 7], + [ 12, 8], + [ -7, -6], + [ 0, 12], + [ -7, 5], + [ 8, -13], + [ -4, 5], + [ -1, -7], + [ 5, -11], + [ -5, -13], + [ 0, -1], + [ 5, 2], + [ -4, 12], + [ -9, 6], + [ -8, -4], + [ 12, -3], + [ 12, 12], + [ -6, 5], + [ -3, 4], + [ 12, 2], + [ -5, 1], + [-12, 5], + [ -2, -6], + [ 12, -7], + [-11, -12], + [ 12, 12], + [ 3, 0], + [ -2, -13], + [ 1, 9], + [ 8, -6], + [ 3, 12], + [ 12, 6], + [ -1, 3], + [-10, 5], + [ 10, 12], + [ 12, 9], + [ 7, 11], + [ 6, 10], + [ 2, 3], + [ 4, -6], + [ 12, -13], + [ 10, 3], + [ -7, 9], + [ -4, -6], + [ 2, -8], + [ 7, -4], + [ 3, -2], + [ 11, 0], + [ 8, -8], + [ 9, 3], + [ -6, -4], + [ -5, 10], + [ -3, 12], + [ -9, 0], + [ 12, -6], + [ 6, -11], + [ -8, 7], + [ 6, 7], + [ -2, 12], + [ -5, 2], + [ 10, 12], + [ -8, -8], + [ -5, -2], + [ 9, -13], + [ -9, 0], + [ 1, -2], + [ 9, 1], + [ -1, -4], + [ 12, -11], + [ -6, 4], + [ 7, 12], + [ 10, 8], + [ 2, 8], + [ -5, -13], + [ 2, 12], + [ 1, 7], + [ 7, -9], + [ 6, -8], + [ -8, 9], + [ -3, -3], + [ -3, -12], + [ 8, 0], + [ -6, 12], + [ -5, -2], + [ 3, 10], + [ 8, -4], + [ 2, -13], + [ 12, 12], + [ 0, -6], + [ 9, 3], + [ -3, -5], + [ -1, 1], + [ 12, -11], + [ 5, -7], + [ -9, -5], + [ 8, 6], + [ 7, 6], + [ -7, 1], + [ -7, -8], + [-12, -8], + [ 3, 9], + [ 12, 3], + [ -6, 7], + [ 9, -8], + [ 2, 8], + [-10, 3], + [ -7, -9], + [-10, -5], + [ 11, 8], + [ -1, 12], + [ 0, 9], + [-12, -5], + [-10, 11], + [ -2, -13], + [ 3, 2], + [ -4, 0], + [ -3, -10], + [ -2, -7], + [ -4, 9], + [ 6, 11], + [ -5, 5], + [ 12, 6], + [ 12, -2], + [ 0, 7], + [ -3, -2], + [ -6, 7], + [ -8, -13], + [ -6, -8], + [ -6, -9], + [ -4, 5], + [ -8, 10], + [ 5, -13], + [ 10, -13], + [ 10, -1], + [ 10, -9], + [ 1, -13], + [ -6, 2], + [ 1, 12], + [ -8, -10], + [ 10, -6], + [ 3, -6], + [ 12, -9], + [ -5, -7], + [ -8, -13], + [ 8, 5], + [ 8, -13], + [ -3, -3], + [ 10, -12], + [ 5, -1], + [ -4, 3], + [ 3, -9], + [ -6, 1], + [ 4, -8], + [-10, 9], + [ 12, 12], + [ -6, -5], + [ 3, 7], + [ 11, -8], + [ 8, -12], + [ -6, 5], + [ -3, 9], + [ -1, 5], + [ -3, 4], + [ -8, 3], + [ 12, 12], + [ 3, 11], + [ 11, -13], + [ 7, 12], + [ 12, 4], + [ -3, 6], + [ 4, 12], + [ 2, 1], + [ -8, 1], + [-11, 1], + [-11, -13], + [ 11, -13], + [ 1, 4], + [ -9, -2], + [ -6, -3], + [ -8, -2], + [ 8, 10], + [ 3, -9], + [ -1, -1], + [ 11, -2], + [ 12, -8], + [ 3, 5], + [ 0, 10], + [ 4, 5], + [-10, 5], + [ 12, 11], + [ 9, -6], + [ 8, -12], + [-10, 9], + [ 12, 4], + [ 10, -2], + [ 12, -2], + [ 0, -11]], dtype=np.int8) From 260753974a6c97ae8af2331e2b9804271e3c32a8 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 18 Sep 2013 23:05:51 +0530 Subject: [PATCH 069/145] Fixing the import issue --- skimage/feature/corner_cy.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index c810150b..2b010693 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -7,6 +7,7 @@ cimport numpy as cnp from libc.float cimport DBL_MAX from libc.math cimport atan2 +from skimage.util import img_as_float from skimage.color import rgb2grey from .util import _prepare_grayscale_input_2D From 1fac3091254e7ca449fdd89f74d3f9d30bc43846 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 19 Sep 2013 19:31:11 +0530 Subject: [PATCH 070/145] Better wrapping of lines; making ofast_mask a global constant --- skimage/feature/orb.py | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 6f053aba..98b3cffc 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -9,8 +9,17 @@ from skimage.transform import pyramid_gaussian from .orb_cy import _orb_loop +OFAST_MASK = np.array([[0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) + + def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, - harris_k=0.05, downscale=np.sqrt(2), n_scales=5): + harris_k=0.05, downscale=np.sqrt(2), n_scales=3): """Detect Oriented Fast keypoints. @@ -20,24 +29,24 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, Input grayscale image. n_keypoints : int Number of keypoints to be returned from this function. The function - will return best `n_keypoints` if more than n_keypoints are detected + will return best ``n_keypoints`` if more than n_keypoints are detected based on the values of other parameters. If not, then all the detected keypoints are returned. fast_n : int - The `n` parameter in `feature.corner_fast`. Minimum number of + The ``n`` parameter in ``feature.corner_fast``. Minimum number of consecutive pixels out of 16 pixels on the circle that should all be either brighter or darker w.r.t testpixel. A point c on the circle is - darker w.r.t test pixel p if `Ic < Ip - threshold` and brighter if - `Ic > Ip + threshold`. Also stands for the n in `FAST-n` corner + darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if + ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner detector. fast_threshold : float - The `threshold` parameter in `feature.corner_fast`. Threshold used to + The ``threshold`` parameter in ``feature.corner_fast``. Threshold used to decide whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. harris_k : float - The `k` parameter in `feature.corner_harris`. Sensitivity factor to - separate corners from edges, typically in range `[0, 0.2]`. Small + The ``k`` parameter in ``feature.corner_harris``. Sensitivity factor to + separate corners from edges, typically in range ``[0, 0.2]``. Small values of k result in detection of sharp corners. downscale : float Downscale factor for the image pyramid. @@ -80,22 +89,17 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, >>> orientations array([-2.35619449, -0.78539816, 2.35619449, 0.78539816, 0.78539816, 2.35619449, -0.78539816, -2.35619449]) + >>> np.rad2deg(orientations) + array([-135., -45., 135., 45., 45., 135., -45., -135.]) >>> scales array([0, 0, 0, 0, 1, 1, 1, 1]) """ + image = _prepare_grayscale_input_2D(image) pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) - ofast_mask = np.array([[0, 0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) - keypoints_list = [] orientations_list = [] scales_list = [] @@ -121,8 +125,8 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, return keypoints, orientations, scales else: best_indices = harris_measure.argsort()[::-1][:n_keypoints] - return keypoints[best_indices], orientations[best_indices], \ - scales[best_indices] + return (keypoints[best_indices], orientations[best_indices], + scales[best_indices]) def descriptor_orb(image, keypoints, orientations, scales, @@ -141,7 +145,7 @@ def descriptor_orb(image, keypoints, orientations, scales, The scales of the corresponding N keypoints. downscale : float Downscale factor for the image pyramid. Should be the same as that - used in `keypoints_orb`. + used in ``keypoints_orb``. n_scales : int Number of scales from the bottom of the image pyramid to extract the features from. @@ -168,13 +172,13 @@ def descriptor_orb(image, keypoints, orientations, scales, >>> from skimage.feature import keypoints_orb, descriptor_orb >>> square = np.zeros((50, 50)) >>> square[20:30, 20:30] = 1 - >>> keypoints, orientations, scales = keypoints_orb(square, n_keypoints=8, \ - n_scales=2) + >>> keypoints, orientations, scales = keypoints_orb(square, n_keypoints=8, + ... n_scales=2) >>> keypoints.shape (8, 2) - >>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, \ - orientations, scales, \ - n_scales=2) + >>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, + ... orientations, scales, + ... n_scales=2) >>> filtered_keypoints.shape (8, 2) >>> descriptors.shape From 3ec1d35065c979ebc6ef4b64e08400d8c0924c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Thu, 19 Sep 2013 23:45:10 +0200 Subject: [PATCH 071/145] Miscellaneous fixes and improvements --- skimage/feature/orb.py | 44 +- skimage/feature/orb_cy.pyx | 539 +------------------ skimage/feature/orb_descriptor_positions.txt | 256 +++++++++ 3 files changed, 296 insertions(+), 543 deletions(-) create mode 100644 skimage/feature/orb_descriptor_positions.txt diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 98b3cffc..3de427e2 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -103,30 +103,34 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, keypoints_list = [] orientations_list = [] scales_list = [] - harris_measure_list = [] + harris_response_list = [] - for i in range(n_scales): - harris_response = corner_harris(pyramid[i], method='k', k=harris_k) - corners = corner_peaks(corner_fast(pyramid[i], fast_n, fast_threshold), - min_distance=1) - keypoints_list.append(corners) - orientations_list.append(corner_orientations(pyramid[i], corners, - ofast_mask)) - scales_list.append(i * np.ones(corners.shape[0], dtype=np.intp)) - harris_measure_list.append(harris_response[corners[:, 0], - corners[:, 1]]) + for scale in range(n_scales): - keypoints = np.vstack(keypoints_list) + corners = corner_peaks(corner_fast(pyramid[scale], fast_n, + fast_threshold), min_distance=1) + keypoints_list.append(corners * downscale ** scale) + + orientations_list.append(corner_orientations(pyramid[scale], corners, + OFAST_MASK)) + + scales_list.append(scale * np.ones(corners.shape[0], dtype=np.intp)) + + harris_response = corner_harris(pyramid[scale], method='k', k=harris_k) + harris_response_list.append(harris_response[corners[:, 0], + corners[:, 1]]) + + keypoints = np.round(np.vstack(keypoints_list)).astype(np.intp) orientations = np.hstack(orientations_list) scales = np.hstack(scales_list) - harris_measure = np.hstack(harris_measure_list) + harris_measure = np.hstack(harris_response_list) if keypoints.shape[0] < n_keypoints: return keypoints, orientations, scales else: best_indices = harris_measure.argsort()[::-1][:n_keypoints] return (keypoints[best_indices], orientations[best_indices], - scales[best_indices]) + scales[best_indices]) def descriptor_orb(image, keypoints, orientations, scales, @@ -193,11 +197,12 @@ def descriptor_orb(image, keypoints, orientations, scales, filtered_keypoints_list = [] descriptors = np.empty((0, 256), dtype=np.bool) - for k in range(n_scales): - curr_image = np.ascontiguousarray(pyramid[k]) + for scale in range(n_scales): + curr_image = np.ascontiguousarray(pyramid[scale]) - curr_scale_mask = scales == k - curr_scale_kpts = keypoints[curr_scale_mask] + curr_scale_mask = scales == scale + curr_scale_kpts = keypoints[curr_scale_mask] / (downscale ** scale) + curr_scale_kpts = np.round(curr_scale_kpts).astype(np.intp) curr_scale_orientation = orientations[curr_scale_mask] border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, @@ -211,8 +216,9 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_orientation) descriptors_list.append(curr_scale_descriptors) - filtered_keypoints_list.append(curr_scale_kpts) + filtered_keypoints_list.append(curr_scale_kpts * downscale ** scale) descriptors = np.vstack(descriptors_list).view(np.bool) filtered_keypoints = np.vstack(filtered_keypoints_list) + filtered_keypoints = np.round(filtered_keypoints).astype(np.intp) return descriptors, filtered_keypoints diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 7e157a41..bb317c7f 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,17 +9,24 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round +pos = np.loadtxt('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): 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], 256), - dtype=np.uint8) + 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 - for i in range(keypoints.shape[0]): + for i in range(descriptors.shape[0]): angle = orientations[i] sin_a = sin(angle * M_PI / 180.) cos_a = cos(angle * M_PI / 180.) @@ -27,11 +34,11 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, kr = keypoints[i, 0] kc = keypoints[i, 1] - for j in range(256): - pr0 = pos0[j, 0] - pc0 = pos0[j, 1] - pr1 = pos1[j, 0] - pc1 = pos1[j, 1] + for j in range(descriptors.shape[1]): + pr0 = cpos0[j, 0] + pc0 = cpos0[j, 1] + pr1 = cpos1[j, 0] + pc1 = cpos1[j, 1] spr0 = round(sin_a * pr0 + cos_a * pc0) spc0 = round(cos_a * pr0 - sin_a * pc0) @@ -42,519 +49,3 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, descriptors[i, j] = True return np.asarray(descriptors) - - -cdef char[:, ::1] pos0 = np.array([[ 8, -3], - [ 4, 2], - [-11, 9], - [ 7, -12], - [ 2, -13], - [ 1, -7], - [ -2, -10], - [-13, -13], - [-13, -3], - [ 10, 4], - [-13, -8], - [-11, 7], - [ 7, 7], - [ -4, -5], - [-13, 2], - [ -9, 0], - [ 12, -6], - [ -3, 6], - [ -6, -13], - [ 11, -13], - [ 4, 7], - [ 5, -3], - [ 3, -7], - [ -8, -7], - [ -2, 11], - [-13, 12], - [ -7, 3], - [ -4, 2], - [-10, -12], - [ 5, -12], - [ 5, -6], - [ 1, 0], - [ 9, 11], - [ 4, 7], - [ 2, -1], - [ -4, -12], - [ -8, -5], - [ 4, 11], - [ 0, -8], - [-13, -2], - [ -3, -2], - [ -6, 9], - [ 8, 12], - [ 0, 9], - [ 7, -5], - [-13, -6], - [ 10, 7], - [ -6, -3], - [ 10, -9], - [-13, 8], - [-13, 0], - [ 3, 3], - [ 5, 7], - [ -1, 7], - [ 3, -10], - [ 2, -4], - [-13, 0], - [-13, -7], - [-13, 3], - [ -7, 12], - [ 6, -10], - [ -9, -1], - [ -2, -5], - [-12, 5], - [ 3, -10], - [ -7, -7], - [ -3, -2], - [ 2, 9], - [-11, -13], - [ -1, 6], - [ 5, -3], - [ -4, -13], - [ -9, -6], - [-12, -10], - [ 10, 2], - [ 7, 12], - [ -7, -13], - [ -4, 9], - [ 7, -1], - [ -7, 6], - [-13, 11], - [ -3, 7], - [ 7, -8], - [-13, -7], - [ 1, -3], - [ 2, -6], - [ -4, 3], - [ -1, -13], - [ 7, 1], - [ 1, -1], - [ 9, 1], - [ -1, -9], - [-13, -13], - [ 7, 7], - [ 12, -5], - [ 6, 3], - [ 5, -13], - [ 2, -12], - [ 3, 8], - [ 2, 6], - [ 9, -12], - [ -8, 4], - [-11, 12], - [ 1, 12], - [ 6, -9], - [ 2, 3], - [ 6, 3], - [ 3, -3], - [ 7, 8], - [-11, -5], - [-10, 11], - [ -5, -8], - [-10, 5], - [ 8, -1], - [ 4, -6], - [-10, 12], - [ 4, -2], - [ -2, 0], - [ -5, -8], - [ 7, -6], - [ -9, -13], - [ -5, -13], - [ 8, -8], - [ -9, -11], - [ 1, -8], - [ 7, -4], - [ -2, 1], - [ 11, -6], - [-12, -9], - [ 3, 7], - [ 5, 5], - [ 0, -4], - [ -9, 12], - [ 0, 7], - [ -1, 2], - [ 5, 11], - [ 3, 5], - [-13, -4], - [ -5, 9], - [ -4, -7], - [ 6, 5], - [ -7, 6], - [-13, 6], - [ 1, -10], - [ 4, 1], - [ -2, -2], - [ 2, -12], - [ -2, -13], - [ 4, 1], - [ -6, -10], - [ -3, -13], - [ 7, 5], - [ 4, -2], - [-13, 9], - [ 7, 1], - [ 7, -8], - [ -7, -4], - [ -8, 11], - [-13, 6], - [ 2, 4], - [ 10, -5], - [ -6, -5], - [ 8, -3], - [ 2, -12], - [-11, -2], - [-12, -13], - [-11, 0], - [ 5, -3], - [ -2, -13], - [ -1, -8], - [-13, -11], - [-10, -2], - [ -3, 9], - [ 2, -3], - [ -9, -13], - [ -4, 6], - [ -4, 12], - [ -6, -11], - [ 6, -3], - [-13, 11], - [ 11, 11], - [ 7, -5], - [ -1, 12], - [ -4, -8], - [ -7, 1], - [-13, -12], - [ -7, -2], - [ -8, 5], - [ -5, -1], - [-13, 7], - [ 1, 5], - [ 1, 0], - [ 9, 12], - [ 5, -8], - [ -1, 11], - [ -9, -3], - [ -1, -10], - [-13, 1], - [ 8, -11], - [ 2, -13], - [ 7, -13], - [-10, -10], - [-10, -8], - [ 4, -6], - [ 3, 12], - [ -4, 2], - [ 5, -13], - [ 4, -13], - [ -9, 9], - [ 0, 3], - [-12, 1], - [ 3, 2], - [-10, -10], - [ 8, -13], - [ -8, -12], - [ 2, 2], - [ 10, 6], - [ 6, 8], - [ -7, 10], - [ -3, -9], - [ -1, -13], - [ -3, -7], - [ -8, -2], - [ 4, 2], - [ 2, -5], - [ 6, -9], - [ 3, -1], - [ 11, -1], - [ -3, 0], - [ 4, -11], - [ 2, -4], - [-10, -6], - [-13, 7], - [-13, 12], - [ 6, 0], - [ 0, -1], - [-13, 3], - [ -9, 8], - [-13, -6], - [ 5, -9], - [ 2, 7], - [ -1, -6], - [ 9, 5], - [ 11, -3], - [ 3, 0], - [ -1, 4], - [ 3, -6], - [-13, 0], - [ 5, 8], - [ 8, 9], - [ 7, -4], - [-10, 4], - [ 7, 3], - [ 9, -7], - [ 7, 0], - [ -1, -6]], dtype=np.int8) - - -cdef char[:, ::1] pos1 = np.array([[ 9, 5], - [ 7, -12], - [ -8, 2], - [ 12, -13], - [ 2, 12], - [ 1, 6], - [ -2, -4], - [-11, -8], - [-12, -9], - [ 11, 9], - [ -8, -9], - [ -9, 12], - [ 12, 6], - [ -3, 0], - [-12, -3], - [ -7, 5], - [ 12, -1], - [ -2, 12], - [ -4, -8], - [ 12, -8], - [ 5, 1], - [ 10, -3], - [ 6, 12], - [ -6, -2], - [ -1, -10], - [ -8, 10], - [ -5, -3], - [ -3, 7], - [ -6, 11], - [ 6, -7], - [ 7, -1], - [ 4, -5], - [ 11, -13], - [ 4, 12], - [ 4, 4], - [ -2, 7], - [ -7, -10], - [ 9, 12], - [ 1, -13], - [ -8, 2], - [ -2, 3], - [ -4, -9], - [ 10, 7], - [ 1, 3], - [ 11, -10], - [-11, 0], - [ 12, 1], - [ -6, 12], - [ 12, -4], - [ -8, -12], - [ -8, -4], - [ 7, 8], - [ 10, -7], - [ 1, -12], - [ 5, 6], - [ 3, -10], - [-13, 5], - [-12, 12], - [-11, 8], - [ -4, 7], - [ 12, 8], - [ -7, -6], - [ 0, 12], - [ -7, 5], - [ 8, -13], - [ -4, 5], - [ -1, -7], - [ 5, -11], - [ -5, -13], - [ 0, -1], - [ 5, 2], - [ -4, 12], - [ -9, 6], - [ -8, -4], - [ 12, -3], - [ 12, 12], - [ -6, 5], - [ -3, 4], - [ 12, 2], - [ -5, 1], - [-12, 5], - [ -2, -6], - [ 12, -7], - [-11, -12], - [ 12, 12], - [ 3, 0], - [ -2, -13], - [ 1, 9], - [ 8, -6], - [ 3, 12], - [ 12, 6], - [ -1, 3], - [-10, 5], - [ 10, 12], - [ 12, 9], - [ 7, 11], - [ 6, 10], - [ 2, 3], - [ 4, -6], - [ 12, -13], - [ 10, 3], - [ -7, 9], - [ -4, -6], - [ 2, -8], - [ 7, -4], - [ 3, -2], - [ 11, 0], - [ 8, -8], - [ 9, 3], - [ -6, -4], - [ -5, 10], - [ -3, 12], - [ -9, 0], - [ 12, -6], - [ 6, -11], - [ -8, 7], - [ 6, 7], - [ -2, 12], - [ -5, 2], - [ 10, 12], - [ -8, -8], - [ -5, -2], - [ 9, -13], - [ -9, 0], - [ 1, -2], - [ 9, 1], - [ -1, -4], - [ 12, -11], - [ -6, 4], - [ 7, 12], - [ 10, 8], - [ 2, 8], - [ -5, -13], - [ 2, 12], - [ 1, 7], - [ 7, -9], - [ 6, -8], - [ -8, 9], - [ -3, -3], - [ -3, -12], - [ 8, 0], - [ -6, 12], - [ -5, -2], - [ 3, 10], - [ 8, -4], - [ 2, -13], - [ 12, 12], - [ 0, -6], - [ 9, 3], - [ -3, -5], - [ -1, 1], - [ 12, -11], - [ 5, -7], - [ -9, -5], - [ 8, 6], - [ 7, 6], - [ -7, 1], - [ -7, -8], - [-12, -8], - [ 3, 9], - [ 12, 3], - [ -6, 7], - [ 9, -8], - [ 2, 8], - [-10, 3], - [ -7, -9], - [-10, -5], - [ 11, 8], - [ -1, 12], - [ 0, 9], - [-12, -5], - [-10, 11], - [ -2, -13], - [ 3, 2], - [ -4, 0], - [ -3, -10], - [ -2, -7], - [ -4, 9], - [ 6, 11], - [ -5, 5], - [ 12, 6], - [ 12, -2], - [ 0, 7], - [ -3, -2], - [ -6, 7], - [ -8, -13], - [ -6, -8], - [ -6, -9], - [ -4, 5], - [ -8, 10], - [ 5, -13], - [ 10, -13], - [ 10, -1], - [ 10, -9], - [ 1, -13], - [ -6, 2], - [ 1, 12], - [ -8, -10], - [ 10, -6], - [ 3, -6], - [ 12, -9], - [ -5, -7], - [ -8, -13], - [ 8, 5], - [ 8, -13], - [ -3, -3], - [ 10, -12], - [ 5, -1], - [ -4, 3], - [ 3, -9], - [ -6, 1], - [ 4, -8], - [-10, 9], - [ 12, 12], - [ -6, -5], - [ 3, 7], - [ 11, -8], - [ 8, -12], - [ -6, 5], - [ -3, 9], - [ -1, 5], - [ -3, 4], - [ -8, 3], - [ 12, 12], - [ 3, 11], - [ 11, -13], - [ 7, 12], - [ 12, 4], - [ -3, 6], - [ 4, 12], - [ 2, 1], - [ -8, 1], - [-11, 1], - [-11, -13], - [ 11, -13], - [ 1, 4], - [ -9, -2], - [ -6, -3], - [ -8, -2], - [ 8, 10], - [ 3, -9], - [ -1, -1], - [ 11, -2], - [ 12, -8], - [ 3, 5], - [ 0, 10], - [ 4, 5], - [-10, 5], - [ 12, 11], - [ 9, -6], - [ 8, -12], - [-10, 9], - [ 12, 4], - [ 10, -2], - [ 12, -2], - [ 0, -11]], dtype=np.int8) diff --git a/skimage/feature/orb_descriptor_positions.txt b/skimage/feature/orb_descriptor_positions.txt new file mode 100644 index 00000000..541f972f --- /dev/null +++ b/skimage/feature/orb_descriptor_positions.txt @@ -0,0 +1,256 @@ +8.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00 5.000000000000000000e+00 +4.000000000000000000e+00 2.000000000000000000e+00 7.000000000000000000e+00 -1.200000000000000000e+01 +-1.100000000000000000e+01 9.000000000000000000e+00 -8.000000000000000000e+00 2.000000000000000000e+00 +7.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01 -1.300000000000000000e+01 +2.000000000000000000e+00 -1.300000000000000000e+01 2.000000000000000000e+00 1.200000000000000000e+01 +1.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+00 6.000000000000000000e+00 +-2.000000000000000000e+00 -1.000000000000000000e+01 -2.000000000000000000e+00 -4.000000000000000000e+00 +-1.300000000000000000e+01 -1.300000000000000000e+01 -1.100000000000000000e+01 -8.000000000000000000e+00 +-1.300000000000000000e+01 -3.000000000000000000e+00 -1.200000000000000000e+01 -9.000000000000000000e+00 +1.000000000000000000e+01 4.000000000000000000e+00 1.100000000000000000e+01 9.000000000000000000e+00 +-1.300000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00 -9.000000000000000000e+00 +-1.100000000000000000e+01 7.000000000000000000e+00 -9.000000000000000000e+00 1.200000000000000000e+01 +7.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01 6.000000000000000000e+00 +-4.000000000000000000e+00 -5.000000000000000000e+00 -3.000000000000000000e+00 0.000000000000000000e+00 +-1.300000000000000000e+01 2.000000000000000000e+00 -1.200000000000000000e+01 -3.000000000000000000e+00 +-9.000000000000000000e+00 0.000000000000000000e+00 -7.000000000000000000e+00 5.000000000000000000e+00 +1.200000000000000000e+01 -6.000000000000000000e+00 1.200000000000000000e+01 -1.000000000000000000e+00 +-3.000000000000000000e+00 6.000000000000000000e+00 -2.000000000000000000e+00 1.200000000000000000e+01 +-6.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 -8.000000000000000000e+00 +1.100000000000000000e+01 -1.300000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00 +4.000000000000000000e+00 7.000000000000000000e+00 5.000000000000000000e+00 1.000000000000000000e+00 +5.000000000000000000e+00 -3.000000000000000000e+00 1.000000000000000000e+01 -3.000000000000000000e+00 +3.000000000000000000e+00 -7.000000000000000000e+00 6.000000000000000000e+00 1.200000000000000000e+01 +-8.000000000000000000e+00 -7.000000000000000000e+00 -6.000000000000000000e+00 -2.000000000000000000e+00 +-2.000000000000000000e+00 1.100000000000000000e+01 -1.000000000000000000e+00 -1.000000000000000000e+01 +-1.300000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00 1.000000000000000000e+01 +-7.000000000000000000e+00 3.000000000000000000e+00 -5.000000000000000000e+00 -3.000000000000000000e+00 +-4.000000000000000000e+00 2.000000000000000000e+00 -3.000000000000000000e+00 7.000000000000000000e+00 +-1.000000000000000000e+01 -1.200000000000000000e+01 -6.000000000000000000e+00 1.100000000000000000e+01 +5.000000000000000000e+00 -1.200000000000000000e+01 6.000000000000000000e+00 -7.000000000000000000e+00 +5.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00 -1.000000000000000000e+00 +1.000000000000000000e+00 0.000000000000000000e+00 4.000000000000000000e+00 -5.000000000000000000e+00 +9.000000000000000000e+00 1.100000000000000000e+01 1.100000000000000000e+01 -1.300000000000000000e+01 +4.000000000000000000e+00 7.000000000000000000e+00 4.000000000000000000e+00 1.200000000000000000e+01 +2.000000000000000000e+00 -1.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00 +-4.000000000000000000e+00 -1.200000000000000000e+01 -2.000000000000000000e+00 7.000000000000000000e+00 +-8.000000000000000000e+00 -5.000000000000000000e+00 -7.000000000000000000e+00 -1.000000000000000000e+01 +4.000000000000000000e+00 1.100000000000000000e+01 9.000000000000000000e+00 1.200000000000000000e+01 +0.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00 -1.300000000000000000e+01 +-1.300000000000000000e+01 -2.000000000000000000e+00 -8.000000000000000000e+00 2.000000000000000000e+00 +-3.000000000000000000e+00 -2.000000000000000000e+00 -2.000000000000000000e+00 3.000000000000000000e+00 +-6.000000000000000000e+00 9.000000000000000000e+00 -4.000000000000000000e+00 -9.000000000000000000e+00 +8.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+01 7.000000000000000000e+00 +0.000000000000000000e+00 9.000000000000000000e+00 1.000000000000000000e+00 3.000000000000000000e+00 +7.000000000000000000e+00 -5.000000000000000000e+00 1.100000000000000000e+01 -1.000000000000000000e+01 +-1.300000000000000000e+01 -6.000000000000000000e+00 -1.100000000000000000e+01 0.000000000000000000e+00 +1.000000000000000000e+01 7.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+00 +-6.000000000000000000e+00 -3.000000000000000000e+00 -6.000000000000000000e+00 1.200000000000000000e+01 +1.000000000000000000e+01 -9.000000000000000000e+00 1.200000000000000000e+01 -4.000000000000000000e+00 +-1.300000000000000000e+01 8.000000000000000000e+00 -8.000000000000000000e+00 -1.200000000000000000e+01 +-1.300000000000000000e+01 0.000000000000000000e+00 -8.000000000000000000e+00 -4.000000000000000000e+00 +3.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00 8.000000000000000000e+00 +5.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+01 -7.000000000000000000e+00 +-1.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+00 -1.200000000000000000e+01 +3.000000000000000000e+00 -1.000000000000000000e+01 5.000000000000000000e+00 6.000000000000000000e+00 +2.000000000000000000e+00 -4.000000000000000000e+00 3.000000000000000000e+00 -1.000000000000000000e+01 +-1.300000000000000000e+01 0.000000000000000000e+00 -1.300000000000000000e+01 5.000000000000000000e+00 +-1.300000000000000000e+01 -7.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01 +-1.300000000000000000e+01 3.000000000000000000e+00 -1.100000000000000000e+01 8.000000000000000000e+00 +-7.000000000000000000e+00 1.200000000000000000e+01 -4.000000000000000000e+00 7.000000000000000000e+00 +6.000000000000000000e+00 -1.000000000000000000e+01 1.200000000000000000e+01 8.000000000000000000e+00 +-9.000000000000000000e+00 -1.000000000000000000e+00 -7.000000000000000000e+00 -6.000000000000000000e+00 +-2.000000000000000000e+00 -5.000000000000000000e+00 0.000000000000000000e+00 1.200000000000000000e+01 +-1.200000000000000000e+01 5.000000000000000000e+00 -7.000000000000000000e+00 5.000000000000000000e+00 +3.000000000000000000e+00 -1.000000000000000000e+01 8.000000000000000000e+00 -1.300000000000000000e+01 +-7.000000000000000000e+00 -7.000000000000000000e+00 -4.000000000000000000e+00 5.000000000000000000e+00 +-3.000000000000000000e+00 -2.000000000000000000e+00 -1.000000000000000000e+00 -7.000000000000000000e+00 +2.000000000000000000e+00 9.000000000000000000e+00 5.000000000000000000e+00 -1.100000000000000000e+01 +-1.100000000000000000e+01 -1.300000000000000000e+01 -5.000000000000000000e+00 -1.300000000000000000e+01 +-1.000000000000000000e+00 6.000000000000000000e+00 0.000000000000000000e+00 -1.000000000000000000e+00 +5.000000000000000000e+00 -3.000000000000000000e+00 5.000000000000000000e+00 2.000000000000000000e+00 +-4.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 1.200000000000000000e+01 +-9.000000000000000000e+00 -6.000000000000000000e+00 -9.000000000000000000e+00 6.000000000000000000e+00 +-1.200000000000000000e+01 -1.000000000000000000e+01 -8.000000000000000000e+00 -4.000000000000000000e+00 +1.000000000000000000e+01 2.000000000000000000e+00 1.200000000000000000e+01 -3.000000000000000000e+00 +7.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01 +-7.000000000000000000e+00 -1.300000000000000000e+01 -6.000000000000000000e+00 5.000000000000000000e+00 +-4.000000000000000000e+00 9.000000000000000000e+00 -3.000000000000000000e+00 4.000000000000000000e+00 +7.000000000000000000e+00 -1.000000000000000000e+00 1.200000000000000000e+01 2.000000000000000000e+00 +-7.000000000000000000e+00 6.000000000000000000e+00 -5.000000000000000000e+00 1.000000000000000000e+00 +-1.300000000000000000e+01 1.100000000000000000e+01 -1.200000000000000000e+01 5.000000000000000000e+00 +-3.000000000000000000e+00 7.000000000000000000e+00 -2.000000000000000000e+00 -6.000000000000000000e+00 +7.000000000000000000e+00 -8.000000000000000000e+00 1.200000000000000000e+01 -7.000000000000000000e+00 +-1.300000000000000000e+01 -7.000000000000000000e+00 -1.100000000000000000e+01 -1.200000000000000000e+01 +1.000000000000000000e+00 -3.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01 +2.000000000000000000e+00 -6.000000000000000000e+00 3.000000000000000000e+00 0.000000000000000000e+00 +-4.000000000000000000e+00 3.000000000000000000e+00 -2.000000000000000000e+00 -1.300000000000000000e+01 +-1.000000000000000000e+00 -1.300000000000000000e+01 1.000000000000000000e+00 9.000000000000000000e+00 +7.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 -6.000000000000000000e+00 +1.000000000000000000e+00 -1.000000000000000000e+00 3.000000000000000000e+00 1.200000000000000000e+01 +9.000000000000000000e+00 1.000000000000000000e+00 1.200000000000000000e+01 6.000000000000000000e+00 +-1.000000000000000000e+00 -9.000000000000000000e+00 -1.000000000000000000e+00 3.000000000000000000e+00 +-1.300000000000000000e+01 -1.300000000000000000e+01 -1.000000000000000000e+01 5.000000000000000000e+00 +7.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+01 1.200000000000000000e+01 +1.200000000000000000e+01 -5.000000000000000000e+00 1.200000000000000000e+01 9.000000000000000000e+00 +6.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00 1.100000000000000000e+01 +5.000000000000000000e+00 -1.300000000000000000e+01 6.000000000000000000e+00 1.000000000000000000e+01 +2.000000000000000000e+00 -1.200000000000000000e+01 2.000000000000000000e+00 3.000000000000000000e+00 +3.000000000000000000e+00 8.000000000000000000e+00 4.000000000000000000e+00 -6.000000000000000000e+00 +2.000000000000000000e+00 6.000000000000000000e+00 1.200000000000000000e+01 -1.300000000000000000e+01 +9.000000000000000000e+00 -1.200000000000000000e+01 1.000000000000000000e+01 3.000000000000000000e+00 +-8.000000000000000000e+00 4.000000000000000000e+00 -7.000000000000000000e+00 9.000000000000000000e+00 +-1.100000000000000000e+01 1.200000000000000000e+01 -4.000000000000000000e+00 -6.000000000000000000e+00 +1.000000000000000000e+00 1.200000000000000000e+01 2.000000000000000000e+00 -8.000000000000000000e+00 +6.000000000000000000e+00 -9.000000000000000000e+00 7.000000000000000000e+00 -4.000000000000000000e+00 +2.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 -2.000000000000000000e+00 +6.000000000000000000e+00 3.000000000000000000e+00 1.100000000000000000e+01 0.000000000000000000e+00 +3.000000000000000000e+00 -3.000000000000000000e+00 8.000000000000000000e+00 -8.000000000000000000e+00 +7.000000000000000000e+00 8.000000000000000000e+00 9.000000000000000000e+00 3.000000000000000000e+00 +-1.100000000000000000e+01 -5.000000000000000000e+00 -6.000000000000000000e+00 -4.000000000000000000e+00 +-1.000000000000000000e+01 1.100000000000000000e+01 -5.000000000000000000e+00 1.000000000000000000e+01 +-5.000000000000000000e+00 -8.000000000000000000e+00 -3.000000000000000000e+00 1.200000000000000000e+01 +-1.000000000000000000e+01 5.000000000000000000e+00 -9.000000000000000000e+00 0.000000000000000000e+00 +8.000000000000000000e+00 -1.000000000000000000e+00 1.200000000000000000e+01 -6.000000000000000000e+00 +4.000000000000000000e+00 -6.000000000000000000e+00 6.000000000000000000e+00 -1.100000000000000000e+01 +-1.000000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00 7.000000000000000000e+00 +4.000000000000000000e+00 -2.000000000000000000e+00 6.000000000000000000e+00 7.000000000000000000e+00 +-2.000000000000000000e+00 0.000000000000000000e+00 -2.000000000000000000e+00 1.200000000000000000e+01 +-5.000000000000000000e+00 -8.000000000000000000e+00 -5.000000000000000000e+00 2.000000000000000000e+00 +7.000000000000000000e+00 -6.000000000000000000e+00 1.000000000000000000e+01 1.200000000000000000e+01 +-9.000000000000000000e+00 -1.300000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00 +-5.000000000000000000e+00 -1.300000000000000000e+01 -5.000000000000000000e+00 -2.000000000000000000e+00 +8.000000000000000000e+00 -8.000000000000000000e+00 9.000000000000000000e+00 -1.300000000000000000e+01 +-9.000000000000000000e+00 -1.100000000000000000e+01 -9.000000000000000000e+00 0.000000000000000000e+00 +1.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00 -2.000000000000000000e+00 +7.000000000000000000e+00 -4.000000000000000000e+00 9.000000000000000000e+00 1.000000000000000000e+00 +-2.000000000000000000e+00 1.000000000000000000e+00 -1.000000000000000000e+00 -4.000000000000000000e+00 +1.100000000000000000e+01 -6.000000000000000000e+00 1.200000000000000000e+01 -1.100000000000000000e+01 +-1.200000000000000000e+01 -9.000000000000000000e+00 -6.000000000000000000e+00 4.000000000000000000e+00 +3.000000000000000000e+00 7.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01 +5.000000000000000000e+00 5.000000000000000000e+00 1.000000000000000000e+01 8.000000000000000000e+00 +0.000000000000000000e+00 -4.000000000000000000e+00 2.000000000000000000e+00 8.000000000000000000e+00 +-9.000000000000000000e+00 1.200000000000000000e+01 -5.000000000000000000e+00 -1.300000000000000000e+01 +0.000000000000000000e+00 7.000000000000000000e+00 2.000000000000000000e+00 1.200000000000000000e+01 +-1.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 7.000000000000000000e+00 +5.000000000000000000e+00 1.100000000000000000e+01 7.000000000000000000e+00 -9.000000000000000000e+00 +3.000000000000000000e+00 5.000000000000000000e+00 6.000000000000000000e+00 -8.000000000000000000e+00 +-1.300000000000000000e+01 -4.000000000000000000e+00 -8.000000000000000000e+00 9.000000000000000000e+00 +-5.000000000000000000e+00 9.000000000000000000e+00 -3.000000000000000000e+00 -3.000000000000000000e+00 +-4.000000000000000000e+00 -7.000000000000000000e+00 -3.000000000000000000e+00 -1.200000000000000000e+01 +6.000000000000000000e+00 5.000000000000000000e+00 8.000000000000000000e+00 0.000000000000000000e+00 +-7.000000000000000000e+00 6.000000000000000000e+00 -6.000000000000000000e+00 1.200000000000000000e+01 +-1.300000000000000000e+01 6.000000000000000000e+00 -5.000000000000000000e+00 -2.000000000000000000e+00 +1.000000000000000000e+00 -1.000000000000000000e+01 3.000000000000000000e+00 1.000000000000000000e+01 +4.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 -4.000000000000000000e+00 +-2.000000000000000000e+00 -2.000000000000000000e+00 2.000000000000000000e+00 -1.300000000000000000e+01 +2.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01 +-2.000000000000000000e+00 -1.300000000000000000e+01 0.000000000000000000e+00 -6.000000000000000000e+00 +4.000000000000000000e+00 1.000000000000000000e+00 9.000000000000000000e+00 3.000000000000000000e+00 +-6.000000000000000000e+00 -1.000000000000000000e+01 -3.000000000000000000e+00 -5.000000000000000000e+00 +-3.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 1.000000000000000000e+00 +7.000000000000000000e+00 5.000000000000000000e+00 1.200000000000000000e+01 -1.100000000000000000e+01 +4.000000000000000000e+00 -2.000000000000000000e+00 5.000000000000000000e+00 -7.000000000000000000e+00 +-1.300000000000000000e+01 9.000000000000000000e+00 -9.000000000000000000e+00 -5.000000000000000000e+00 +7.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 6.000000000000000000e+00 +7.000000000000000000e+00 -8.000000000000000000e+00 7.000000000000000000e+00 6.000000000000000000e+00 +-7.000000000000000000e+00 -4.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+00 +-8.000000000000000000e+00 1.100000000000000000e+01 -7.000000000000000000e+00 -8.000000000000000000e+00 +-1.300000000000000000e+01 6.000000000000000000e+00 -1.200000000000000000e+01 -8.000000000000000000e+00 +2.000000000000000000e+00 4.000000000000000000e+00 3.000000000000000000e+00 9.000000000000000000e+00 +1.000000000000000000e+01 -5.000000000000000000e+00 1.200000000000000000e+01 3.000000000000000000e+00 +-6.000000000000000000e+00 -5.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00 +8.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00 -8.000000000000000000e+00 +2.000000000000000000e+00 -1.200000000000000000e+01 2.000000000000000000e+00 8.000000000000000000e+00 +-1.100000000000000000e+01 -2.000000000000000000e+00 -1.000000000000000000e+01 3.000000000000000000e+00 +-1.200000000000000000e+01 -1.300000000000000000e+01 -7.000000000000000000e+00 -9.000000000000000000e+00 +-1.100000000000000000e+01 0.000000000000000000e+00 -1.000000000000000000e+01 -5.000000000000000000e+00 +5.000000000000000000e+00 -3.000000000000000000e+00 1.100000000000000000e+01 8.000000000000000000e+00 +-2.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 1.200000000000000000e+01 +-1.000000000000000000e+00 -8.000000000000000000e+00 0.000000000000000000e+00 9.000000000000000000e+00 +-1.300000000000000000e+01 -1.100000000000000000e+01 -1.200000000000000000e+01 -5.000000000000000000e+00 +-1.000000000000000000e+01 -2.000000000000000000e+00 -1.000000000000000000e+01 1.100000000000000000e+01 +-3.000000000000000000e+00 9.000000000000000000e+00 -2.000000000000000000e+00 -1.300000000000000000e+01 +2.000000000000000000e+00 -3.000000000000000000e+00 3.000000000000000000e+00 2.000000000000000000e+00 +-9.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 0.000000000000000000e+00 +-4.000000000000000000e+00 6.000000000000000000e+00 -3.000000000000000000e+00 -1.000000000000000000e+01 +-4.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00 -7.000000000000000000e+00 +-6.000000000000000000e+00 -1.100000000000000000e+01 -4.000000000000000000e+00 9.000000000000000000e+00 +6.000000000000000000e+00 -3.000000000000000000e+00 6.000000000000000000e+00 1.100000000000000000e+01 +-1.300000000000000000e+01 1.100000000000000000e+01 -5.000000000000000000e+00 5.000000000000000000e+00 +1.100000000000000000e+01 1.100000000000000000e+01 1.200000000000000000e+01 6.000000000000000000e+00 +7.000000000000000000e+00 -5.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00 +-1.000000000000000000e+00 1.200000000000000000e+01 0.000000000000000000e+00 7.000000000000000000e+00 +-4.000000000000000000e+00 -8.000000000000000000e+00 -3.000000000000000000e+00 -2.000000000000000000e+00 +-7.000000000000000000e+00 1.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00 +-1.300000000000000000e+01 -1.200000000000000000e+01 -8.000000000000000000e+00 -1.300000000000000000e+01 +-7.000000000000000000e+00 -2.000000000000000000e+00 -6.000000000000000000e+00 -8.000000000000000000e+00 +-8.000000000000000000e+00 5.000000000000000000e+00 -6.000000000000000000e+00 -9.000000000000000000e+00 +-5.000000000000000000e+00 -1.000000000000000000e+00 -4.000000000000000000e+00 5.000000000000000000e+00 +-1.300000000000000000e+01 7.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+01 +1.000000000000000000e+00 5.000000000000000000e+00 5.000000000000000000e+00 -1.300000000000000000e+01 +1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+01 -1.300000000000000000e+01 +9.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+01 -1.000000000000000000e+00 +5.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+01 -9.000000000000000000e+00 +-1.000000000000000000e+00 1.100000000000000000e+01 1.000000000000000000e+00 -1.300000000000000000e+01 +-9.000000000000000000e+00 -3.000000000000000000e+00 -6.000000000000000000e+00 2.000000000000000000e+00 +-1.000000000000000000e+00 -1.000000000000000000e+01 1.000000000000000000e+00 1.200000000000000000e+01 +-1.300000000000000000e+01 1.000000000000000000e+00 -8.000000000000000000e+00 -1.000000000000000000e+01 +8.000000000000000000e+00 -1.100000000000000000e+01 1.000000000000000000e+01 -6.000000000000000000e+00 +2.000000000000000000e+00 -1.300000000000000000e+01 3.000000000000000000e+00 -6.000000000000000000e+00 +7.000000000000000000e+00 -1.300000000000000000e+01 1.200000000000000000e+01 -9.000000000000000000e+00 +-1.000000000000000000e+01 -1.000000000000000000e+01 -5.000000000000000000e+00 -7.000000000000000000e+00 +-1.000000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00 -1.300000000000000000e+01 +4.000000000000000000e+00 -6.000000000000000000e+00 8.000000000000000000e+00 5.000000000000000000e+00 +3.000000000000000000e+00 1.200000000000000000e+01 8.000000000000000000e+00 -1.300000000000000000e+01 +-4.000000000000000000e+00 2.000000000000000000e+00 -3.000000000000000000e+00 -3.000000000000000000e+00 +5.000000000000000000e+00 -1.300000000000000000e+01 1.000000000000000000e+01 -1.200000000000000000e+01 +4.000000000000000000e+00 -1.300000000000000000e+01 5.000000000000000000e+00 -1.000000000000000000e+00 +-9.000000000000000000e+00 9.000000000000000000e+00 -4.000000000000000000e+00 3.000000000000000000e+00 +0.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 -9.000000000000000000e+00 +-1.200000000000000000e+01 1.000000000000000000e+00 -6.000000000000000000e+00 1.000000000000000000e+00 +3.000000000000000000e+00 2.000000000000000000e+00 4.000000000000000000e+00 -8.000000000000000000e+00 +-1.000000000000000000e+01 -1.000000000000000000e+01 -1.000000000000000000e+01 9.000000000000000000e+00 +8.000000000000000000e+00 -1.300000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01 +-8.000000000000000000e+00 -1.200000000000000000e+01 -6.000000000000000000e+00 -5.000000000000000000e+00 +2.000000000000000000e+00 2.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00 +1.000000000000000000e+01 6.000000000000000000e+00 1.100000000000000000e+01 -8.000000000000000000e+00 +6.000000000000000000e+00 8.000000000000000000e+00 8.000000000000000000e+00 -1.200000000000000000e+01 +-7.000000000000000000e+00 1.000000000000000000e+01 -6.000000000000000000e+00 5.000000000000000000e+00 +-3.000000000000000000e+00 -9.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00 +-1.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 5.000000000000000000e+00 +-3.000000000000000000e+00 -7.000000000000000000e+00 -3.000000000000000000e+00 4.000000000000000000e+00 +-8.000000000000000000e+00 -2.000000000000000000e+00 -8.000000000000000000e+00 3.000000000000000000e+00 +4.000000000000000000e+00 2.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01 +2.000000000000000000e+00 -5.000000000000000000e+00 3.000000000000000000e+00 1.100000000000000000e+01 +6.000000000000000000e+00 -9.000000000000000000e+00 1.100000000000000000e+01 -1.300000000000000000e+01 +3.000000000000000000e+00 -1.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01 +1.100000000000000000e+01 -1.000000000000000000e+00 1.200000000000000000e+01 4.000000000000000000e+00 +-3.000000000000000000e+00 0.000000000000000000e+00 -3.000000000000000000e+00 6.000000000000000000e+00 +4.000000000000000000e+00 -1.100000000000000000e+01 4.000000000000000000e+00 1.200000000000000000e+01 +2.000000000000000000e+00 -4.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 +-1.000000000000000000e+01 -6.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00 +-1.300000000000000000e+01 7.000000000000000000e+00 -1.100000000000000000e+01 1.000000000000000000e+00 +-1.300000000000000000e+01 1.200000000000000000e+01 -1.100000000000000000e+01 -1.300000000000000000e+01 +6.000000000000000000e+00 0.000000000000000000e+00 1.100000000000000000e+01 -1.300000000000000000e+01 +0.000000000000000000e+00 -1.000000000000000000e+00 1.000000000000000000e+00 4.000000000000000000e+00 +-1.300000000000000000e+01 3.000000000000000000e+00 -9.000000000000000000e+00 -2.000000000000000000e+00 +-9.000000000000000000e+00 8.000000000000000000e+00 -6.000000000000000000e+00 -3.000000000000000000e+00 +-1.300000000000000000e+01 -6.000000000000000000e+00 -8.000000000000000000e+00 -2.000000000000000000e+00 +5.000000000000000000e+00 -9.000000000000000000e+00 8.000000000000000000e+00 1.000000000000000000e+01 +2.000000000000000000e+00 7.000000000000000000e+00 3.000000000000000000e+00 -9.000000000000000000e+00 +-1.000000000000000000e+00 -6.000000000000000000e+00 -1.000000000000000000e+00 -1.000000000000000000e+00 +9.000000000000000000e+00 5.000000000000000000e+00 1.100000000000000000e+01 -2.000000000000000000e+00 +1.100000000000000000e+01 -3.000000000000000000e+00 1.200000000000000000e+01 -8.000000000000000000e+00 +3.000000000000000000e+00 0.000000000000000000e+00 3.000000000000000000e+00 5.000000000000000000e+00 +-1.000000000000000000e+00 4.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+01 +3.000000000000000000e+00 -6.000000000000000000e+00 4.000000000000000000e+00 5.000000000000000000e+00 +-1.300000000000000000e+01 0.000000000000000000e+00 -1.000000000000000000e+01 5.000000000000000000e+00 +5.000000000000000000e+00 8.000000000000000000e+00 1.200000000000000000e+01 1.100000000000000000e+01 +8.000000000000000000e+00 9.000000000000000000e+00 9.000000000000000000e+00 -6.000000000000000000e+00 +7.000000000000000000e+00 -4.000000000000000000e+00 8.000000000000000000e+00 -1.200000000000000000e+01 +-1.000000000000000000e+01 4.000000000000000000e+00 -1.000000000000000000e+01 9.000000000000000000e+00 +7.000000000000000000e+00 3.000000000000000000e+00 1.200000000000000000e+01 4.000000000000000000e+00 +9.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+01 -2.000000000000000000e+00 +7.000000000000000000e+00 0.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00 +-1.000000000000000000e+00 -6.000000000000000000e+00 0.000000000000000000e+00 -1.100000000000000000e+01 From a53d93e0f7ae3b2dd628843efbad8f790d075fa7 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 21 Sep 2013 03:13:30 +0530 Subject: [PATCH 072/145] Improved match_binary_descriptors function --- bento.info | 3 ++ skimage/feature/_brief.py | 50 -------------------- skimage/feature/match.py | 88 ++++++++++++++++++++++++++++++++++++ skimage/feature/match_cy.pyx | 21 +++++++++ skimage/feature/orb_cy.pyx | 2 +- skimage/feature/setup.py | 3 ++ 6 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 skimage/feature/match.py create mode 100644 skimage/feature/match_cy.pyx diff --git a/bento.info b/bento.info index 725d1c06..f819d7c0 100644 --- a/bento.info +++ b/bento.info @@ -96,6 +96,9 @@ Library: Extension: skimage.feature.censure_cy Sources: skimage/feature/censure_cy.pyx + Extension: skimage.feature.match_cy + Sources: + skimage/feature/match_cy.pyx Extension: skimage.feature.orb_cy Sources: skimage/feature/orb_cy.pyx diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 9e0475d3..be7e4ac9 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -173,53 +173,3 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) return descriptors, keypoints - - -def match_keypoints_brief(keypoints1, descriptors1, keypoints2, - descriptors2, threshold=0.15): - """**Experimental function**. - - Match keypoints described using BRIEF descriptors in one image to - those in second image. - - Parameters - ---------- - keypoints1 : (M, 2) ndarray - M Keypoints from the first image described using skimage.feature.brief - descriptors1 : (M, P) ndarray - BRIEF descriptors of size P about M keypoints in the first image. - keypoints2 : (N, 2) ndarray - N Keypoints from the second image described using skimage.feature.brief - descriptors2 : (N, P) ndarray - BRIEF descriptors of size P about N keypoints in the second image. - threshold : float in range [0, 1] - Maximum allowable hamming distance between descriptors of two keypoints - in separate images to be regarded as a match. Default is 0.15. - - Returns - ------- - match_keypoints_brief : (Q, 2, 2) ndarray - Location of Q matched keypoint pairs from two images. - - """ - if (keypoints1.shape[0] != descriptors1.shape[0] - or keypoints2.shape[0] != descriptors2.shape[0]): - raise ValueError("The number of keypoints and number of described " - "keypoints do not match. Make the optional parameter " - "return_keypoints True to get described keypoints.") - - if descriptors1.shape[1] != descriptors2.shape[1]: - raise ValueError("Descriptor sizes for matching keypoints in both " - "the images should be equal.") - - # Get hamming distances between keeypoints1 and keypoints2 - distance = pairwise_hamming_distance(descriptors1, descriptors2) - - temp = distance > threshold - row_check = np.any(~temp, axis=1) - matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] - matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp) - matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] - matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] - - return matched_keypoint_pairs diff --git a/skimage/feature/match.py b/skimage/feature/match.py new file mode 100644 index 00000000..d9f95c90 --- /dev/null +++ b/skimage/feature/match.py @@ -0,0 +1,88 @@ +import numpy as np + +from .util import pairwise_hamming_distance +from .match_cy import _binary_cross_check_loop + + +def match_binary_descriptors(keypoints1, descriptors1, keypoints2, + descriptors2, threshold=0.40, cross_check=True, + return_mask=True): + """Match keypoints described using binary descriptors in one image to + those in second image. + + Parameters + ---------- + keypoints1 : (M, 2) ndarray + M Keypoints from the first image described using skimage.feature.brief + descriptors1 : (M, P) ndarray + BRIEF descriptors of size P about M keypoints in the first image. + keypoints2 : (N, 2) ndarray + N Keypoints from the second image described using skimage.feature.brief + descriptors2 : (N, P) ndarray + BRIEF descriptors of size P about N keypoints in the second image. + threshold : float in range [0, 1] + Maximum allowable hamming distance between descriptors of two keypoints + in separate images to be regarded as a match. + cross_check : bool + Cross check if True. + return_mask : bool + Return index masks mask1 and mask2 for matched keypoints from + keypoints1 and keypoints2 respectively. + + Returns + ------- + match_keypoints_pairs : (Q, 2, 2) ndarray + Location of Q matched keypoint pairs from two images. + mask1 : (Q,) ndarray + Indices of keypoints in keypoints1 that have been matched. Returned + only when return_mask is True. + mask2 : (Q,) ndarray + Indices of keypoints in keypoints2 that have been matched. Returned + only when return_mask is True. + + """ + if (keypoints1.shape[0] != descriptors1.shape[0] + or keypoints2.shape[0] != descriptors2.shape[0]): + raise ValueError("The number of keypoints and number of described " + "keypoints do not match.") + + if descriptors1.shape[1] != descriptors2.shape[1]: + raise ValueError("Descriptor sizes for matching keypoints in both " + "the images should be equal.") + + # Get hamming distances between keypoints1 and keypoints2 + distance = pairwise_hamming_distance(descriptors1, descriptors2) + + 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_keypoints2_index, + distance, threshold) + + matched_keypoint_pairs = np.zeros((matched_index.shape[0], 2, 2), + dtype=np.intp) + mask1 = matched_index[:, 0] + mask2 = matched_index[:, 1] + matched_keypoint_pairs[:, 0, :] = keypoints1[mask1] + matched_keypoint_pairs[:, 1, :] = keypoints2[mask2] + if return_mask: + return (matched_keypoint_pairs, mask1, mask2) + else: + return matched_keypoint_pairs + + else: + temp = distance > threshold + row_check = np.any(~temp, axis=1) + matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] + matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), + dtype=np.intp) + matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] + matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] + mask1 = np.where(row_check == True) + mask2 = np.argmin(distance, axis=1)[row_check] + if return_mask: + return (matched_keypoint_pairs, mask1, mask2) + else: + return matched_keypoint_pairs diff --git a/skimage/feature/match_cy.pyx b/skimage/feature/match_cy.pyx new file mode 100644 index 00000000..8a1bb461 --- /dev/null +++ b/skimage/feature/match_cy.pyx @@ -0,0 +1,21 @@ +import numpy as np + + +def _binary_cross_check_loop(Py_ssize_t[:] matched_keypoints1_index, + Py_ssize_t[:] matched_keypoints2_index, + double[:, ::1] distance, double threshold): + cdef Py_ssize_t i + #matched_index = [] + + cdef Py_ssize_t count = 0 + cdef Py_ssize_t[:, ::1] matched_index = np.zeros((len(matched_keypoints1_index), 2), dtype=np.intp) + + for i in range(len(matched_keypoints1_index)): + if (matched_keypoints2_index[matched_keypoints1_index[i]] == i and + distance[i, matched_keypoints1_index[i]] < threshold): + #matched_index.append([i, matched_keypoints1_index[i]]) + matched_index[count, 0] = i + matched_index[count, 1] = matched_keypoints1_index[i] + count += 1 + + return np.asarray(matched_index[:count, :]) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index bb317c7f..e7197797 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,7 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round -pos = np.loadtxt('orb_descriptor_positions.txt', dtype=np.int8) +pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) pos0 = np.ascontiguousarray(pos[:, :2]) pos1 = np.ascontiguousarray(pos[:, 2:]) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 4f54faeb..9bd72ca3 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -16,6 +16,7 @@ def configuration(parent_package='', top_path=None): cython(['censure_cy.pyx'], working_path=base_path) cython(['orb_cy.pyx'], working_path=base_path) cython(['_brief_cy.pyx'], working_path=base_path) + cython(['match_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) @@ -27,6 +28,8 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('_brief_cy', sources=['_brief_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('match_cy', sources=['match_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'], From ba92c47497f04d8435120c0bc558587b3f7e65e3 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 21 Sep 2013 20:15:58 +0530 Subject: [PATCH 073/145] ORB matching example --- doc/examples/plot_orb_matching.py | 60 +++++++++++++++++++++++++++++++ skimage/feature/__init__.py | 5 +-- skimage/feature/orb.py | 6 ++-- skimage/feature/orb_cy.pyx | 2 +- 4 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 doc/examples/plot_orb_matching.py diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb_matching.py new file mode 100644 index 00000000..7d1119ab --- /dev/null +++ b/doc/examples/plot_orb_matching.py @@ -0,0 +1,60 @@ +import numpy as np +from skimage import data +from skimage import transform as tf +from skimage.feature import pairwise_hamming_distance, brief, match_binary_descriptors, corner_harris, corner_peaks, keypoints_orb, descriptor_orb +from skimage.color import rgb2gray +from skimage import img_as_float +import matplotlib.pyplot as plt + +rotate = 0.5 +translate = (-100, -200) +scaling = (1.5, 1.5) +match_threshold = 0.40 +match_cross_check = True + +img_color = data.lena() +tform = tf.AffineTransform(scale = scaling, rotation=rotate, translation=translate) +transformed_img_color = tf.warp(img_color, tform) +img = rgb2gray(img_color) +transformed_img = rgb2gray(transformed_img_color) + +keypoints1, orientations1, scales1 = keypoints_orb(img, n_keypoints=250) +keypoints1.shape +descriptors1, keypoints1 = descriptor_orb(img, keypoints1, orientations1, scales1) +keypoints1.shape +descriptors1.shape + +keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, n_keypoints=250) +keypoints2.shape +descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2, orientations2, scales2) +keypoints2.shape +descriptors2.shape + +pairwise_hamming_distance(descriptors1, descriptors2) +matched_keypoints, mask1, mask2 = match_binary_descriptors(keypoints1, descriptors1, keypoints2, descriptors2, cross_check=match_cross_check, threshold=match_threshold) + +matched_keypoints.shape + +# Plotting the matched correspondences in both the images using matplotlib +src = matched_keypoints[:, 0, :] +dst = matched_keypoints[:, 1, :] +src_scale = 10 * (scales1[mask1] + 1) ** 2 +dst_scale = 10 * (scales2[mask2] + 1) ** 2 + +img_combined = np.concatenate((img_as_float(img_color), img_as_float(transformed_img_color)), axis=1) +offset = img.shape + +fig, ax = plt.subplots(nrows=1, ncols=1) +plt.gray() + +ax.imshow(img_combined, interpolation='nearest') +ax.axis('off') +ax.axis((0, 2 * offset[1], offset[0], 0)) +ax.set_title('Matched correspondences : Rotation = %f; Scale = %s; Translation = %s; threshold = %f; cross_check = %r' % (rotate, scaling, translate, match_threshold, match_cross_check)) + +for m in range(len(src)): + ax.plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), '-', color='g') + ax.scatter(src[m, 1], src[m, 0], src_scale[m], facecolors='none', edgecolors='b') + ax.scatter(dst[m, 1] + offset[1], dst[m, 0], dst_scale[m], facecolors='none', edgecolors='b') + +plt.show() diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 8b33d6c7..89370fd7 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -9,7 +9,8 @@ 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 brief, match_keypoints_brief +from ._brief import brief +from .match import match_binary_descriptors from .util import pairwise_hamming_distance from .censure import keypoints_censure from .orb import keypoints_orb, descriptor_orb @@ -30,7 +31,7 @@ __all__ = ['daisy', 'match_template', 'brief', 'pairwise_hamming_distance', - 'match_keypoints_brief', + 'match_binary_descriptors', 'keypoints_censure', 'corner_fast', 'corner_orientations', diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 3de427e2..577ce9cc 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -19,7 +19,7 @@ OFAST_MASK = np.array([[0, 0, 1, 1, 1, 0, 0], def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, - harris_k=0.05, downscale=np.sqrt(2), n_scales=3): + harris_k=0.05, downscale=np.sqrt(2), n_scales=4): """Detect Oriented Fast keypoints. @@ -134,7 +134,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, def descriptor_orb(image, keypoints, orientations, scales, - downscale=np.sqrt(2), n_scales=5): + downscale=np.sqrt(2), n_scales=4): """Compute rBRIEF descriptors of input keypoints. Parameters @@ -206,7 +206,7 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_orientation = orientations[curr_scale_mask] border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, - dist=13) + dist=14) curr_scale_kpts = curr_scale_kpts[border_mask] curr_scale_orientation = curr_scale_orientation[border_mask] diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index e7197797..e312f95b 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,7 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round -pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) +pos = np.loadtxt("skimage/feature/orb_descriptor_positions.txt", dtype=np.int8) pos0 = np.ascontiguousarray(pos[:, :2]) pos1 = np.ascontiguousarray(pos[:, 2:]) From f0fea633508231e3daea3f32bb53bda1915169b1 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 21 Sep 2013 22:20:21 +0530 Subject: [PATCH 074/145] Minor code changes; Explicit docs --- skimage/feature/match.py | 45 +++++++++++++++--------------------- skimage/feature/match_cy.pyx | 3 --- skimage/feature/orb_cy.pyx | 2 +- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/skimage/feature/match.py b/skimage/feature/match.py index d9f95c90..4093c6b6 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -5,8 +5,7 @@ from .match_cy import _binary_cross_check_loop def match_binary_descriptors(keypoints1, descriptors1, keypoints2, - descriptors2, threshold=0.40, cross_check=True, - return_mask=True): + descriptors2, threshold=0.40, cross_check=True): """Match keypoints described using binary descriptors in one image to those in second image. @@ -15,30 +14,28 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2, keypoints1 : (M, 2) ndarray M Keypoints from the first image described using skimage.feature.brief descriptors1 : (M, P) ndarray - BRIEF descriptors of size P about M keypoints in the first image. + Binary descriptors of size P about M keypoints in the first image. keypoints2 : (N, 2) ndarray N Keypoints from the second image described using skimage.feature.brief descriptors2 : (N, P) ndarray - BRIEF descriptors of size P about N keypoints in the second image. + Binary descriptors of size P about N keypoints in the second image. threshold : float in range [0, 1] Maximum allowable hamming distance between descriptors of two keypoints in separate images to be regarded as a match. cross_check : bool - Cross check if True. - return_mask : bool - Return index masks mask1 and mask2 for matched keypoints from - keypoints1 and keypoints2 respectively. + If True, the matched keypoints are returned after cross checking i.e. a + matched pair (keypoint1, keypoint2) is returned iff keypoint2 is the best + match for keypoint1 in second image and keypoint1 is the best match for + keypoint2 in first image. Returns ------- - match_keypoints_pairs : (Q, 2, 2) ndarray + matches : (Q, 2, 2) ndarray Location of Q matched keypoint pairs from two images. mask1 : (Q,) ndarray - Indices of keypoints in keypoints1 that have been matched. Returned - only when return_mask is True. + Indices of keypoints in keypoints1 that have been matched. mask2 : (Q,) ndarray - Indices of keypoints in keypoints2 that have been matched. Returned - only when return_mask is True. + Indices of keypoints in keypoints2 that have been matched. """ if (keypoints1.shape[0] != descriptors1.shape[0] @@ -61,28 +58,22 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2, matched_keypoints2_index, distance, threshold) - matched_keypoint_pairs = np.zeros((matched_index.shape[0], 2, 2), + matches = np.zeros((matched_index.shape[0], 2, 2), dtype=np.intp) mask1 = matched_index[:, 0] mask2 = matched_index[:, 1] - matched_keypoint_pairs[:, 0, :] = keypoints1[mask1] - matched_keypoint_pairs[:, 1, :] = keypoints2[mask2] - if return_mask: - return (matched_keypoint_pairs, mask1, mask2) - else: - return matched_keypoint_pairs + matches[:, 0, :] = keypoints1[mask1] + matches[:, 1, :] = keypoints2[mask2] else: temp = distance > threshold row_check = np.any(~temp, axis=1) matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] - matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), + matches = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp) - matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] - matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] + matches[:, 0, :] = keypoints1[row_check] + matches[:, 1, :] = matched_keypoints2[row_check] mask1 = np.where(row_check == True) mask2 = np.argmin(distance, axis=1)[row_check] - if return_mask: - return (matched_keypoint_pairs, mask1, mask2) - else: - return matched_keypoint_pairs + + return matches, mask1, mask2 diff --git a/skimage/feature/match_cy.pyx b/skimage/feature/match_cy.pyx index 8a1bb461..6574d573 100644 --- a/skimage/feature/match_cy.pyx +++ b/skimage/feature/match_cy.pyx @@ -5,15 +5,12 @@ def _binary_cross_check_loop(Py_ssize_t[:] matched_keypoints1_index, Py_ssize_t[:] matched_keypoints2_index, double[:, ::1] distance, double threshold): cdef Py_ssize_t i - #matched_index = [] - cdef Py_ssize_t count = 0 cdef Py_ssize_t[:, ::1] matched_index = np.zeros((len(matched_keypoints1_index), 2), dtype=np.intp) for i in range(len(matched_keypoints1_index)): if (matched_keypoints2_index[matched_keypoints1_index[i]] == i and distance[i, matched_keypoints1_index[i]] < threshold): - #matched_index.append([i, matched_keypoints1_index[i]]) matched_index[count, 0] = i matched_index[count, 1] = matched_keypoints1_index[i] count += 1 diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index e312f95b..e7197797 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,7 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round -pos = np.loadtxt("skimage/feature/orb_descriptor_positions.txt", dtype=np.int8) +pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) pos0 = np.ascontiguousarray(pos[:, :2]) pos1 = np.ascontiguousarray(pos[:, 2:]) From a10fc660f8e574aee359500c4b0d5b37ed35f52c Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 26 Sep 2013 08:46:20 +0530 Subject: [PATCH 075/145] Changing values of default parameters and OFAST_MASK --- skimage/feature/orb.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 577ce9cc..7498f7e3 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -9,17 +9,15 @@ from skimage.transform import pyramid_gaussian from .orb_cy import _orb_loop -OFAST_MASK = np.array([[0, 0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 0, 0]], dtype=np.uint8) +OFAST_MASK = np.zeros((31, 31)) +umax = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3] +for i in range(-15, 16): + for j in range(-umax[np.abs(i)], umax[np.abs(i)] + 1): + OFAST_MASK[15 + j, 15 + i] = 1 -def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, - harris_k=0.05, downscale=np.sqrt(2), n_scales=4): +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. @@ -134,7 +132,7 @@ def keypoints_orb(image, n_keypoints=200, fast_n=9, fast_threshold=0.20, def descriptor_orb(image, keypoints, orientations, scales, - downscale=np.sqrt(2), n_scales=4): + downscale=1.2, n_scales=8): """Compute rBRIEF descriptors of input keypoints. Parameters @@ -206,7 +204,7 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_scale_orientation = orientations[curr_scale_mask] border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, - dist=14) + dist=16) curr_scale_kpts = curr_scale_kpts[border_mask] curr_scale_orientation = curr_scale_orientation[border_mask] From a93fe9dbb984a24dfb212b3994ef615b82d3e36f Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 26 Sep 2013 17:57:12 +0530 Subject: [PATCH 076/145] Correcting the orientation bug --- skimage/feature/orb_cy.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index e7197797..21126fbd 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -6,7 +6,7 @@ cimport numpy as cnp import numpy as np -from libc.math cimport sin, cos, M_PI, round +from libc.math cimport sin, cos, round pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) @@ -28,8 +28,8 @@ def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints, for i in range(descriptors.shape[0]): angle = orientations[i] - sin_a = sin(angle * M_PI / 180.) - cos_a = cos(angle * M_PI / 180.) + sin_a = sin(angle) + cos_a = cos(angle) kr = keypoints[i, 0] kc = keypoints[i, 1] From 11403f9ce30eee3769147edfb53a446c93668e54 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 26 Sep 2013 17:58:37 +0530 Subject: [PATCH 077/145] Making matching graphics more clear --- doc/examples/plot_orb_matching.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb_matching.py index 7d1119ab..78409dd1 100644 --- a/doc/examples/plot_orb_matching.py +++ b/doc/examples/plot_orb_matching.py @@ -9,7 +9,7 @@ import matplotlib.pyplot as plt rotate = 0.5 translate = (-100, -200) scaling = (1.5, 1.5) -match_threshold = 0.40 +match_threshold = 0.25 match_cross_check = True img_color = data.lena() @@ -18,13 +18,13 @@ transformed_img_color = tf.warp(img_color, tform) img = rgb2gray(img_color) transformed_img = rgb2gray(transformed_img_color) -keypoints1, orientations1, scales1 = keypoints_orb(img, n_keypoints=250) +keypoints1, orientations1, scales1 = keypoints_orb(img, n_keypoints=500) keypoints1.shape descriptors1, keypoints1 = descriptor_orb(img, keypoints1, orientations1, scales1) keypoints1.shape descriptors1.shape -keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, n_keypoints=250) +keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, n_keypoints=500) keypoints2.shape descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2, orientations2, scales2) keypoints2.shape @@ -38,8 +38,8 @@ matched_keypoints.shape # Plotting the matched correspondences in both the images using matplotlib src = matched_keypoints[:, 0, :] dst = matched_keypoints[:, 1, :] -src_scale = 10 * (scales1[mask1] + 1) ** 2 -dst_scale = 10 * (scales2[mask2] + 1) ** 2 +src_scale = 10 * (scales1[mask1] + 1) ** 1.5 +dst_scale = 10 * (scales2[mask2] + 1) ** 1.5 img_combined = np.concatenate((img_as_float(img_color), img_as_float(transformed_img_color)), axis=1) offset = img.shape @@ -53,8 +53,10 @@ ax.axis((0, 2 * offset[1], offset[0], 0)) ax.set_title('Matched correspondences : Rotation = %f; Scale = %s; Translation = %s; threshold = %f; cross_check = %r' % (rotate, scaling, translate, match_threshold, match_cross_check)) for m in range(len(src)): - ax.plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), '-', color='g') - ax.scatter(src[m, 1], src[m, 0], src_scale[m], facecolors='none', edgecolors='b') - ax.scatter(dst[m, 1] + offset[1], dst[m, 0], dst_scale[m], facecolors='none', edgecolors='b') + c = np.random.rand(3,1) + ax.plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), '-', color=c) + ax.scatter(src[m, 1], src[m, 0], src_scale[m], facecolors='none', edgecolors=c) + ax.scatter(dst[m, 1] + offset[1], dst[m, 0], dst_scale[m], facecolors='none', edgecolors=c) plt.show() + From 73afae9d7253cf5f36e08c080f6d4fcaefcd6464 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 26 Sep 2013 19:33:28 +0530 Subject: [PATCH 078/145] Documenting the example code --- doc/examples/plot_orb_matching.py | 36 +++++++++++++++++++++++-------- skimage/feature/orb.py | 3 ++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb_matching.py index 78409dd1..660aadae 100644 --- a/doc/examples/plot_orb_matching.py +++ b/doc/examples/plot_orb_matching.py @@ -1,37 +1,54 @@ import numpy as np from skimage import data from skimage import transform as tf -from skimage.feature import pairwise_hamming_distance, brief, match_binary_descriptors, corner_harris, corner_peaks, keypoints_orb, descriptor_orb +from skimage.feature import (pairwise_hamming_distance, + match_binary_descriptors, corner_harris, + corner_peaks, keypoints_orb, descriptor_orb) from skimage.color import rgb2gray from skimage import img_as_float import matplotlib.pyplot as plt +# Initializing parameters for transformation rotate = 0.5 translate = (-100, -200) scaling = (1.5, 1.5) -match_threshold = 0.25 -match_cross_check = True +# Creating a transformed image from the original Lena image by scaling and +# rotating it img_color = data.lena() -tform = tf.AffineTransform(scale = scaling, rotation=rotate, translation=translate) +tform = tf.AffineTransform(scale = scaling, rotation=rotate, + translation=translate) transformed_img_color = tf.warp(img_color, tform) img = rgb2gray(img_color) transformed_img = rgb2gray(transformed_img_color) +# Extracting oFAST keypoints and computing their rBRIEF descriptors keypoints1, orientations1, scales1 = keypoints_orb(img, n_keypoints=500) keypoints1.shape -descriptors1, keypoints1 = descriptor_orb(img, keypoints1, orientations1, scales1) +descriptors1, keypoints1 = descriptor_orb(img, keypoints1, orientations1, + scales1) keypoints1.shape descriptors1.shape -keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, n_keypoints=500) +keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, + n_keypoints=500) keypoints2.shape -descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2, orientations2, scales2) +descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2, + orientations2, scales2) keypoints2.shape descriptors2.shape +#Initializing parameters for Descriptor matching +match_threshold = 0.25 +match_cross_check = True + pairwise_hamming_distance(descriptors1, descriptors2) -matched_keypoints, mask1, mask2 = match_binary_descriptors(keypoints1, descriptors1, keypoints2, descriptors2, cross_check=match_cross_check, threshold=match_threshold) +matched_keypoints, mask1, mask2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2, + cross_check=match_cross_check, + threshold=match_threshold) matched_keypoints.shape @@ -41,7 +58,8 @@ dst = matched_keypoints[:, 1, :] src_scale = 10 * (scales1[mask1] + 1) ** 1.5 dst_scale = 10 * (scales2[mask2] + 1) ** 1.5 -img_combined = np.concatenate((img_as_float(img_color), img_as_float(transformed_img_color)), axis=1) +img_combined = np.concatenate((img_as_float(img_color), + img_as_float(transformed_img_color)), axis=1) offset = img.shape fig, ax = plt.subplots(nrows=1, ncols=1) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 7498f7e3..fb4a77ff 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -1,6 +1,7 @@ import numpy as np -from .util import _mask_border_keypoints, _prepare_grayscale_input_2D +from skimage.feature.util import (_mask_border_keypoints, + _prepare_grayscale_input_2D) from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) From 6e566e831f7774323a32f93bdaa77f638340c6e6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 28 Sep 2013 23:22:40 +0530 Subject: [PATCH 079/145] Minor doc change --- skimage/feature/orb.py | 3 ++- skimage/feature/util.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index fb4a77ff..c45e0789 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -48,7 +48,8 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, separate corners from edges, typically in range ``[0, 0.2]``. Small values of k result in detection of sharp corners. downscale : float - Downscale factor for the image pyramid. + Downscale factor for the image pyramid. Default value 1.2 is chosen so + that we have more scales per octave i.e. log1.2(2) in this case. n_scales : int Number of scales from the bottom of the image pyramid to extract the features from. diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 178eab66..9bbbaef5 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -1,6 +1,6 @@ import numpy as np -from ..util import img_as_float +from skimage.util import img_as_float def _prepare_grayscale_input_2D(image): From ed738b301718a869f7ac0ff8f450d0005dd92e9d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 1 Oct 2013 09:38:02 +0530 Subject: [PATCH 080/145] Minor doc changes --- skimage/feature/censure.py | 4 +++- skimage/feature/orb.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index cf3d543a..dbe8daf5 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -126,7 +126,9 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', max_scale : int Maximum scale to extract keypoints from. The keypoints will be extracted from all the scales except the first and the last i.e. - from the scales in the range [min_scale + 1, max_scale - 1]. + from the scales in the range [min_scale + 1, max_scale - 1]. The filter + sizes for different scales is such that the two adjacent scales + comprise of an octave. mode : {'DoB', 'Octagon', 'STAR'} Type of bi-level filter used to get the scales of the input image. Possible values are 'DoB', 'Octagon' and 'STAR'. The three modes diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index c45e0789..63586ff6 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -49,7 +49,7 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, values of k result in detection of sharp corners. downscale : float Downscale factor for the image pyramid. Default value 1.2 is chosen so - that we have more scales per octave i.e. log1.2(2) in this case. + that we have more dense scales that enable robust scale invariance. n_scales : int Number of scales from the bottom of the image pyramid to extract the features from. From e9762f2673d553ac2102ca58f55414848afb7a9f Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 1 Oct 2013 10:22:01 +0530 Subject: [PATCH 081/145] Incorporating mat_binary_descriptors changes in test_brief --- skimage/feature/tests/_test_brief.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/skimage/feature/tests/_test_brief.py b/skimage/feature/tests/_test_brief.py index 1d26cbbd..c7e3a134 100644 --- a/skimage/feature/tests/_test_brief.py +++ b/skimage/feature/tests/_test_brief.py @@ -3,7 +3,7 @@ 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 (brief, match_keypoints_brief, corner_peaks, +from skimage.feature import (brief, match_binary_descriptors, corner_peaks, corner_harris) @@ -14,7 +14,7 @@ def test_brief_color_image_unsupported_error(): assert_raises(ValueError, brief, img, keypoints) -def test_match_keypoints_brief_lena_translation(): +def test_matching_brief_descriptors_lena_translation(): """Test matched keypoints between lena image and its translated version.""" img = data.lena() img = rgb2gray(img) @@ -29,15 +29,17 @@ def test_match_keypoints_brief_lena_translation(): descriptors2, keypoints2 = brief(translated_img, keypoints2, descriptor_size=512) - matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, - keypoints2, descriptors2, - threshold=0.10) + matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2, + threshold=0.10) assert_array_equal(matched_keypoints[:, 0, :], matched_keypoints[:, 1, :] + [20, 15]) -def test_match_keypoints_brief_lena_rotation(): +def test_matching_brief_descriptors_lena_rotation(): """Verify matched keypoints result between lena image and its rotated version with the expected keypoint pairs.""" img = data.lena() @@ -53,9 +55,11 @@ def test_match_keypoints_brief_lena_rotation(): descriptors2, keypoints2 = brief(rotated_img, keypoints2, descriptor_size=512) - matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, - keypoints2, descriptors2, - threshold=0.07) + matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2, + threshold=0.07) expected = np.array([[[263, 272], [234, 298]], @@ -75,6 +79,7 @@ def test_match_keypoints_brief_lena_rotation(): [[454, 176], [435, 221]]]) + print matched_keypoints assert_array_equal(matched_keypoints, expected) From ffd144ef3680514f6ca36b9d845d3f9ef9dd4fdb Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 2 Oct 2013 01:49:51 +0530 Subject: [PATCH 082/145] tests for match_binary_descriptors; many corrections and changes --- skimage/feature/__init__.py | 4 +- skimage/feature/_brief.py | 23 ++-- skimage/feature/match.py | 2 +- skimage/feature/tests/_test_brief.py | 92 +++++--------- skimage/feature/tests/test_match.py | 173 +++++++++++++++++++++++++++ skimage/feature/util.py | 6 + 6 files changed, 228 insertions(+), 72 deletions(-) create mode 100644 skimage/feature/tests/test_match.py diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 89370fd7..0487f7b6 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -9,7 +9,7 @@ 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 brief +from ._brief import descriptor_brief from .match import match_binary_descriptors from .util import pairwise_hamming_distance from .censure import keypoints_censure @@ -29,7 +29,7 @@ __all__ = ['daisy', 'corner_peaks', 'corner_moravec', 'match_template', - 'brief', + 'descriptor_brief', 'pairwise_hamming_distance', 'match_binary_descriptors', 'keypoints_censure', diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index be7e4ac9..94c92f04 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -2,13 +2,13 @@ import numpy as np from scipy.ndimage.filters import gaussian_filter from .util import (_mask_border_keypoints, pairwise_hamming_distance, - _prepare_grayscale_input_2D) + _prepare_grayscale_input_2D) from ._brief_cy import _brief_loop -def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, - sample_seed=1, variance=2): +def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', + patch_size=49, sample_seed=1, variance=2): """**Experimental function**. Extract BRIEF Descriptor about given keypoints for a given image. @@ -57,8 +57,9 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, Examples -------- - >> from skimage.feature import corner_peaks, corner_harris, \\ - .. pairwise_hamming_distance, brief, match_keypoints_brief + >> import numpy as np + >> from skimage.feature.corner import corner_peaks, corner_harris + >> from skimage.feature import pairwise_hamming_distance, descriptor_brief, match_binary_descriptors >> square1 = np.zeros([8, 8], dtype=np.int32) >> square1[2:6, 2:6] = 1 >> square1 @@ -76,7 +77,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [2, 5], [5, 2], [5, 5]]) - >> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size=5) + >> descriptors1, keypoints1 = descriptor_brief(square1, keypoints1, patch_size=5) >> keypoints1 array([[2, 2], [2, 5], @@ -100,7 +101,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [2, 6], [6, 2], [6, 6]]) - >> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size=5) + >> descriptors2, keypoints2 = descriptor_brief(square2, keypoints2, patch_size=5) >> keypoints2 array([[2, 2], [2, 6], @@ -111,8 +112,11 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [ 0.3203125, 0.03125 , 0.640625 , 0.375 ], [ 0.375 , 0.6328125, 0.0390625, 0.328125 ], [ 0.625 , 0.3671875, 0.34375 , 0.0234375]]) - >> match_keypoints_brief(keypoints1, descriptors1, - .. keypoints2, descriptors2) + >> matched_kpts, mask1, mask2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2) + >> matched_kpts array([[[ 2, 2], [ 2, 2]], @@ -165,6 +169,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, 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) pos1 = np.ascontiguousarray(pos1) diff --git a/skimage/feature/match.py b/skimage/feature/match.py index 4093c6b6..d9ae886a 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -73,7 +73,7 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2, dtype=np.intp) matches[:, 0, :] = keypoints1[row_check] matches[:, 1, :] = matched_keypoints2[row_check] - mask1 = np.where(row_check == True) + mask1 = np.where(row_check == True)[0] mask2 = np.argmin(distance, axis=1)[row_check] return matches, mask1, mask2 diff --git a/skimage/feature/tests/_test_brief.py b/skimage/feature/tests/_test_brief.py index c7e3a134..7983b023 100644 --- a/skimage/feature/tests/_test_brief.py +++ b/skimage/feature/tests/_test_brief.py @@ -3,84 +3,56 @@ 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 (brief, match_binary_descriptors, corner_peaks, +from skimage.feature import (descriptor_brief, match_binary_descriptors, corner_peaks, corner_harris) -def test_brief_color_image_unsupported_error(): +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 = [[7, 5], [11, 13]] - assert_raises(ValueError, brief, img, keypoints) + assert_raises(ValueError, descriptor_brief, img, keypoints) -def test_matching_brief_descriptors_lena_translation(): - """Test matched keypoints between lena image and its translated version.""" +def test_descriptor_brief_normal_mode(): + """Verify the computed BRIEF descriptors with expected for normal mode.""" img = data.lena() img = rgb2gray(img) - img.shape - tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20)) - translated_img = tf.warp(img, tform) + keypoints = corner_peaks(corner_harris(img), min_distance=5) + descriptors, keypoints = descriptor_brief(img, keypoints[:8], + descriptor_size=8) - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + expected = np.array([[ True, False, True, False, True, True, False, False], + [False, False, False, False, True, False, False, False], + [ True, True, True, True, True, True, True, True], + [ True, False, True, True, False, True, False, True], + [False, True, True, True, True, True, True, True], + [ True, False, False, False, False, True, False, True], + [False, True, True, True, False, False, True, False], + [False, False, False, False, True, False, False, False]], dtype=bool) - keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5) - descriptors2, keypoints2 = brief(translated_img, keypoints2, - descriptor_size=512) - - matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, - descriptors1, - keypoints2, - descriptors2, - threshold=0.10) - - assert_array_equal(matched_keypoints[:, 0, :], matched_keypoints[:, 1, :] + - [20, 15]) + assert_array_equal(descriptors, expected) -def test_matching_brief_descriptors_lena_rotation(): - """Verify matched keypoints result between lena image and its rotated - version with the expected keypoint pairs.""" +def test_descriptor_brief_uniform_mode(): + """Verify the computed BRIEF descriptors with expected for uniform mode.""" img = data.lena() img = rgb2gray(img) - img.shape - tform = tf.SimilarityTransform(scale=1, rotation=0.10, translation=(0, 0)) - rotated_img = tf.warp(img, tform) + keypoints = corner_peaks(corner_harris(img), min_distance=5) + descriptors, keypoints = descriptor_brief(img, keypoints[:8], + descriptor_size=8, + mode='uniform') - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + expected = np.array([[ True, False, True, False, False, True, False, False], + [False, True, False, False, True, True, True, True], + [ True, False, False, False, False, False, False, False], + [False, True, True, False, False, False, True, False], + [False, False, False, False, False, False, True, False], + [False, True, False, False, True, False, False, False], + [False, False, True, True, False, False, True, True], + [ True, True, False, False, False, False, False, False]], dtype=bool) - keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - descriptors2, keypoints2 = brief(rotated_img, keypoints2, - descriptor_size=512) - - matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, - descriptors1, - keypoints2, - descriptors2, - threshold=0.07) - - expected = np.array([[[263, 272], - [234, 298]], - - [[271, 120], - [258, 146]], - - [[323, 164], - [305, 195]], - - [[414, 70], - [405, 111]], - - [[435, 181], - [415, 223]], - - [[454, 176], - [435, 221]]]) - - print matched_keypoints - assert_array_equal(matched_keypoints, expected) + assert_array_equal(descriptors, expected) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py new file mode 100644 index 00000000..1e1435e3 --- /dev/null +++ b/skimage/feature/tests/test_match.py @@ -0,0 +1,173 @@ +import numpy as np +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) + + +def test_match_binary_descriptors_unequal_descriptor_keypoints_error(): + """Number of descriptors should be equal to the number of keypoints.""" + kp1 = np.array([[40, 50], + [60, 40], + [30, 70]]) + des1 = np.array([[True, True, False, True], + [False, True, False, True]]) + kp2 = np.array([[60, 50], + [50, 80]]) + des2 = np.array([[True, False, False, True], + [False, True, True, True]]) + assert_raises(ValueError, match_binary_descriptors, kp1, des1, kp2, des2) + + +def test_match_binary_descriptors_unequal_descriptor_sizes_error(): + """Sizes of descriptors of keypoints to be matched should be equal.""" + kp1 = np.array([[40, 50], + [60, 40]]) + des1 = np.array([[True, True, False, True], + [False, True, False, True]]) + kp2 = np.array([[60, 50], + [50, 80]]) + des2 = np.array([[True, False, False, True, False], + [False, True, True, True, False]]) + assert_raises(ValueError, match_binary_descriptors, kp1, des1, kp2, des2) + + +def test_match_binary_descriptors_lena_rotation_crosscheck_false(): + """Verify matched keypoints and their corresponding masks results between + lena image and its rotated version with the expected keypoint pairs with + cross_check disabled.""" + img = data.lena() + img = rgb2gray(img) + tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) + rotated_img = tf.warp(img, tform) + + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = descriptor_brief(img, keypoints1, descriptor_size=512) + + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, + descriptor_size=512) + + matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2, + threshold=0.13, + cross_check=False) + + expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 39, 40, 42, 45]) + expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 10, 11, 12, 13]) + expected = np.array([[[245, 141], + [221, 176]], + + [[247, 130], + [225, 165]], + + [[263, 272], + [219, 309]], + + [[271, 120], + [250, 159]], + + [[311, 174], + [282, 218]], + + [[323, 164], + [294, 210]], + + [[327, 147], + [301, 195]], + + [[377, 157], + [349, 211]], + + [[414, 70], + [399, 131]], + + [[425, 67], + [399, 131]], + + [[435, 181], + [403, 244]], + + [[454, 176], + [423, 242]], + + [[467, 166], + [437, 234]]]) + + assert_array_equal(matched_keypoints, expected) + assert_array_equal(m1, expected_mask1) + assert_array_equal(m2, expected_mask2) + + +def test_match_binary_descriptors_lena_rotation_crosscheck_true(): + """Verify matched keypoints and their corresponding masks results between + lena image and its rotated version with the expected keypoint pairs with + cross_check enabled.""" + img = data.lena() + img = rgb2gray(img) + tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) + rotated_img = tf.warp(img, tform) + + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = descriptor_brief(img, keypoints1, descriptor_size=512) + + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, + descriptor_size=512) + + matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, + descriptors1, + keypoints2, + descriptors2, + threshold=0.13) + + expected = np.array([[[245, 141], + [221, 176]], + + [[247, 130], + [225, 165]], + + [[263, 272], + [219, 309]], + + [[271, 120], + [250, 159]], + + [[311, 174], + [282, 218]], + + [[323, 164], + [294, 210]], + + [[327, 147], + [301, 195]], + + [[377, 157], + [349, 211]], + + [[414, 70], + [399, 131]], + + [[435, 181], + [403, 244]], + + [[454, 176], + [423, 242]], + + [[467, 166], + [437, 234]]]) + + expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 40, 42, 45]) + expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 11, 12, 13]) + assert_array_equal(matched_keypoints, expected) + assert_array_equal(m1, expected_mask1) + assert_array_equal(m2, expected_mask2) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 9bbbaef5..29b36475 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -47,3 +47,9 @@ def pairwise_hamming_distance(array1, array2): """ distance = (array1[:, None] != array2[None]).mean(axis=2) return distance + + +def _create_keypoint_recarray(row, col, octave=None, orientation=None, + response=None): + kpt_recarray = np.recarray() + From 58515ad14e6222dca82125cab3fa96c59a90e172 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 2 Oct 2013 01:58:09 +0530 Subject: [PATCH 083/145] Cleaning util.py --- skimage/feature/util.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 29b36475..9bbbaef5 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -47,9 +47,3 @@ def pairwise_hamming_distance(array1, array2): """ distance = (array1[:, None] != array2[None]).mean(axis=2) return distance - - -def _create_keypoint_recarray(row, col, octave=None, orientation=None, - response=None): - kpt_recarray = np.recarray() - From 142ad4e7741ab960a06e3ed57ceae57de13194a7 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 2 Oct 2013 20:15:22 +0530 Subject: [PATCH 084/145] Adding tests for orb; fixing some bugs --- skimage/feature/orb.py | 30 +++++----- skimage/feature/orb_cy.pyx | 3 +- skimage/feature/tests/test_orb.py | 93 +++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 skimage/feature/tests/test_orb.py diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 63586ff6..8f659394 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -18,7 +18,7 @@ for i in range(-15, 16): def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, - harris_k=0.04, downscale=1.2, n_scales=8): + harris_k=0.04, downscale=1.2, n_scales=8): """Detect Oriented Fast keypoints. @@ -201,22 +201,24 @@ def descriptor_orb(image, keypoints, orientations, scales, curr_image = np.ascontiguousarray(pyramid[scale]) curr_scale_mask = scales == scale - curr_scale_kpts = keypoints[curr_scale_mask] / (downscale ** scale) - curr_scale_kpts = np.round(curr_scale_kpts).astype(np.intp) - curr_scale_orientation = orientations[curr_scale_mask] + if np.sum(curr_scale_mask) > 0: + curr_scale_kpts = keypoints[curr_scale_mask] / (downscale ** scale) + curr_scale_kpts = np.round(curr_scale_kpts).astype(np.intp) + curr_scale_orientation = orientations[curr_scale_mask] - border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, - dist=16) - curr_scale_kpts = curr_scale_kpts[border_mask] - curr_scale_orientation = curr_scale_orientation[border_mask] + border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, + dist=16) - curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) - curr_scale_orientation = np.ascontiguousarray(curr_scale_orientation) - curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, - curr_scale_orientation) + curr_scale_kpts = curr_scale_kpts[border_mask] + curr_scale_orientation = curr_scale_orientation[border_mask] - descriptors_list.append(curr_scale_descriptors) - filtered_keypoints_list.append(curr_scale_kpts * downscale ** scale) + curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) + curr_scale_orientation = np.ascontiguousarray(curr_scale_orientation) + curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, + curr_scale_orientation) + + descriptors_list.append(curr_scale_descriptors) + filtered_keypoints_list.append(curr_scale_kpts * downscale ** scale) descriptors = np.vstack(descriptors_list).view(np.bool) filtered_keypoints = np.vstack(filtered_keypoints_list) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 21126fbd..b1af3af2 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,8 @@ import numpy as np from libc.math cimport sin, cos, round -pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) +pos = np.loadtxt(os.path.join(os.path.dirname(__file__), + "orb_descriptor_positions.txt"), dtype=np.int8) pos0 = np.ascontiguousarray(pos[:, :2]) pos1 = np.ascontiguousarray(pos[:, 2:]) diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py new file mode 100644 index 00000000..a577cf22 --- /dev/null +++ b/skimage/feature/tests/test_orb.py @@ -0,0 +1,93 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_almost_equal +from skimage.feature import keypoints_orb, descriptor_orb +from skimage.data import lena +from skimage.color import rgb2gray + + +def test_keypoints_orb_desired_no_of_keypoints(): + img = rgb2gray(lena()) + keypoints, orientations, scales = keypoints_orb(img, n_keypoints=10, + fast_n=12, + fast_threshold=0.20) + exp_keypoints = np.array([[435, 180], + [436, 180], + [376, 156], + [455, 176], + [435, 180], + [269, 111], + [376, 156], + [311, 173], + [413, 70], + [311, 173]]) + exp_scales = np.array([0, 1, 0, 0, 2, 0, 1, 1, 0, 3]) + exp_orientations = np.array([-175.64733392, -167.94842949, -148.98350192, + -142.03599837, -176.08535837, -53.08162354, + -150.89208271, 97.7693776 , -173.4479964 , + 38.66312042]) + assert_array_equal(exp_keypoints, keypoints) + assert_array_equal(exp_scales, scales) + assert_almost_equal(exp_orientations, np.rad2deg(orientations)) + + +def test_keypoints_orb_less_than_desired_no_of_keypoints(): + img = rgb2gray(lena()) + keypoints, orientations, scales = keypoints_orb(img, n_keypoints=15, + fast_n=12, + fast_threshold=0.33, + downscale=2, n_scales=2) + exp_keypoints = np.array([[ 67, 157], + [247, 146], + [269, 111], + [413, 70], + [435, 180], + [230, 136], + [264, 336], + [330, 148], + [372, 156]]) + exp_scales = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1]) + exp_orientations = np.array([-105.76503839, -96.28973044, -53.08162354, + -173.4479964 , -175.64733392, -106.07927215, + -163.40016243, 75.80865813, -154.73195911]) + assert_array_equal(exp_keypoints, keypoints) + assert_array_equal(exp_scales, scales) + assert_almost_equal(exp_orientations, np.rad2deg(orientations)) + + +def test_descriptor_orb(): + img = rgb2gray(lena()) + keypoints, orientations, scales = keypoints_orb(img, n_keypoints=10, + fast_n=12, + fast_threshold=0.20) + descriptors, filtered_keypoints = descriptor_orb(img, keypoints, orientations, scales) + + exp_filtered_keypoints = np.array([[435, 180], + [376, 156], + [455, 176], + [269, 111], + [413, 70], + [436, 180], + [376, 156], + [311, 173], + [435, 180], + [311, 173]]) + + 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, True, False, True, True, True, False, True, False, True]], + dtype=bool) + + assert_array_equal(exp_filtered_keypoints, filtered_keypoints) + assert_array_equal(descriptors_120_129, descriptors[:, 120:130]) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() From 3a0cebed9f69ea819d77426211e53d2c127c6762 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 12 Oct 2013 20:35:28 +0530 Subject: [PATCH 085/145] Fix missing import --- skimage/feature/orb_cy.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index b1af3af2..82c6e6ad 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -5,6 +5,7 @@ cimport numpy as cnp import numpy as np +import os from libc.math cimport sin, cos, round From 94d90667008426872cb721490c3a79363a5183af Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 12 Oct 2013 21:15:26 +0530 Subject: [PATCH 086/145] Absolute path fix --- skimage/feature/orb_cy.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 82c6e6ad..939471c9 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -10,7 +10,7 @@ import os from libc.math cimport sin, cos, round -pos = np.loadtxt(os.path.join(os.path.dirname(__file__), +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:]) From 5611da485f8a131201339b6983f9ab62b513c54d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 29 Oct 2013 02:49:29 +0530 Subject: [PATCH 087/145] Introducing recarray for storing keypoint variables --- skimage/feature/orb.py | 56 +++++++++++++++++++---------------------- skimage/feature/util.py | 14 +++++++++++ 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 8f659394..9b394dd9 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -1,7 +1,8 @@ import numpy as np from skimage.feature.util import (_mask_border_keypoints, - _prepare_grayscale_input_2D) + _prepare_grayscale_input_2D, + _create_keypoint_recarray) from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) @@ -120,33 +121,30 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, harris_response_list.append(harris_response[corners[:, 0], corners[:, 1]]) - keypoints = np.round(np.vstack(keypoints_list)).astype(np.intp) + keypoints = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) - scales = np.hstack(scales_list) + octaves = downscale ** np.hstack(scales_list) harris_measure = np.hstack(harris_response_list) + kpts_recarray = _create_keypoint_recarray(keypoints[:, 0], keypoints[:, 1], + octaves, orientations, + harris_measure) - if keypoints.shape[0] < n_keypoints: - return keypoints, orientations, scales + if kpts_recarray.shape[0] < n_keypoints: + return kpts_recarray else: best_indices = harris_measure.argsort()[::-1][:n_keypoints] - return (keypoints[best_indices], orientations[best_indices], - scales[best_indices]) + return kpts_recarray[best_indices] -def descriptor_orb(image, keypoints, orientations, scales, - downscale=1.2, n_scales=8): +def descriptor_orb(image, kpts_recarray, downscale=1.2, n_scales=8): """Compute rBRIEF descriptors of input keypoints. Parameters ---------- image : 2D ndarray Input grayscale image. - keypoints : (N, 2) ndarray + kpts_recarray : (N, 2) ndarray Array of N input keypoint locations in the format (row, col). - orientations : (N,) ndarray - The orientations of the corresponding N keypoints. - scales : (N,) ndarray - The scales of the corresponding N keypoints. downscale : float Downscale factor for the image pyramid. Should be the same as that used in ``keypoints_orb``. @@ -194,33 +192,31 @@ def descriptor_orb(image, keypoints, orientations, scales, pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) descriptors_list = [] - filtered_keypoints_list = [] - descriptors = np.empty((0, 256), dtype=np.bool) + kpts_recarray_list = [] for scale in range(n_scales): curr_image = np.ascontiguousarray(pyramid[scale]) - curr_scale_mask = scales == scale + curr_scale_mask = (np.log(kpts_recarray.octave) / + np.log(downscale)).astype(np.intp) == scale if np.sum(curr_scale_mask) > 0: - curr_scale_kpts = keypoints[curr_scale_mask] / (downscale ** scale) - curr_scale_kpts = np.round(curr_scale_kpts).astype(np.intp) - curr_scale_orientation = orientations[curr_scale_mask] - - border_mask = _mask_border_keypoints(curr_image, curr_scale_kpts, + curr_kpts_recarray = kpts_recarray[curr_scale_mask] + curr_scale_kpts = np.squeeze(np.dstack((curr_kpts_recarray.row / curr_kpts_recarray.octave, + curr_kpts_recarray.col / curr_kpts_recarray.octave))) + border_mask = _mask_border_keypoints(curr_image, + curr_scale_kpts, dist=16) - curr_scale_kpts = curr_scale_kpts[border_mask] - curr_scale_orientation = curr_scale_orientation[border_mask] + curr_kpts_recarray = curr_kpts_recarray[border_mask] - curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts) - curr_scale_orientation = np.ascontiguousarray(curr_scale_orientation) + curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts[border_mask].astype(np.intp)) + curr_scale_orientation = np.ascontiguousarray(curr_kpts_recarray.orientation) curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, curr_scale_orientation) descriptors_list.append(curr_scale_descriptors) - filtered_keypoints_list.append(curr_scale_kpts * downscale ** scale) + kpts_recarray_list.append(curr_kpts_recarray) descriptors = np.vstack(descriptors_list).view(np.bool) - filtered_keypoints = np.vstack(filtered_keypoints_list) - filtered_keypoints = np.round(filtered_keypoints).astype(np.intp) - return descriptors, filtered_keypoints + filtered_kpts_recarray = np.hstack(kpts_recarray_list) + return descriptors, filtered_kpts_recarray diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 9bbbaef5..18325cc8 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -3,6 +3,20 @@ import numpy as np from skimage.util import img_as_float +def _create_keypoint_recarray(row, col, octave=None, orientation=None, + response=None): + keypoints = np.zeros(row.shape[0], + dtype=[('row', np.double), ('col', np.double), + ('octave', np.double), ('orientation', np.double), + ('response', np.double)]) + keypoints['row'] = row + keypoints['col'] = col + keypoints['octave'] = octave + keypoints['orientation'] = orientation + keypoints['response'] = response + return keypoints.view(np.recarray) + + def _prepare_grayscale_input_2D(image): image = np.squeeze(image) if image.ndim != 2: From 38bdd3e52377fc38a2e879c030a984397dba954a Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 1 Nov 2013 01:14:55 +0530 Subject: [PATCH 088/145] Made recarray changes in docstrings and tests --- skimage/feature/orb.py | 96 ++++++++++++++----------------- skimage/feature/tests/test_orb.py | 91 ++++++++++++++--------------- 2 files changed, 85 insertions(+), 102 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 9b394dd9..55058a8e 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -57,12 +57,8 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, Returns ------- - keypoints : (N, 2) ndarray - The oFAST keypoints. - orientations : (N,) ndarray - The orientations of the N extracted keypoints. - scales : (N,) ndarray - The scales of the N extracted keypoints. + keypoints : record array + Record array with fields row, col, octave, orientation, response. References ---------- @@ -75,25 +71,20 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, >>> from skimage.feature import keypoints_orb, descriptor_orb >>> square = np.zeros((50, 50)) >>> square[20:30, 20:30] = 1 - >>> keypoints, orientations, scales = keypoints_orb(square, n_keypoints=8, n_scales=2) + >>> keypoints = keypoints_orb(square, n_keypoints=8, n_scales=2) >>> keypoints.shape - (8, 2) - >>> keypoints - array([[29, 29], - [29, 20], - [20, 29], - [20, 20], - [15, 15], - [15, 20], - [20, 15], - [20, 20]]) - >>> orientations - array([-2.35619449, -0.78539816, 2.35619449, 0.78539816, 0.78539816, - 2.35619449, -0.78539816, -2.35619449]) - >>> np.rad2deg(orientations) - array([-135., -45., 135., 45., 45., 135., -45., -135.]) - >>> scales - array([0, 0, 0, 0, 1, 1, 1, 1]) + (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]) """ @@ -121,30 +112,31 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, harris_response_list.append(harris_response[corners[:, 0], corners[:, 1]]) - keypoints = np.vstack(keypoints_list) + keypoints_array = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) octaves = downscale ** np.hstack(scales_list) harris_measure = np.hstack(harris_response_list) - kpts_recarray = _create_keypoint_recarray(keypoints[:, 0], keypoints[:, 1], - octaves, orientations, - harris_measure) + keypoints = _create_keypoint_recarray(keypoints_array[:, 0], + keypoints_array[:, 1], + octaves, orientations, + harris_measure) - if kpts_recarray.shape[0] < n_keypoints: - return kpts_recarray + if keypoints.shape[0] < n_keypoints: + return keypoints else: best_indices = harris_measure.argsort()[::-1][:n_keypoints] - return kpts_recarray[best_indices] + return keypoints[best_indices] -def descriptor_orb(image, kpts_recarray, downscale=1.2, n_scales=8): +def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8): """Compute rBRIEF descriptors of input keypoints. Parameters ---------- image : 2D ndarray Input grayscale image. - kpts_recarray : (N, 2) ndarray - Array of N input keypoint locations in the format (row, col). + keypoints : record array + Record array with fields row, col, octave, orientation, response. downscale : float Downscale factor for the image pyramid. Should be the same as that used in ``keypoints_orb``. @@ -159,8 +151,9 @@ def descriptor_orb(image, kpts_recarray, downscale=1.2, n_scales=8): filtering out those near the image border. Size of each descriptor is 32 bytes or 256 bits. filtered_keypoints : (P, 2) ndarray - Location i.e. (row, col) of P keypoints after removing out those that - are near border. + Record array with fields row, col, octave, orientation, response for + P keypoints obtained after removing out those that are near the + border. References ---------- @@ -174,15 +167,12 @@ def descriptor_orb(image, kpts_recarray, downscale=1.2, n_scales=8): >>> from skimage.feature import keypoints_orb, descriptor_orb >>> square = np.zeros((50, 50)) >>> square[20:30, 20:30] = 1 - >>> keypoints, orientations, scales = keypoints_orb(square, n_keypoints=8, - ... n_scales=2) + >>> keypoints = keypoints_orb(square, n_keypoints=8, n_scales=2) >>> keypoints.shape - (8, 2) - >>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, - ... orientations, scales, - ... n_scales=2) + (8,) + >>> descriptors, filtered_keypoints = descriptor_orb(square, keypoints, n_scales=2) >>> filtered_keypoints.shape - (8, 2) + (8,) >>> descriptors.shape (8, 256) @@ -192,31 +182,31 @@ def descriptor_orb(image, kpts_recarray, downscale=1.2, n_scales=8): pyramid = list(pyramid_gaussian(image, n_scales - 1, downscale)) descriptors_list = [] - kpts_recarray_list = [] + keypoints_list = [] for scale in range(n_scales): curr_image = np.ascontiguousarray(pyramid[scale]) - curr_scale_mask = (np.log(kpts_recarray.octave) / + curr_scale_mask = (np.log(keypoints.octave) / np.log(downscale)).astype(np.intp) == scale if np.sum(curr_scale_mask) > 0: - curr_kpts_recarray = kpts_recarray[curr_scale_mask] - curr_scale_kpts = np.squeeze(np.dstack((curr_kpts_recarray.row / curr_kpts_recarray.octave, - curr_kpts_recarray.col / curr_kpts_recarray.octave))) + 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) - curr_kpts_recarray = curr_kpts_recarray[border_mask] + curr_keypoints = curr_keypoints[border_mask] curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts[border_mask].astype(np.intp)) - curr_scale_orientation = np.ascontiguousarray(curr_kpts_recarray.orientation) + curr_scale_orientation = np.ascontiguousarray(curr_keypoints.orientation) curr_scale_descriptors = _orb_loop(curr_image, curr_scale_kpts, curr_scale_orientation) descriptors_list.append(curr_scale_descriptors) - kpts_recarray_list.append(curr_kpts_recarray) + keypoints_list.append(curr_keypoints) descriptors = np.vstack(descriptors_list).view(np.bool) - filtered_kpts_recarray = np.hstack(kpts_recarray_list) - return descriptors, filtered_kpts_recarray + filtered_keypoints = np.hstack(keypoints_list) + return descriptors, filtered_keypoints.view(np.recarray) diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index a577cf22..1333b6b1 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -7,73 +7,66 @@ from skimage.color import rgb2gray def test_keypoints_orb_desired_no_of_keypoints(): img = rgb2gray(lena()) - keypoints, orientations, scales = keypoints_orb(img, n_keypoints=10, - fast_n=12, - fast_threshold=0.20) - exp_keypoints = np.array([[435, 180], - [436, 180], - [376, 156], - [455, 176], - [435, 180], - [269, 111], - [376, 156], - [311, 173], - [413, 70], - [311, 173]]) - exp_scales = np.array([0, 1, 0, 0, 2, 0, 1, 1, 0, 3]) + 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]) + + exp_octaves = 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, -150.89208271, 97.7693776 , -173.4479964 , 38.66312042]) - assert_array_equal(exp_keypoints, keypoints) - assert_array_equal(exp_scales, scales) - assert_almost_equal(exp_orientations, np.rad2deg(orientations)) + exp_response = np.array([ 0.96770745, 0.81027306, 0.72376257, + 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)) def test_keypoints_orb_less_than_desired_no_of_keypoints(): img = rgb2gray(lena()) - keypoints, orientations, scales = keypoints_orb(img, n_keypoints=15, + keypoints = keypoints_orb(img, n_keypoints=15, fast_n=12, fast_threshold=0.33, downscale=2, n_scales=2) - exp_keypoints = np.array([[ 67, 157], - [247, 146], - [269, 111], - [413, 70], - [435, 180], - [230, 136], - [264, 336], - [330, 148], - [372, 156]]) - exp_scales = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1]) + 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_octaves = 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, -163.40016243, 75.80865813, -154.73195911]) - assert_array_equal(exp_keypoints, keypoints) - assert_array_equal(exp_scales, scales) - assert_almost_equal(exp_orientations, np.rad2deg(orientations)) + exp_response = np.array([ 0.13197835, 0.24931321, 0.44351774, + 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)) def test_descriptor_orb(): img = rgb2gray(lena()) - keypoints, orientations, scales = keypoints_orb(img, n_keypoints=10, - fast_n=12, - fast_threshold=0.20) - descriptors, filtered_keypoints = descriptor_orb(img, keypoints, orientations, scales) - - exp_filtered_keypoints = np.array([[435, 180], - [376, 156], - [455, 176], - [269, 111], - [413, 70], - [436, 180], - [376, 156], - [311, 173], - [435, 180], - [311, 173]]) + keypoints = keypoints_orb(img, n_keypoints=10, fast_n=12, + fast_threshold=0.20) + descriptors, filtered_keypoints = descriptor_orb(img, keypoints) 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], + [ 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], @@ -81,10 +74,10 @@ def test_descriptor_orb(): [ 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, True, False, True, True, True, False, True, False, True]], + [ True, False, False, False, False, False, True, True, True, False]], dtype=bool) - assert_array_equal(exp_filtered_keypoints, filtered_keypoints) + assert_array_equal(descriptors_120_129, descriptors[:, 120:130]) From 1a2efa7e377dea8adc3773670aaec50db22b63e3 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 3 Nov 2013 22:41:58 +0530 Subject: [PATCH 089/145] Extending recarray changes to BRIEF --- skimage/feature/__init__.py | 3 ++- skimage/feature/_brief.py | 20 ++++++++++++-------- skimage/feature/orb.py | 10 +++++----- skimage/feature/tests/_test_brief.py | 18 +++++++++++++----- skimage/feature/util.py | 2 +- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 0487f7b6..50217f53 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -11,7 +11,7 @@ from .corner_cy import corner_moravec, corner_orientations from .template import match_template from ._brief import descriptor_brief from .match import match_binary_descriptors -from .util import pairwise_hamming_distance +from .util import pairwise_hamming_distance, create_keypoint_recarray from .censure import keypoints_censure from .orb import keypoints_orb, descriptor_orb @@ -31,6 +31,7 @@ __all__ = ['daisy', 'match_template', 'descriptor_brief', 'pairwise_hamming_distance', + 'create_keypoint_recarray', 'match_binary_descriptors', 'keypoints_censure', 'corner_fast', diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 94c92f04..b7ecfb5e 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -17,8 +17,9 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', ---------- image : 2D ndarray Input image. - keypoints : (P, 2) ndarray - Array of keypoint locations in the format (row, col). + keypoints : record array with P rows + Record array with fields row, col, octave, orientation, response. + Octave, orientation and response can be None. descriptor_size : int Size of BRIEF descriptor about each keypoint. Sizes 128, 256 and 512 preferred by the authors. Default is 256. @@ -141,15 +142,18 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', image = np.ascontiguousarray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') + 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 - keypoints = keypoints[_mask_border_keypoints(image, keypoints, patch_size // 2)] - keypoints = np.ascontiguousarray(keypoints) + 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.shape[0], descriptor_size), dtype=bool, - order='C') + 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': @@ -175,6 +179,6 @@ 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, pos1, pos2) + _brief_loop(image, descriptors.view(np.uint8), keypoints_loc, pos1, pos2) return descriptors, keypoints diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 55058a8e..fcfe4d7d 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -2,7 +2,7 @@ import numpy as np from skimage.feature.util import (_mask_border_keypoints, _prepare_grayscale_input_2D, - _create_keypoint_recarray) + create_keypoint_recarray) from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) @@ -116,10 +116,10 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, orientations = np.hstack(orientations_list) octaves = downscale ** np.hstack(scales_list) harris_measure = np.hstack(harris_response_list) - keypoints = _create_keypoint_recarray(keypoints_array[:, 0], - keypoints_array[:, 1], - octaves, orientations, - harris_measure) + keypoints = create_keypoint_recarray(keypoints_array[:, 0], + keypoints_array[:, 1], + octaves, orientations, + harris_measure) if keypoints.shape[0] < n_keypoints: return keypoints diff --git a/skimage/feature/tests/_test_brief.py b/skimage/feature/tests/_test_brief.py index 7983b023..40a0ac38 100644 --- a/skimage/feature/tests/_test_brief.py +++ b/skimage/feature/tests/_test_brief.py @@ -3,14 +3,17 @@ 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) +from skimage.feature import (descriptor_brief, match_binary_descriptors, + corner_peaks, corner_harris, + create_keypoint_recarray) 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 = [[7, 5], [11, 13]] + 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) @@ -18,7 +21,10 @@ def test_descriptor_brief_normal_mode(): """Verify the computed BRIEF descriptors with expected for normal mode.""" img = data.lena() img = rgb2gray(img) - keypoints = corner_peaks(corner_harris(img), min_distance=5) + 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) @@ -38,7 +44,9 @@ def test_descriptor_brief_uniform_mode(): """Verify the computed BRIEF descriptors with expected for uniform mode.""" img = data.lena() img = rgb2gray(img) - keypoints = corner_peaks(corner_harris(img), min_distance=5) + 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') diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 18325cc8..a0942d79 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -3,7 +3,7 @@ import numpy as np from skimage.util import img_as_float -def _create_keypoint_recarray(row, col, octave=None, orientation=None, +def create_keypoint_recarray(row, col, octave=None, orientation=None, response=None): keypoints = np.zeros(row.shape[0], dtype=[('row', np.double), ('col', np.double), From 0d79b3963e0ddeb09be7e11754d8459c7121bd68 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 12 Nov 2013 03:08:09 +0530 Subject: [PATCH 090/145] Incorporating recarray changes to match.py --- skimage/feature/match.py | 20 ++++++++++-------- skimage/feature/tests/test_match.py | 32 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/skimage/feature/match.py b/skimage/feature/match.py index d9ae886a..405fb86b 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -11,12 +11,14 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2, Parameters ---------- - keypoints1 : (M, 2) ndarray - M Keypoints from the first image described using skimage.feature.brief + keypoints1 : record array with M rows + Record array with fields row, col, octave, orientation, response. + Octave, orientation and response can be None. descriptors1 : (M, P) ndarray Binary descriptors of size P about M keypoints in the first image. - keypoints2 : (N, 2) ndarray - N Keypoints from the second image described using skimage.feature.brief + keypoints2 : record array with N rows + Record array with fields row, col, octave, orientation, response. + Octave, orientation and response can be None. descriptors2 : (N, P) ndarray Binary descriptors of size P about N keypoints in the second image. threshold : float in range [0, 1] @@ -49,6 +51,8 @@ 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) @@ -62,16 +66,16 @@ def match_binary_descriptors(keypoints1, descriptors1, keypoints2, dtype=np.intp) mask1 = matched_index[:, 0] mask2 = matched_index[:, 1] - matches[:, 0, :] = keypoints1[mask1] - matches[:, 1, :] = keypoints2[mask2] + matches[:, 0, :] = kp1[mask1] + matches[:, 1, :] = kp2[mask2] else: temp = distance > threshold row_check = np.any(~temp, axis=1) - matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] + matched_keypoints2 = kp2[np.argmin(distance, axis=1)] matches = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp) - matches[:, 0, :] = keypoints1[row_check] + matches[:, 0, :] = kp1[row_check] matches[:, 1, :] = matched_keypoints2[row_check] mask1 = np.where(row_check == True)[0] mask2 = np.argmin(distance, axis=1)[row_check] diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index 1e1435e3..9bb2c958 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -4,7 +4,8 @@ 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) + corner_peaks, corner_harris, + create_keypoint_recarray) def test_match_binary_descriptors_unequal_descriptor_keypoints_error(): @@ -12,26 +13,30 @@ def test_match_binary_descriptors_unequal_descriptor_keypoints_error(): kp1 = np.array([[40, 50], [60, 40], [30, 70]]) + keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) des1 = np.array([[True, True, False, True], [False, True, False, True]]) kp2 = np.array([[60, 50], [50, 80]]) + keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) des2 = np.array([[True, False, False, True], [False, True, True, True]]) - assert_raises(ValueError, match_binary_descriptors, kp1, des1, kp2, des2) + assert_raises(ValueError, match_binary_descriptors, keypoints1, des1, keypoints2, des2) def test_match_binary_descriptors_unequal_descriptor_sizes_error(): """Sizes of descriptors of keypoints to be matched should be equal.""" kp1 = np.array([[40, 50], [60, 40]]) + keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) des1 = np.array([[True, True, False, True], [False, True, False, True]]) kp2 = np.array([[60, 50], [50, 80]]) + keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) des2 = np.array([[True, False, False, True, False], [False, True, True, True, False]]) - assert_raises(ValueError, match_binary_descriptors, kp1, des1, kp2, des2) + assert_raises(ValueError, match_binary_descriptors, keypoints1, des1, keypoints2, des2) def test_match_binary_descriptors_lena_rotation_crosscheck_false(): @@ -43,10 +48,13 @@ def test_match_binary_descriptors_lena_rotation_crosscheck_false(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, keypoints1 = descriptor_brief(img, keypoints1, descriptor_size=512) + kp1 = corner_peaks(corner_harris(img), min_distance=5) + keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) + descriptors1, keypoints1 = descriptor_brief(img, keypoints1, + descriptor_size=512) - keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + kp2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, descriptor_size=512) @@ -57,8 +65,10 @@ def test_match_binary_descriptors_lena_rotation_crosscheck_false(): threshold=0.13, cross_check=False) - expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 39, 40, 42, 45]) - expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 10, 11, 12, 13]) + expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 39, 40, + 42, 45]) + expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 10, 11, + 12, 13]) expected = np.array([[[245, 141], [221, 176]], @@ -112,10 +122,12 @@ def test_match_binary_descriptors_lena_rotation_crosscheck_true(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + kp1 = corner_peaks(corner_harris(img), min_distance=5) + keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) descriptors1, keypoints1 = descriptor_brief(img, keypoints1, descriptor_size=512) - keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + kp2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, descriptor_size=512) From 172c5ae1ec63e15016c8ce1cdf9be35cff4a7fd4 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 14 Nov 2013 22:48:22 +0530 Subject: [PATCH 091/145] Made recarray changes in the brief docstring --- skimage/feature/_brief.py | 61 +++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index b7ecfb5e..286a5357 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -46,9 +46,9 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', 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 : (Q, 2) ndarray - Location i.e. (row, col) of keypoints after removing out those that - are near border. + keypoints : record array with Q rows + Record array with fields row, col, octave, orientation, response. + Octave, orientation and response can be None. References ---------- @@ -58,9 +58,10 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', Examples -------- - >> import numpy as np >> from skimage.feature.corner import corner_peaks, corner_harris - >> from skimage.feature import pairwise_hamming_distance, descriptor_brief, match_binary_descriptors + >> 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 @@ -73,17 +74,13 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', [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 - array([[2, 2], - [2, 5], - [5, 2], - [5, 5]]) + >> keypoints1 = create_keypoint_recarray(keypoints1[:, 0], keypoints1[:, 1]) >> descriptors1, keypoints1 = descriptor_brief(square1, keypoints1, patch_size=5) >> keypoints1 - array([[2, 2], - [2, 5], - [5, 2], - [5, 5]]) + 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', '> square2 = np.zeros([9, 9], dtype=np.int32) >> square2[2:7, 2:7] = 1 >> square2 @@ -97,38 +94,34 @@ def descriptor_brief(image, keypoints, descriptor_size=256, mode='normal', [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 - array([[2, 2], - [2, 6], - [6, 2], - [6, 6]]) + 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', '> descriptors2, keypoints2 = descriptor_brief(square2, keypoints2, patch_size=5) - >> keypoints2 - array([[2, 2], - [2, 6], - [6, 2], - [6, 6]]) >> 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) + ... descriptors1, + ... keypoints2, + ... descriptors2) >> matched_kpts - array([[[ 2, 2], - [ 2, 2]], + array([[[2, 2], + [2, 2]], - [[ 2, 5], - [ 2, 6]], + [[2, 5], + [2, 6]], - [[ 5, 2], - [ 6, 2]], + [[5, 2], + [6, 2]], - [[ 5, 5], - [ 6, 6]]]) + [[5, 5], + [6, 6]]]) """ From a9106ab9d379212e32d6d416003fa9ff3d230043 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 15 Nov 2013 12:42:15 +0530 Subject: [PATCH 092/145] Recarray changes made in example --- doc/examples/plot_orb_matching.py | 18 ++++++++---------- skimage/feature/orb.py | 2 +- skimage/feature/tests/test_orb.py | 10 +++++----- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb_matching.py index 660aadae..0818b76c 100644 --- a/doc/examples/plot_orb_matching.py +++ b/doc/examples/plot_orb_matching.py @@ -23,23 +23,21 @@ img = rgb2gray(img_color) transformed_img = rgb2gray(transformed_img_color) # Extracting oFAST keypoints and computing their rBRIEF descriptors -keypoints1, orientations1, scales1 = keypoints_orb(img, n_keypoints=500) +keypoints1 = keypoints_orb(img, n_keypoints=500) keypoints1.shape -descriptors1, keypoints1 = descriptor_orb(img, keypoints1, orientations1, - scales1) +descriptors1, keypoints1 = descriptor_orb(img, keypoints1) keypoints1.shape descriptors1.shape -keypoints2, orientations2, scales2 = keypoints_orb(transformed_img, +keypoints2 = keypoints_orb(transformed_img, n_keypoints=500) keypoints2.shape -descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2, - orientations2, scales2) +descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2) keypoints2.shape descriptors2.shape #Initializing parameters for Descriptor matching -match_threshold = 0.25 +match_threshold = 0.3 match_cross_check = True pairwise_hamming_distance(descriptors1, descriptors2) @@ -55,8 +53,8 @@ matched_keypoints.shape # Plotting the matched correspondences in both the images using matplotlib src = matched_keypoints[:, 0, :] dst = matched_keypoints[:, 1, :] -src_scale = 10 * (scales1[mask1] + 1) ** 1.5 -dst_scale = 10 * (scales2[mask2] + 1) ** 1.5 +src_scale = 10 * (keypoints1.octave[mask1] + 1) ** 1.5 +dst_scale = 10 * (keypoints2.octave[mask2] + 1) ** 1.5 img_combined = np.concatenate((img_as_float(img_color), img_as_float(transformed_img_color)), axis=1) @@ -71,7 +69,7 @@ ax.axis((0, 2 * offset[1], offset[0], 0)) ax.set_title('Matched correspondences : Rotation = %f; Scale = %s; Translation = %s; threshold = %f; cross_check = %r' % (rotate, scaling, translate, match_threshold, match_cross_check)) for m in range(len(src)): - c = np.random.rand(3,1) + c = np.random.rand(3,1) ax.plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), '-', color=c) ax.scatter(src[m, 1], src[m, 0], src_scale[m], facecolors='none', edgecolors=c) ax.scatter(dst[m, 1] + offset[1], dst[m, 0], dst_scale[m], facecolors='none', edgecolors=c) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index fcfe4d7d..1c712521 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -199,7 +199,7 @@ def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8): curr_keypoints = curr_keypoints[border_mask] - curr_scale_kpts = np.ascontiguousarray(curr_scale_kpts[border_mask].astype(np.intp)) + 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) diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index 1333b6b1..956ff4b4 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -34,10 +34,9 @@ def test_keypoints_orb_desired_no_of_keypoints(): 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) + keypoints = keypoints_orb(img, n_keypoints=15, fast_n=12, + fast_threshold=0.33, downscale=2, n_scales=2) + exp_row = np.array([ 67., 247., 269., 413., 435., 230., 264., 330., 372.]) exp_col = np.array([ 157., 146., 111., 70., 180., 136., 336., @@ -48,6 +47,7 @@ def test_keypoints_orb_less_than_desired_no_of_keypoints(): exp_orientations = np.array([-105.76503839, -96.28973044, -53.08162354, -173.4479964 , -175.64733392, -106.07927215, -163.40016243, 75.80865813, -154.73195911]) + exp_response = np.array([ 0.13197835, 0.24931321, 0.44351774, 0.39063076, 0.96770745, 0.04935129, 0.21431068, 0.15826555, 0.42403573]) @@ -66,7 +66,7 @@ def test_descriptor_orb(): descriptors, filtered_keypoints = descriptor_orb(img, keypoints) 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], + [ 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], From 87c566c6bce75cbb31eba25f76a6686500e016fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 20:22:47 +0100 Subject: [PATCH 093/145] Finish create_keypoint_recarray helper function --- skimage/feature/util.py | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index a0942d79..26b988ef 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -3,17 +3,42 @@ import numpy as np from skimage.util import img_as_float -def create_keypoint_recarray(row, col, octave=None, orientation=None, - response=None): - keypoints = np.zeros(row.shape[0], - dtype=[('row', np.double), ('col', np.double), - ('octave', np.double), ('orientation', np.double), - ('response', np.double)]) - keypoints['row'] = row - keypoints['col'] = col - keypoints['octave'] = octave - keypoints['orientation'] = orientation - keypoints['response'] = response +def create_keypoint_recarray(rows, cols, scales=None, orientations=None, + responses=None): + """Create keypoint array that allows field access through attributes. + + 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. + + Returns + ------- + recarray : (N, 5) recarray + Array with the fields: `row`, `col`, `scale`, `orientation` and + `response`. + + """ + + dtype = [('row', np.double), + ('col', np.double), + ('scale', np.double), + ('orientation', np.double), + ('response', np.double)] + keypoints = np.zeros(row.shape[0], dtype=dtype) + keypoints['row'] = rows + keypoints['col'] = cols + keypoints['scale'] = scales + keypoints['orientation'] = orientations + keypoints['response'] = responses return keypoints.view(np.recarray) From ea1ae457f179f4599a58426f223dfdb3990b1ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 21:46:04 +0100 Subject: [PATCH 094/145] Improve BRIEF and various tweaks --- .../orb_descriptor_positions.txt | 0 skimage/feature/_brief.py | 96 ++++++++++--------- skimage/feature/_brief_cy.pyx | 10 +- skimage/feature/orb.py | 3 +- skimage/feature/orb_cy.pyx | 19 ++-- skimage/feature/util.py | 42 +++++--- 6 files changed, 98 insertions(+), 72 deletions(-) rename skimage/{feature => data}/orb_descriptor_positions.txt (100%) 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): From d4bc4cf859e9b5208f8d48b7f508cae26205b750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 21:48:13 +0100 Subject: [PATCH 095/145] Improve FAST example output --- skimage/feature/corner.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 90aac771..33f9b39a 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -554,19 +554,19 @@ def corner_fast(image, n=12, threshold=0.15): >>> from skimage.feature import corner_fast, corner_peaks >>> square = np.zeros((12, 12)) >>> square[3:9, 3:9] = 1 - >>> square - array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + >>> square.astype(int) + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) >>> corner_peaks(corner_fast(square, 9), min_distance=1) array([[3, 3], [3, 8], From cf68bfd67105be758bd29bf27acf0adfd172c705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 21:50:02 +0100 Subject: [PATCH 096/145] Add comments to FAST implementation --- skimage/feature/corner_cy.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 2b010693..688063c8 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -143,7 +143,7 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): # Similar pixel bins[k] = 's' - # High speed test for n>=12 + # High speed test for n >= 12 if n >= 12: speed_sum_b = 0 speed_sum_d = 0 @@ -155,10 +155,12 @@ def _corner_fast(double[:, ::1] image, char n, double threshold): if speed_sum_d < 3 and speed_sum_b < 3: continue + # Test for bright pixels curr_response = \ _corner_fast_response(curr_pixel, circle_intensities, bins, 'b', n) + # Test for dark pixels if curr_response == 0: curr_response = \ _corner_fast_response(curr_pixel, circle_intensities, From 2f11f2277bb1ed9afcf30be8b4eb55745d507637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 21:55:21 +0100 Subject: [PATCH 097/145] Improve corner_orientations example output --- skimage/feature/corner_cy.pyx | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 688063c8..7249fde6 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -176,7 +176,7 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): The orientation of corners is computed using the first order central moment i.e. the center of mass approach. The corner orientation is the angle of - the vector from the corner coordinate to the intensity centroid in the + the vector from the corner coordinate to the intensity centroid in the local neighborhood around the corner calculated using first order central moment. @@ -206,23 +206,23 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): Examples -------- >>> from skimage.morphology import octagon - >>> from skimage.feature import corner_fast, corner_peaks, \ - ... corner_orientations + >>> from skimage.feature import (corner_fast, corner_peaks, + ... corner_orientations) >>> square = np.zeros((12, 12)) >>> square[3:9, 3:9] = 1 - >>> square - array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 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., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0.], - [ 0., 0., 0., 1., 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., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + >>> square.astype(int) + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1) >>> corners array([[3, 3], From 845448a152171d38660f618b93d086c3ab34c9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 22:59:13 +0100 Subject: [PATCH 098/145] Implement object oriented interface for BRIEF --- skimage/feature/__init__.py | 7 +- skimage/feature/_brief.py | 202 +++++++++++++-------------- skimage/feature/_brief_cy.pyx | 8 +- skimage/feature/match.py | 26 ++-- skimage/feature/tests/_test_brief.py | 39 +++--- skimage/feature/util.py | 85 ++++++----- 6 files changed, 173 insertions(+), 194 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 50217f53..c7a5a42f 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -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', diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 6f90c39e..048cd7bb 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -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', '> 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', '> 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 diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/_brief_cy.pyx index 5c027c57..8cd1afa7 100644 --- a/skimage/feature/_brief_cy.pyx +++ b/skimage/feature/_brief_cy.pyx @@ -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 diff --git a/skimage/feature/match.py b/skimage/feature/match.py index 405fb86b..86d32eb8 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -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 diff --git a/skimage/feature/tests/_test_brief.py b/skimage/feature/tests/_test_brief.py index 40a0ac38..61042119 100644 --- a/skimage/feature/tests/_test_brief.py +++ b/skimage/feature/tests/_test_brief.py @@ -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], diff --git a/skimage/feature/util.py b/skimage/feature/util.py index b2ddcb7e..1da4b613 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -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 From e839ce2086dc6b66513c734fdb80e0fe760e9d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 22:59:48 +0100 Subject: [PATCH 099/145] Reactive BRIEF test cases --- skimage/feature/tests/{_test_brief.py => test_brief.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename skimage/feature/tests/{_test_brief.py => test_brief.py} (100%) diff --git a/skimage/feature/tests/_test_brief.py b/skimage/feature/tests/test_brief.py similarity index 100% rename from skimage/feature/tests/_test_brief.py rename to skimage/feature/tests/test_brief.py From 69dccda7cc87854e60c311edae4d7ba8d15b2da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 23:03:57 +0100 Subject: [PATCH 100/145] Add test case for border keypoints --- skimage/feature/tests/test_brief.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 61042119..0f0a9b1f 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -7,14 +7,14 @@ from skimage.feature import (BRIEF, match_binary_descriptors, corner_peaks, corner_harris) -def test_descriptor_brief_color_image_unsupported_error(): +def test_color_image_unsupported_error(): """Brief descriptors can be evaluated on gray-scale images only.""" img = np.zeros((20, 20, 3)) keypoints = np.asarray([[7, 5], [11, 13]]) assert_raises(ValueError, BRIEF().extract, img, keypoints) -def test_descriptor_brief_normal_mode(): +def test_normal_mode(): """Verify the computed BRIEF descriptors with expected for normal mode.""" img = rgb2gray(data.lena()) @@ -36,7 +36,7 @@ def test_descriptor_brief_normal_mode(): assert_array_equal(descriptors, expected) -def test_descriptor_brief_uniform_mode(): +def test_uniform_mode(): """Verify the computed BRIEF descriptors with expected for uniform mode.""" img = rgb2gray(data.lena()) @@ -58,6 +58,17 @@ def test_descriptor_brief_uniform_mode(): assert_array_equal(descriptors, expected) +def test_border(): + img = np.zeros((100, 100)) + keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]]) + + extractor = BRIEF(patch_size=41) + descs, mask = extractor.extract(img, keypoints) + + assert descs.shape[0] == 3 + assert_array_equal(mask, (False, True, True, True)) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 29026e7231640d8048cd2972153d96bf525e2621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 23:08:16 +0100 Subject: [PATCH 101/145] Improve example output of corner_moravec --- skimage/feature/corner_cy.pyx | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 7249fde6..c337fafd 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -38,25 +38,25 @@ def corner_moravec(image, Py_ssize_t window_size=1): Examples -------- - >>> from skimage.feature import corner_moravec, peak_local_max + >>> from skimage.feature import corner_moravec >>> square = np.zeros([7, 7]) >>> square[3, 3] = 1 - >>> square - array([[ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 1., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.]]) - >>> corner_moravec(square) - array([[ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 1., 1., 1., 0., 0.], - [ 0., 0., 1., 2., 1., 0., 0.], - [ 0., 0., 1., 1., 1., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0.]]) + >>> square.astype(int) + array([[0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0]]) + >>> corner_moravec(square).astype(int) + array([[0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 0, 0], + [0, 0, 1, 2, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0]]) """ cdef Py_ssize_t rows = image.shape[0] From 79616815ff7e2e2a6f82117c95157afc44dc0362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 23:32:50 +0100 Subject: [PATCH 102/145] Implement CenSurE detector in object oriented interface --- skimage/feature/__init__.py | 6 +- skimage/feature/{_brief.py => brief.py} | 1 + .../feature/{_brief_cy.pyx => brief_cy.pyx} | 0 skimage/feature/censure.py | 171 ++++++++++-------- skimage/feature/orb.py | 71 +++++--- skimage/feature/setup.py | 4 +- skimage/feature/tests/_test_censure.py | 89 --------- skimage/feature/tests/test_censure.py | 89 +++++++++ 8 files changed, 231 insertions(+), 200 deletions(-) rename skimage/feature/{_brief.py => brief.py} (99%) rename skimage/feature/{_brief_cy.pyx => brief_cy.pyx} (100%) delete mode 100644 skimage/feature/tests/_test_censure.py create mode 100644 skimage/feature/tests/test_censure.py diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c7a5a42f..6c9c0df5 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -9,10 +9,10 @@ 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 BRIEF +from .brief import BRIEF +from .censure import CenSurE from .match import match_binary_descriptors from .util import pairwise_hamming_distance -from .censure import keypoints_censure from .orb import keypoints_orb, descriptor_orb __all__ = ['daisy', @@ -30,9 +30,9 @@ __all__ = ['daisy', 'corner_moravec', 'match_template', 'BRIEF', + 'CenSurE', 'pairwise_hamming_distance', 'match_binary_descriptors', - 'keypoints_censure', 'corner_fast', 'corner_orientations', 'structure_tensor', diff --git a/skimage/feature/_brief.py b/skimage/feature/brief.py similarity index 99% rename from skimage/feature/_brief.py rename to skimage/feature/brief.py index 048cd7bb..e29dd230 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/brief.py @@ -101,6 +101,7 @@ class BRIEF(DescriptorExtractor): def __init__(self, descriptor_size=256, patch_size=49, mode='normal', sigma=1, sample_seed=1): + mode = mode.lower() if mode not in ('normal', 'uniform'): raise ValueError("`mode` must be 'normal' or 'uniform'.") diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/brief_cy.pyx similarity index 100% rename from skimage/feature/_brief_cy.pyx rename to skimage/feature/brief_cy.pyx diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index dbe8daf5..dc9cd58c 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -1,7 +1,7 @@ import numpy as np from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve -from .util import _prepare_grayscale_input_2D +from skimage.feature.util import FeatureDetector, _prepare_grayscale_input_2D from skimage.transform import integral_image from skimage.feature import structure_tensor @@ -66,19 +66,19 @@ def _filter_image(image, min_scale, max_scale, mode): mo, no = OCTAGON_OUTER_SHAPE[min_scale + i - 1] mi, ni = OCTAGON_INNER_SHAPE[min_scale + i - 1] response[:, :, i] = convolve(image, - _octagon_filter_kernel(mo, no, mi, ni)) + _octagon_kernel(mo, no, mi, ni)) elif mode == 'star': for i in range(max_scale - min_scale + 1): m = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][0]] n = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][1]] - response[:, :, i] = convolve(image, _star_filter_kernel(m, n)) + response[:, :, i] = convolve(image, _star_kernel(m, n)) return response -def _octagon_filter_kernel(mo, no, mi, ni): +def _octagon_kernel(mo, no, mi, ni): outer = (mo + 2 * no)**2 - 2 * no * (no + 1) inner = (mi + 2 * ni)**2 - 2 * ni * (ni + 1) outer_weight = 1.0 / (outer - inner) @@ -92,7 +92,7 @@ def _octagon_filter_kernel(mo, no, mi, ni): return bfilter -def _star_filter_kernel(m, n): +def _star_kernel(m, n): c = m + m // 2 - n - n // 2 outer_star = star(m) inner_star = np.zeros_like(outer_star) @@ -106,21 +106,15 @@ def _star_filter_kernel(m, n): def _suppress_lines(feature_mask, image, sigma, line_threshold): Axx, Axy, Ayy = structure_tensor(image, sigma) - feature_mask[(Axx + Ayy) * (Axx + Ayy) - > line_threshold * (Axx * Ayy - Axy * Axy)] = False + feature_mask[(Axx + Ayy) ** 2 + > line_threshold * (Axx * Ayy - Axy ** 2)] = False -def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', - non_max_threshold=0.15, line_threshold=10): - """**Experimental function**. - Extracts CenSurE keypoints along with the corresponding scale using - either Difference of Boxes, Octagon or STAR bi-level filter. +class CenSurE(FeatureDetector): + + """CenSurE keypoint detector. - Parameters - ---------- - image : 2D ndarray - Input image. min_scale : int Minimum scale to extract keypoints from. max_scale : int @@ -145,13 +139,6 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', Threshold for rejecting interest points which have ratio of principal curvatures greater than this value. - Returns - ------- - keypoints : (N, 2) array - Location of the extracted keypoints in the ``(row, col)`` format. - scales : (N, 1) array - The corresponding scale of the N extracted keypoints. - References ---------- .. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas @@ -166,69 +153,101 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', """ - # (1) First we generate the required scales on the input grayscale image - # using a bi-level filter and stack them up in `filter_response`. - # (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on the - # filter_response to suppress points that are neither minima or maxima in - # 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray `feature_mask` - # containing all the minimas and maximas in `filter_response` as True. - # (3) Then we suppress all the points in the `feature_mask` for which the - # corresponding point in the image at a particular scale has the ratio of - # principal curvatures greater than `line_threshold`. - # (4) Finally, we remove the border keypoints and return the keypoints - # along with its corresponding scale. + def __init__(self, min_scale=1, max_scale=7, mode='DoB', + non_max_threshold=0.15, line_threshold=10): - image = _prepare_grayscale_input_2D(image) + mode = mode.lower() + if mode not in ('dob', 'octagon', 'star'): + raise ValueError("`mode` must be one of 'DoB', 'Octagon', 'STAR'.") - mode = mode.lower() - if mode not in ('dob', 'octagon', 'star'): - raise ValueError('Mode must be one of "DoB", "Octagon", "STAR".') + if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2: + raise ValueError('The scales must be >= 1 and the number of ' + 'scales should be >= 3.') - if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2: - raise ValueError('The scales must be >= 1 and the number of scales ' - 'should be >= 3.') + self.min_scale = min_scale + self.max_scale = max_scale + self.mode = mode + self.non_max_threshold = non_max_threshold + self.line_threshold = line_threshold - image = np.ascontiguousarray(image) + def detect(self, image): + """Detect CenSurE keypoints along with the corresponding scale. - # Generating all the scales - filter_response = _filter_image(image, min_scale, max_scale, mode) + Parameters + ---------- + image : 2D ndarray + Input image. - # Suppressing points that are neither minima or maxima in their 3 x 3 x 3 - # neighbourhood to zero - minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response - maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response + Returns + ------- + keypoints : (N, 2) array + Keypoint coordinates as ``(row, col)``. + scales : (N, 1) array + Corresponding scales of the N extracted keypoints. - feature_mask = minimas | maximas - feature_mask[filter_response < non_max_threshold] = False + """ - for i in range(1, max_scale - min_scale): - # sigma = (window_size - 1) / 6.0, so the window covers > 99% of the - # kernel's distribution - # window_size = 7 + 2 * (min_scale - 1 + i) - # Hence sigma = 1 + (min_scale - 1 + i)/ 3.0 - _suppress_lines(feature_mask[:, :, i], image, - (1 + (min_scale + i - 1) / 3.0), line_threshold) + # (1) First we generate the required scales on the input grayscale + # image using a bi-level filter and stack them up in `filter_response`. - rows, cols, scales = np.nonzero(feature_mask[..., 1:max_scale - min_scale]) - keypoints = np.column_stack([rows, cols]) - scales = scales + min_scale + 1 + # (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on + # the filter_response to suppress points that are neither minima or + # maxima in 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray + # `feature_mask` containing all the minimas and maximas in + # `filter_response` as True. + # (3) Then we suppress all the points in the `feature_mask` for which + # the corresponding point in the image at a particular scale has the + # ratio of principal curvatures greater than `line_threshold`. + # (4) Finally, we remove the border keypoints and return the keypoints + # along with its corresponding scale. - if mode == 'dob': - return keypoints, scales + num_scales = self.max_scale - self.min_scale - cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) + image = np.ascontiguousarray(_prepare_grayscale_input_2D(image)) - if mode == 'octagon': - for i in range(min_scale + 1, max_scale): - c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \ - + OCTAGON_OUTER_SHAPE[i - 1][1] - cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \ - & (scales == i) - elif mode == 'star': - for i in range(min_scale + 1, max_scale): - c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \ - + STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2 - cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \ - & (scales == i) + # Generating all the scales + filter_response = _filter_image(image, self.min_scale, self.max_scale, + self.mode) - return keypoints[cumulative_mask], scales[cumulative_mask] + # Suppressing points that are neither minima or maxima in their + # 3 x 3 x 3 neighborhood to zero + minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response + maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response + + feature_mask = minimas | maximas + feature_mask[filter_response < self.non_max_threshold] = False + + for i in range(1, num_scales): + # sigma = (window_size - 1) / 6.0, so the window covers > 99% of + # the kernel's distribution + # window_size = 7 + 2 * (min_scale - 1 + i) + # Hence sigma = 1 + (min_scale - 1 + i)/ 3.0 + _suppress_lines(feature_mask[:, :, i], image, + (1 + (self.min_scale + i - 1) / 3.0), + self.line_threshold) + + rows, cols, scales = np.nonzero(feature_mask[..., 1:num_scales]) + keypoints = np.column_stack([rows, cols]) + scales = scales + self.min_scale + 1 + + if self.mode == 'dob': + return keypoints, scales + + cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) + + if self.mode == 'octagon': + for i in range(self.min_scale + 1, self.max_scale): + c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \ + + OCTAGON_OUTER_SHAPE[i - 1][1] + cumulative_mask |= ( + _mask_border_keypoints(image.shape, keypoints, c) + & (scales == i)) + elif self.mode == 'star': + for i in range(self.min_scale + 1, self.max_scale): + c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \ + + STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2 + cumulative_mask |= ( + _mask_border_keypoints(image.shape, keypoints, c) + & (scales == i)) + + return keypoints[cumulative_mask], scales[cumulative_mask] diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index fb52e40e..74414465 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -1,8 +1,7 @@ import numpy as np from skimage.feature.util import (_mask_border_keypoints, - _prepare_grayscale_input_2D, - create_keypoint_recarray) + _prepare_grayscale_input_2D) from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) @@ -12,36 +11,39 @@ from .orb_cy import _orb_loop OFAST_MASK = np.zeros((31, 31)) -umax = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3] +OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3] for i in range(-15, 16): - for j in range(-umax[np.abs(i)], umax[np.abs(i)] + 1): + for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1): OFAST_MASK[15 + j, 15 + i] = 1 + + + 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. + """Detect oriented FAST keypoints. Parameters ---------- image : 2D ndarray - Input grayscale image. + Input image. n_keypoints : int - Number of keypoints to be returned from this function. The function - will return best ``n_keypoints`` if more than n_keypoints are detected - based on the values of other parameters. If not, then all the detected - keypoints are returned. + Number of keypoints to be returned. The function will return the best + ``n_keypoints`` according to the Harris corner response if more than + ``n_keypoints`` are detected. If not, then all the detected keypoints + are returned. fast_n : int The ``n`` parameter in ``feature.corner_fast``. Minimum number of consecutive pixels out of 16 pixels on the circle that should all be - either brighter or darker w.r.t testpixel. A point c on the circle is + either brighter or darker w.r.t test-pixel. A point c on the circle is darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner detector. fast_threshold : float - The ``threshold`` parameter in ``feature.corner_fast``. Threshold used to - decide whether the pixels on the circle are brighter, darker or + The ``threshold`` parameter in ``feature.corner_fast``. Threshold used + to decide whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. harris_k : float @@ -50,15 +52,17 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, values of k result in detection of sharp corners. downscale : float Downscale factor for the image pyramid. Default value 1.2 is chosen so - that we have more dense scales that enable robust scale invariance. + that there are more dense scales which enable robust scale invariance + for a subsequent feature description. n_scales : int - Number of scales from the bottom of the image pyramid to extract - the features from. + Maximum number of scales from the bottom of the image pyramid to + extract the features from. Returns ------- - keypoints : record array - Record array with fields row, col, octave, orientation, response. + keypoints : (N, ...) recarray + Record array as returned by `skimage.feature.create_keypoint_recarray` + with the fields: `row`, `col`, `scale`, `orientation` and `response`. References ---------- @@ -97,46 +101,53 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, scales_list = [] harris_response_list = [] - for scale in range(n_scales): + for octave in range(len(pyramid)): - corners = corner_peaks(corner_fast(pyramid[scale], fast_n, + # Extract keypoints for current octave + corners = corner_peaks(corner_fast(pyramid[octave], fast_n, fast_threshold), min_distance=1) - keypoints_list.append(corners * downscale ** scale) - orientations_list.append(corner_orientations(pyramid[scale], corners, + # Scale keypoint coordinates so they correspond to the original + # image shape + keypoints_list.append(corners * downscale ** octave) + + orientations_list.append(corner_orientations(pyramid[octave], corners, OFAST_MASK)) - scales_list.append(scale * np.ones(corners.shape[0], dtype=np.intp)) + scales_list.append(octave * np.ones(corners.shape[0], dtype=np.intp)) - harris_response = corner_harris(pyramid[scale], method='k', k=harris_k) + harris_response = corner_harris(pyramid[octave], method='k', + k=harris_k) harris_response_list.append(harris_response[corners[:, 0], corners[:, 1]]) keypoints_array = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) - octaves = downscale ** np.hstack(scales_list) + scales = downscale ** np.hstack(scales_list) harris_measure = np.hstack(harris_response_list) keypoints = create_keypoint_recarray(keypoints_array[:, 0], keypoints_array[:, 1], - octaves, orientations, + scales, orientations, harris_measure) 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] def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8): - """Compute rBRIEF descriptors of input keypoints. + """Compute rBRIEF descriptors for keypoints. Parameters ---------- image : 2D ndarray - Input grayscale image. - keypoints : record array - Record array with fields row, col, octave, orientation, response. + 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``. diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 9bd72ca3..9dc35643 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -15,7 +15,7 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) cython(['orb_cy.pyx'], working_path=base_path) - cython(['_brief_cy.pyx'], working_path=base_path) + cython(['brief_cy.pyx'], working_path=base_path) cython(['match_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) @@ -26,7 +26,7 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('orb_cy', sources=['orb_cy.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('_brief_cy', sources=['_brief_cy.c'], + config.add_extension('brief_cy', sources=['brief_cy.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('match_cy', sources=['match_cy.c'], include_dirs=[get_numpy_include_dirs()]) diff --git a/skimage/feature/tests/_test_censure.py b/skimage/feature/tests/_test_censure.py deleted file mode 100644 index 4cd2ad68..00000000 --- a/skimage/feature/tests/_test_censure.py +++ /dev/null @@ -1,89 +0,0 @@ -import numpy as np -from numpy.testing import assert_array_equal, assert_raises -from skimage.data import moon -from skimage.feature import keypoints_censure - - -def test_keypoints_censure_color_image_unsupported_error(): - """Censure keypoints can be extracted from gray-scale images only.""" - img = np.zeros((20, 20, 3)) - assert_raises(ValueError, keypoints_censure, img) - - -def test_keypoints_censure_mode_validity_error(): - """Mode argument in keypoints_censure can be either DoB, Octagon or - STAR.""" - img = np.zeros((20, 20)) - assert_raises(ValueError, keypoints_censure, img, mode='dummy') - - -def test_keypoints_censure_scale_range_error(): - """Difference between the the max_scale and min_scale parameters in - keypoints_censure should be greater than or equal to two.""" - img = np.zeros((20, 20)) - assert_raises(ValueError, keypoints_censure, img, min_scale=1, max_scale=2) - - -def test_keypoints_censure_moon_image_dob(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for DoB filter.""" - img = moon() - actual_kp_dob, actual_scale = keypoints_censure(img, 1, 7, 'DoB', 0.15) - expected_kp_dob = np.array([[ 21, 497], - [ 36, 46], - [119, 350], - [185, 177], - [287, 250], - [357, 239], - [463, 116], - [464, 132], - [467, 260]]) - expected_scale = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) - - assert_array_equal(expected_kp_dob, actual_kp_dob) - assert_array_equal(expected_scale, actual_scale) - - -def test_keypoints_censure_moon_image_octagon(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for Octagon filter.""" - img = moon() - actual_kp_octagon, actual_scale = keypoints_censure(img, 1, 7, 'Octagon', - 0.15) - expected_kp_octagon = np.array([[ 21, 496], - [ 35, 46], - [287, 250], - [356, 239], - [463, 116]]) - - expected_scale = np.array([3, 4, 2, 2, 2]) - - assert_array_equal(expected_kp_octagon, actual_kp_octagon) - assert_array_equal(expected_scale, actual_scale) - - -def test_keypoints_censure_moon_image_star(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for STAR filter.""" - img = moon() - actual_kp_star, actual_scale = keypoints_censure(img, 1, 7, 'STAR', 0.15) - expected_kp_star = np.array([[ 21, 497], - [ 36, 46], - [117, 356], - [185, 177], - [260, 227], - [287, 250], - [357, 239], - [451, 281], - [463, 116], - [467, 260]]) - - expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) - - assert_array_equal(expected_kp_star, actual_kp_star) - assert_array_equal(expected_scale, actual_scale) - - -if __name__ == '__main__': - from numpy import testing - testing.run_module_suite() diff --git a/skimage/feature/tests/test_censure.py b/skimage/feature/tests/test_censure.py new file mode 100644 index 00000000..611604b6 --- /dev/null +++ b/skimage/feature/tests/test_censure.py @@ -0,0 +1,89 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_raises +from skimage.data import moon +from skimage.feature import CenSurE + + +img = moon() + + +def test_keypoints_censure_color_image_unsupported_error(): + """Censure keypoints can be extracted from gray-scale images only.""" + assert_raises(ValueError, CenSurE().detect, np.zeros((20, 20, 3))) + + +def test_keypoints_censure_mode_validity_error(): + """Mode argument in keypoints_censure can be either DoB, Octagon or + STAR.""" + assert_raises(ValueError, CenSurE, mode='dummy') + + +def test_keypoints_censure_scale_range_error(): + """Difference between the the max_scale and min_scale parameters in + keypoints_censure should be greater than or equal to two.""" + assert_raises(ValueError, CenSurE, min_scale=1, max_scale=2) + + +def test_keypoints_censure_moon_image_dob(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for DoB filter.""" + detector = CenSurE() + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 497], + [ 36, 46], + [119, 350], + [185, 177], + [287, 250], + [357, 239], + [463, 116], + [464, 132], + [467, 260]]) + expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scales, scales) + + +def test_keypoints_censure_moon_image_octagon(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for Octagon filter.""" + + detector = CenSurE(mode='octagon') + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 496], + [ 35, 46], + [287, 250], + [356, 239], + [463, 116]]) + + expected_scales = np.array([3, 4, 2, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scales, scales) + + +def test_keypoints_censure_moon_image_star(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for STAR filter.""" + detector = CenSurE(mode='star') + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 497], + [ 36, 46], + [117, 356], + [185, 177], + [260, 227], + [287, 250], + [357, 239], + [451, 281], + [463, 116], + [467, 260]]) + + expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scale, scales) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() From 2705907270549a4b6990e5da005ae453a7ad560c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 01:46:16 +0100 Subject: [PATCH 103/145] Refactor ORB --- skimage/feature/__init__.py | 20 +- skimage/feature/censure.py | 4 +- skimage/feature/orb.py | 334 +++++++++++++++++------------- skimage/feature/orb_cy.pyx | 12 +- skimage/feature/tests/test_orb.py | 115 ++++++---- 5 files changed, 284 insertions(+), 201 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 6c9c0df5..4f541227 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -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'] diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index dc9cd58c..a2830033 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -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. """ diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 74414465..62bf257f 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -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]) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index 68911bb9..b497c74d 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -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) diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index 956ff4b4..f857ca75 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -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__': From f22e00e693c811f8b6d4c593cc2bc6c65b95e938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 03:55:40 +0100 Subject: [PATCH 104/145] Refactor match_descriptors and fix small bugs in ORB --- doc/examples/plot_orb_matching.py | 101 ++++++-------- skimage/feature/__init__.py | 6 +- skimage/feature/corner_cy.pyx | 9 +- skimage/feature/match.py | 102 ++++++--------- skimage/feature/match_cy.pyx | 18 --- skimage/feature/orb.py | 8 +- skimage/feature/setup.py | 3 - skimage/feature/tests/test_match.py | 195 ++++++++-------------------- skimage/feature/tests/test_orb.py | 2 - skimage/feature/util.py | 25 ---- 10 files changed, 152 insertions(+), 317 deletions(-) delete mode 100644 skimage/feature/match_cy.pyx diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb_matching.py index 0818b76c..2fc5027e 100644 --- a/doc/examples/plot_orb_matching.py +++ b/doc/examples/plot_orb_matching.py @@ -1,78 +1,63 @@ import numpy as np from skimage import data from skimage import transform as tf -from skimage.feature import (pairwise_hamming_distance, - match_binary_descriptors, corner_harris, - corner_peaks, keypoints_orb, descriptor_orb) +from skimage.feature import (match_descriptors, corner_harris, + corner_peaks, ORB) from skimage.color import rgb2gray from skimage import img_as_float import matplotlib.pyplot as plt -# Initializing parameters for transformation -rotate = 0.5 -translate = (-100, -200) -scaling = (1.5, 1.5) -# Creating a transformed image from the original Lena image by scaling and -# rotating it -img_color = data.lena() -tform = tf.AffineTransform(scale = scaling, rotation=rotate, - translation=translate) -transformed_img_color = tf.warp(img_color, tform) -img = rgb2gray(img_color) -transformed_img = rgb2gray(transformed_img_color) +img1_color = data.lena() +img2_color = tf.rotate(img1_color, 180) +tform = tf.AffineTransform(scale=(1.3, 1.1), rotation=0.5, + translation=(0, -200)) +img3_color = tf.warp(img1_color, tform) +img1 = rgb2gray(img1_color) +img2 = rgb2gray(img2_color) +img3 = rgb2gray(img3_color) -# Extracting oFAST keypoints and computing their rBRIEF descriptors -keypoints1 = keypoints_orb(img, n_keypoints=500) -keypoints1.shape -descriptors1, keypoints1 = descriptor_orb(img, keypoints1) -keypoints1.shape -descriptors1.shape +descriptor_extractor = ORB(n_keypoints=200) +keypoints1, descriptors1 = descriptor_extractor.detect_and_extract(img1) +keypoints2, descriptors2 = descriptor_extractor.detect_and_extract(img2) +keypoints3, descriptors3 = descriptor_extractor.detect_and_extract(img3) -keypoints2 = keypoints_orb(transformed_img, - n_keypoints=500) -keypoints2.shape -descriptors2, keypoints2 = descriptor_orb(transformed_img, keypoints2) -keypoints2.shape -descriptors2.shape +idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) +src12 = keypoints1[idxs1] +dst12 = keypoints2[idxs2] -#Initializing parameters for Descriptor matching -match_threshold = 0.3 -match_cross_check = True +idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) +src13 = keypoints1[idxs1] +dst13 = keypoints3[idxs3] -pairwise_hamming_distance(descriptors1, descriptors2) -matched_keypoints, mask1, mask2 = match_binary_descriptors(keypoints1, - descriptors1, - keypoints2, - descriptors2, - cross_check=match_cross_check, - threshold=match_threshold) +img12 = np.concatenate((img_as_float(img1_color), + img_as_float(img2_color)), axis=1) +img13 = np.concatenate((img_as_float(img1_color), + img_as_float(img3_color)), axis=1) -matched_keypoints.shape +imgs = (img12, img13) +srcs = (src12, src13) +dsts = (dst12, dst13) -# Plotting the matched correspondences in both the images using matplotlib -src = matched_keypoints[:, 0, :] -dst = matched_keypoints[:, 1, :] -src_scale = 10 * (keypoints1.octave[mask1] + 1) ** 1.5 -dst_scale = 10 * (keypoints2.octave[mask2] + 1) ** 1.5 +offset = img1.shape -img_combined = np.concatenate((img_as_float(img_color), - img_as_float(transformed_img_color)), axis=1) -offset = img.shape +fig, ax = plt.subplots(nrows=2, ncols=1) -fig, ax = plt.subplots(nrows=1, ncols=1) -plt.gray() +for i in range(2): -ax.imshow(img_combined, interpolation='nearest') -ax.axis('off') -ax.axis((0, 2 * offset[1], offset[0], 0)) -ax.set_title('Matched correspondences : Rotation = %f; Scale = %s; Translation = %s; threshold = %f; cross_check = %r' % (rotate, scaling, translate, match_threshold, match_cross_check)) + ax[i].imshow(imgs[i], interpolation='nearest') + ax[i].axis('off') + ax[i].axis((0, 2 * offset[1], offset[0], 0)) -for m in range(len(src)): - c = np.random.rand(3,1) - ax.plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), '-', color=c) - ax.scatter(src[m, 1], src[m, 0], src_scale[m], facecolors='none', edgecolors=c) - ax.scatter(dst[m, 1] + offset[1], dst[m, 0], dst_scale[m], facecolors='none', edgecolors=c) + src = srcs[i] + dst = dsts[i] + + for m in range(len(src)): + color = np.random.rand(3, 1) + ax[i].plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), + '-', color=color) + ax[i].scatter(src[m, 1], src[m, 0], facecolors='none', edgecolors=color) + ax[i].scatter(dst[m, 1] + offset[1], dst[m, 0], facecolors='none', + edgecolors=color) plt.show() - diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 4f541227..4344c311 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -12,8 +12,7 @@ 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 .match import match_descriptors __all__ = ['daisy', @@ -39,5 +38,4 @@ __all__ = ['daisy', 'BRIEF', 'CenSurE', 'ORB', - 'pairwise_hamming_distance', - 'match_binary_descriptors'] + 'match_descriptors'] diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index c337fafd..86ded549 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -7,7 +7,7 @@ cimport numpy as cnp from libc.float cimport DBL_MAX from libc.math cimport atan2 -from skimage.util import img_as_float +from skimage.util import img_as_float, pad from skimage.color import rgb2grey from .util import _prepare_grayscale_input_2D @@ -240,7 +240,8 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1: raise ValueError("Size of mask must be uneven.") - cdef double[:, :] cimage = image + cdef double[:, :] cimage = pad(image, 16, mode='constant', + constant_values=0) cdef char[:, ::1] cmask = np.ascontiguousarray(mask != 0, dtype=np.uint8) cdef Py_ssize_t i, r, c, r0, c0 @@ -253,8 +254,8 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): cdef double m01, m10 for i in range(corners.shape[0]): - r0 = corners[i, 0] - mrows2 - c0 = corners[i, 1] - mcols2 + r0 = corners[i, 0] - mrows2 + 16 + c0 = corners[i, 1] - mcols2 + 16 m01 = 0 m10 = 0 diff --git a/skimage/feature/match.py b/skimage/feature/match.py index 86d32eb8..c6f23f6b 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -1,81 +1,65 @@ import numpy as np - -from .util import pairwise_hamming_distance -from .match_cy import _binary_cross_check_loop +from scipy.spatial.distance import cdist -def match_binary_descriptors(keypoints1, descriptors1, keypoints2, - descriptors2, threshold=0.40, cross_check=True): - """Match keypoints described using binary descriptors in one image to - those in second image. +def match_descriptors(descriptors1, descriptors2, metric=None, p=2, + threshold=0, cross_check=True): + """Brute-force matching of descriptors. + + For each descriptor in the first set this matcher finds the closest + descriptor in the second set (and vice-versa in the case of enabled + cross-checking). Parameters ---------- - keypoints1 : record array with M rows - Record array with fields row, col, octave, orientation, response. - Octave, orientation and response can be None. - descriptors1 : (M, P) ndarray + descriptors1 : (M, P) array Binary descriptors of size P about M keypoints in the first image. - keypoints2 : record array with N rows - Record array with fields row, col, octave, orientation, response. - Octave, orientation and response can be None. - descriptors2 : (N, P) ndarray + descriptors2 : (N, P) array Binary descriptors of size P about N keypoints in the second image. - threshold : float in range [0, 1] - Maximum allowable hamming distance between descriptors of two keypoints + metric : {'euclidean', 'cityblock', 'minkowski', 'hamming', ...} + The metric to compute the distance between two descriptors. See + `scipy.spatial.distance.cdist` for all possible types. The hamming + distance should be used for binary descriptors. By default the L2-norm + is used for all descriptors of dtype float or double and the Hamming + distance is used for binary descriptors automatically. + p : int + The p-norm to apply for ``metric='minkowski'``. + threshold : float + Maximum allowed distance between descriptors of two keypoints in separate images to be regarded as a match. cross_check : bool If True, the matched keypoints are returned after cross checking i.e. a - matched pair (keypoint1, keypoint2) is returned iff keypoint2 is the best - match for keypoint1 in second image and keypoint1 is the best match for - keypoint2 in first image. + matched pair (keypoint1, keypoint2) is returned if keypoint2 is the + best match for keypoint1 in second image and keypoint1 is the best + match for keypoint2 in first image. Returns ------- - matches : (Q, 2, 2) ndarray - Location of Q matched keypoint pairs from two images. - idxs1 : (Q,) ndarray - Indices of keypoints in keypoints1 that have been matched. - idxs2 : (Q,) ndarray - Indices of keypoints in keypoints2 that have been matched. + indices1 : (Q, ) array + Indices of corresponding matches for first set of descriptors. + indices2 : (Q, ) array + Indices of corresponding matches for second set of descriptors. """ - if (keypoints1.shape[0] != descriptors1.shape[0] - or keypoints2.shape[0] != descriptors2.shape[0]): - raise ValueError("The number of keypoints and number of described " - "keypoints do not match.") if descriptors1.shape[1] != descriptors2.shape[1]: - raise ValueError("Descriptor sizes for matching keypoints in both " - "the images should be equal.") + raise ValueError("Descriptor length must equal.") - # Get hamming distances between keypoints1 and keypoints2 - distance = pairwise_hamming_distance(descriptors1, descriptors2) + if metric is None: + if np.issubdtype(descriptors1.dtype, np.bool): + metric = 'hamming' + else: + metric = 'euclidean' + + distances = cdist(descriptors1, descriptors2, metric=metric, p=p) + + indices1 = np.arange(descriptors1.shape[0]) + indices2 = np.argmin(distances, axis=1) if cross_check: - matched_keypoints1_index = np.argmin(distance, axis=1) - matched_keypoints2_index = np.argmin(distance, axis=0) + matches1 = np.argmin(distances, axis=0) + mask = indices1 == matches1[indices2] + indices1 = indices1[mask] + indices2 = indices2[mask] - matched_idxs = _binary_cross_check_loop(matched_keypoints1_index, - matched_keypoints2_index, - distance, threshold) - - matches = np.zeros((matched_idxs.shape[0], 2, 2), - dtype=np.intp) - 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 = keypoints2[np.argmin(distance, axis=1)] - matches = np.zeros((np.sum(row_check), 2, 2), - dtype=np.intp) - matches[:, 0, :] = keypoints1[row_check] - matches[:, 1, :] = matched_keypoints2[row_check] - idxs1 = np.where(row_check == True)[0] - idxs2 = np.argmin(distance, axis=1)[row_check] - - return matches, mask1, mask2 + return indices1, indices2 diff --git a/skimage/feature/match_cy.pyx b/skimage/feature/match_cy.pyx deleted file mode 100644 index 6574d573..00000000 --- a/skimage/feature/match_cy.pyx +++ /dev/null @@ -1,18 +0,0 @@ -import numpy as np - - -def _binary_cross_check_loop(Py_ssize_t[:] matched_keypoints1_index, - Py_ssize_t[:] matched_keypoints2_index, - double[:, ::1] distance, double threshold): - cdef Py_ssize_t i - cdef Py_ssize_t count = 0 - cdef Py_ssize_t[:, ::1] matched_index = np.zeros((len(matched_keypoints1_index), 2), dtype=np.intp) - - for i in range(len(matched_keypoints1_index)): - if (matched_keypoints2_index[matched_keypoints1_index[i]] == i and - distance[i, matched_keypoints1_index[i]] < threshold): - matched_index[count, 0] = i - matched_index[count, 1] = matched_keypoints1_index[i] - count += 1 - - return np.asarray(matched_index[:count, :]) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 62bf257f..013a6f94 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -151,7 +151,7 @@ class ORB(FeatureDetector, DescriptorExtractor): def _extract_octave(self, octave_image, keypoints, orientations): mask = _mask_border_keypoints(octave_image.shape, keypoints, - distance=16) + distance=20) keypoints = np.array(keypoints[mask], dtype=np.intp, order='C', copy=False) orientations = np.array(orientations[mask], dtype=np.double, order='C', @@ -260,8 +260,8 @@ class ORB(FeatureDetector, DescriptorExtractor): descriptors, mask = self._extract_octave(octave_image, keypoints, orientations) - keypoints_list.append(keypoints * self.downscale ** octave) - responses_list.append(responses) + keypoints_list.append(keypoints[mask] * self.downscale ** octave) + responses_list.append(responses[mask]) descriptors_list.append(descriptors) keypoints = np.vstack(keypoints_list) @@ -273,4 +273,4 @@ class ORB(FeatureDetector, DescriptorExtractor): 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]) + return keypoints[best_indices], descriptors[best_indices] diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 9dc35643..c135f0d4 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -16,7 +16,6 @@ def configuration(parent_package='', top_path=None): cython(['censure_cy.pyx'], working_path=base_path) cython(['orb_cy.pyx'], working_path=base_path) cython(['brief_cy.pyx'], working_path=base_path) - cython(['match_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) @@ -28,8 +27,6 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('brief_cy', sources=['brief_cy.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('match_cy', sources=['match_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'], diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index 9bb2c958..3b17ed4d 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -1,45 +1,32 @@ import numpy as np -from numpy.testing import assert_array_equal, assert_raises +from numpy.testing import assert_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_descriptors, + corner_peaks, corner_harris) -def test_match_binary_descriptors_unequal_descriptor_keypoints_error(): - """Number of descriptors should be equal to the number of keypoints.""" - kp1 = np.array([[40, 50], - [60, 40], - [30, 70]]) - keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) - des1 = np.array([[True, True, False, True], - [False, True, False, True]]) - kp2 = np.array([[60, 50], - [50, 80]]) - keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) - des2 = np.array([[True, False, False, True], - [False, True, True, True]]) - assert_raises(ValueError, match_binary_descriptors, keypoints1, des1, keypoints2, des2) - - -def test_match_binary_descriptors_unequal_descriptor_sizes_error(): +def test_binary_descriptors_unequal_descriptor_sizes_error(): """Sizes of descriptors of keypoints to be matched should be equal.""" - kp1 = np.array([[40, 50], - [60, 40]]) - keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) des1 = np.array([[True, True, False, True], [False, True, False, True]]) - kp2 = np.array([[60, 50], - [50, 80]]) - keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) des2 = np.array([[True, False, False, True, False], [False, True, True, True, False]]) - assert_raises(ValueError, match_binary_descriptors, keypoints1, des1, keypoints2, des2) + assert_raises(ValueError, match_descriptors, des1, des2) -def test_match_binary_descriptors_lena_rotation_crosscheck_false(): +def test_binary_descriptors(): + des1 = np.array([[True, True, False, True, True], + [False, True, False, True, True]]) + des2 = np.array([[True, False, False, True, False], + [False, False, True, True, True]]) + indices1, indices2 = match_descriptors(des1, des2) + assert_equal(indices1, [0, 1]) + assert_equal(indices2, [0, 1]) + + +def test_binary_descriptors_lena_rotation_crosscheck_false(): """Verify matched keypoints and their corresponding masks results between lena image and its rotated version with the expected keypoint pairs with cross_check disabled.""" @@ -48,72 +35,35 @@ def test_match_binary_descriptors_lena_rotation_crosscheck_false(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - kp1 = corner_peaks(corner_harris(img), min_distance=5) - keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) - descriptors1, keypoints1 = descriptor_brief(img, keypoints1, - descriptor_size=512) + descriptor = BRIEF(descriptor_size=512) - kp2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) - descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, - descriptor_size=512) + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, mask1 = descriptor.extract(img, keypoints1) + keypoints1 = keypoints1[mask1] - matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, - descriptors1, - keypoints2, - descriptors2, - threshold=0.13, - cross_check=False) + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) + keypoints2 = keypoints1[mask2] - expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 39, 40, - 42, 45]) - expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 10, 11, - 12, 13]) - expected = np.array([[[245, 141], - [221, 176]], + m1, m2 = match_descriptors(descriptors1, descriptors2, threshold=0.13, + cross_check=False) - [[247, 130], - [225, 165]], + print m1 + print m2 - [[263, 272], - [219, 309]], - - [[271, 120], - [250, 159]], - - [[311, 174], - [282, 218]], - - [[323, 164], - [294, 210]], - - [[327, 147], - [301, 195]], - - [[377, 157], - [349, 211]], - - [[414, 70], - [399, 131]], - - [[425, 67], - [399, 131]], - - [[435, 181], - [403, 244]], - - [[454, 176], - [423, 242]], - - [[467, 166], - [437, 234]]]) - - assert_array_equal(matched_keypoints, expected) - assert_array_equal(m1, expected_mask1) - assert_array_equal(m2, expected_mask2) + expected_mask1 = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46]) + expected_mask2 = np.array([33, 0, 35, 7, 1, 35, 3, 2, 3, 6, 4, 9, + 11, 10, 28, 7, 8, 5, 31, 14, 13, 15, 21, 16, + 16, 13, 17, 18, 19, 21, 22, 23, 0, 24, 1, 24, + 23, 0, 26, 27, 25, 34, 28, 14, 29, 30, 21]) + assert_equal(m1, expected_mask1) + assert_equal(m2, expected_mask2) -def test_match_binary_descriptors_lena_rotation_crosscheck_true(): +def test_binary_descriptors_lena_rotation_crosscheck_true(): """Verify matched keypoints and their corresponding masks results between lena image and its rotated version with the expected keypoint pairs with cross_check enabled.""" @@ -122,62 +72,27 @@ def test_match_binary_descriptors_lena_rotation_crosscheck_true(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - kp1 = corner_peaks(corner_harris(img), min_distance=5) - keypoints1 = create_keypoint_recarray(kp1[:, 0], kp1[:, 1]) - descriptors1, keypoints1 = descriptor_brief(img, keypoints1, descriptor_size=512) + descriptor = BRIEF(descriptor_size=512) - kp2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - keypoints2 = create_keypoint_recarray(kp2[:, 0], kp2[:, 1]) - descriptors2, keypoints2 = descriptor_brief(rotated_img, keypoints2, - descriptor_size=512) + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, mask1 = descriptor.extract(img, keypoints1) + keypoints1 = keypoints1[mask1] - matched_keypoints, m1, m2 = match_binary_descriptors(keypoints1, - descriptors1, - keypoints2, - descriptors2, - threshold=0.13) + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) + keypoints2 = keypoints1[mask2] - expected = np.array([[[245, 141], - [221, 176]], + m1, m2 = match_descriptors(descriptors1, descriptors2, threshold=0.13, + cross_check=True) - [[247, 130], - [225, 165]], - - [[263, 272], - [219, 309]], - - [[271, 120], - [250, 159]], - - [[311, 174], - [282, 218]], - - [[323, 164], - [294, 210]], - - [[327, 147], - [301, 195]], - - [[377, 157], - [349, 211]], - - [[414, 70], - [399, 131]], - - [[435, 181], - [403, 244]], - - [[454, 176], - [423, 242]], - - [[467, 166], - [437, 234]]]) - - expected_mask1 = np.array([11, 12, 16, 20, 24, 26, 27, 29, 35, 40, 42, 45]) - expected_mask2 = np.array([ 1, 3, 0, 4, 6, 7, 8, 9, 10, 11, 12, 13]) - assert_array_equal(matched_keypoints, expected) - assert_array_equal(m1, expected_mask1) - assert_array_equal(m2, expected_mask2) + expected_mask1 = np.array([ 0, 1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 15, + 16, 17, 19, 20, 21, 24, 26, 27, 28, 29, 30, 35, + 36, 38, 39, 40, 42, 44, 45]) + expected_mask2 = np.array([33, 0, 35, 1, 3, 2, 6, 4, 9, 11, 10, 7, + 8, 5, 14, 13, 15, 16, 17, 18, 19, 21, 22, 24, + 23, 26, 27, 25, 28, 29, 30]) + assert_equal(m1, expected_mask1) + assert_equal(m2, expected_mask2) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index f857ca75..9c2a1d6e 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -105,8 +105,6 @@ def test_descriptor_orb(): 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__': from numpy import testing diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 1da4b613..d3945554 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -76,28 +76,3 @@ def _mask_border_keypoints(image_shape, keypoints, distance): & (keypoints[:, 1] < (cols - distance + 1))) return mask - - -def pairwise_hamming_distance(array1, array2): - """**Experimental function**. - - Calculate hamming dissimilarity measure between two sets of - vectors. - - Parameters - ---------- - array1 : (P1, D) array - P1 vectors of size D. - array2 : (P2, D) array - P2 vectors of size D. - - Returns - ------- - distance : (P1, P2) array of dtype float - 2D ndarray with value at an index (i, j) representing the hamming - distance in the range [0, 1] between ith vector in array1 and jth - vector in array2. - - """ - distance = (array1[:, None] != array2[None]).mean(axis=2) - return distance From 5662d705aa4b132fba895b5fd8557e05586e05ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 04:00:07 +0100 Subject: [PATCH 105/145] Use dynamic pad-width for arbitrary mask shapes --- skimage/feature/corner_cy.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 86ded549..3fd860a2 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -240,8 +240,6 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1: raise ValueError("Size of mask must be uneven.") - cdef double[:, :] cimage = pad(image, 16, mode='constant', - constant_values=0) cdef char[:, ::1] cmask = np.ascontiguousarray(mask != 0, dtype=np.uint8) cdef Py_ssize_t i, r, c, r0, c0 @@ -249,13 +247,15 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): cdef Py_ssize_t mcols = mask.shape[1] cdef Py_ssize_t mrows2 = (mrows - 1) / 2 cdef Py_ssize_t mcols2 = (mcols - 1) / 2 + cdef double[:, :] cimage = pad(image, (mrows2, mcols2), mode='constant', + constant_values=0) cdef double[:] orientations = np.zeros(corners.shape[0], dtype=np.double) cdef double curr_pixel cdef double m01, m10 for i in range(corners.shape[0]): - r0 = corners[i, 0] - mrows2 + 16 - c0 = corners[i, 1] - mcols2 + 16 + r0 = corners[i, 0] + c0 = corners[i, 1] m01 = 0 m10 = 0 From dea338b490e13cb9bd8b0c9b34abe5b571327267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 04:02:48 +0100 Subject: [PATCH 106/145] Fix test cases --- skimage/feature/tests/test_brief.py | 3 +-- skimage/feature/tests/test_util.py | 25 ------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 0f0a9b1f..414be70c 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -3,8 +3,7 @@ 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 (BRIEF, match_binary_descriptors, - corner_peaks, corner_harris) +from skimage.feature import BRIEF, corner_peaks, corner_harris def test_color_image_unsupported_error(): diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 6e25f51a..6e56d0dd 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -1,30 +1,5 @@ import numpy as np from numpy.testing import assert_array_equal -from skimage.feature.util import pairwise_hamming_distance - - -def test_pairwise_hamming_distance_range(): - """Values of all the pairwise hamming distances should be in the range - [0, 1].""" - a = np.random.random_sample((10, 50)) > 0.5 - b = np.random.random_sample((20, 50)) > 0.5 - dist = pairwise_hamming_distance(a, b) - assert np.all((0 <= dist) & (dist <= 1)) - - -def test_pairwise_hamming_distance_value(): - """The result of pairwise_hamming_distance of two fixed sets of boolean - vectors should be same as expected.""" - np.random.seed(10) - a = np.random.random_sample((4, 100)) > 0.5 - np.random.seed(20) - b = np.random.random_sample((3, 100)) > 0.5 - result = pairwise_hamming_distance(a, b) - expected = np.array([[0.5 , 0.49, 0.44], - [0.44, 0.53, 0.52], - [0.4 , 0.55, 0.5 ], - [0.47, 0.48, 0.57]]) - assert_array_equal(result, expected) if __name__ == '__main__': From 722347388bbf5a8de5e08b45a03d15b81948a0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 04:11:43 +0100 Subject: [PATCH 107/145] Update bento.info --- bento.info | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bento.info b/bento.info index f819d7c0..986eb34e 100644 --- a/bento.info +++ b/bento.info @@ -96,15 +96,12 @@ Library: Extension: skimage.feature.censure_cy Sources: skimage/feature/censure_cy.pyx - Extension: skimage.feature.match_cy - Sources: - skimage/feature/match_cy.pyx Extension: skimage.feature.orb_cy Sources: skimage/feature/orb_cy.pyx - Extension: skimage.feature._brief_cy + Extension: skimage.feature.brief_cy Sources: - skimage/feature/_brief_cy.pyx + skimage/feature/brief_cy.pyx Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx From d7b598c30b41f5a83c94e9c1a4c8fe166e9f4f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 10:03:00 +0100 Subject: [PATCH 108/145] Fix legacy import --- skimage/feature/brief.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index e29dd230..38ed7061 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -4,7 +4,7 @@ from scipy.ndimage.filters import gaussian_filter from .util import (DescriptorExtractor, _mask_border_keypoints, _prepare_grayscale_input_2D) -from ._brief_cy import _brief_loop +from .brief_cy import _brief_loop class BRIEF(DescriptorExtractor): From 89dc0ac5ac8582f1164a89bcf4b04a44fe95b944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 10:23:34 +0100 Subject: [PATCH 109/145] Update BRIEF example --- skimage/feature/brief.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index 38ed7061..cac7f347 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -47,7 +47,7 @@ class BRIEF(DescriptorExtractor): Examples -------- >>> from skimage.feature import (corner_harris, corner_peaks, BRIEF, - ... match_binary_descriptors) + ... match_descriptors) >>> import numpy as np >>> square1 = np.zeros((8, 8), dtype=np.int32) >>> square1[2:6, 2:6] = 1 @@ -77,24 +77,19 @@ class BRIEF(DescriptorExtractor): >>> 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]], - - [[2, 5], - [2, 6]], - - [[5, 2], - [6, 2]], - - [[5, 5], - [6, 6]]]) - >>> mask1 - array([0, 1, 2, 3]) - >>> mask2 - array([0, 1, 2, 3]) + >>> idxs1, idxs2 = match_descriptors(descs1, descs2) + >>> idxs1, idxs2 + (array([0, 1, 2, 3]), array([0, 1, 2, 3])) + >>> keypoints1[idxs1] + array([[2, 2], + [2, 5], + [5, 2], + [5, 5]]) + >>> keypoints2[idxs2] + array([[2, 2], + [2, 6], + [6, 2], + [6, 6]]) """ From f9cfdb7e2d76383d01d64e5bc1ae3409586ba755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 10:38:19 +0100 Subject: [PATCH 110/145] Add example to ORB doc string --- skimage/feature/orb.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 013a6f94..35f0ad45 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -60,6 +60,34 @@ class ORB(FeatureDetector, DescriptorExtractor): "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 ORB, match_descriptors + >>> img1 = np.zeros((100, 100)) + >>> img2 = np.zeros_like(img1) + >>> np.random.seed(1) + >>> square = np.random.rand(20, 20) + >>> img1[40:60, 40:60] = square + >>> img2[53:73, 53:73] = square + >>> detector_extractor = ORB(n_keypoints=5) + >>> keypoints1, descriptors1 = detector_extractor.detect_and_extract(img1) + >>> keypoints2, descriptors2 = detector_extractor.detect_and_extract(img2) + >>> idxs1, idxs2 = match_descriptors(descriptors1, descriptors2) + >>> idxs1, idxs2 + (array([0, 1, 2, 3, 4]), array([0, 1, 2, 3, 4])) + >>> keypoints1[idxs1] + array([[ 42., 40.], + [ 47., 58.], + [ 44., 40.], + [ 59., 42.], + [ 45., 44.]]) + >>> keypoints2[idxs2] + array([[ 55., 53.], + [ 60., 71.], + [ 57., 53.], + [ 72., 55.], + [ 58., 57.]]) + """ def __init__(self, downscale=1.2, n_scales=8, From 88f7038edb1d8e5f20d37993d543841e9fea795b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 10:49:43 +0100 Subject: [PATCH 111/145] Add note about usage of ORB --- skimage/feature/orb.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 35f0ad45..ac9631db 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -192,6 +192,11 @@ class ORB(FeatureDetector, DescriptorExtractor): def extract(self, image, keypoints, scales, orientations): """Extract rBRIEF binary descriptors for given keypoints in image. + Note that the keypoints must be extracted using the same `downscale` + and `n_scales` parameters. Additionally, if you want to extract both + keypoints and descriptors you should use the faster + `detect_and_extract`. + Parameters ---------- image : 2D array @@ -254,6 +259,9 @@ class ORB(FeatureDetector, DescriptorExtractor): def detect_and_extract(self, image): """Detect oriented FAST keypoints and extract rBRIEF descriptors. + Note that this is faster than first calling `detect` and then + `extract`. + Parameters ---------- image : 2D array From 85efc6a2cd4143b5c27861b5779b5fc304685f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 10:58:38 +0100 Subject: [PATCH 112/145] Add example to doc string of CenSurE --- skimage/feature/censure.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index a2830033..f201e12f 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -151,6 +151,32 @@ class CenSurE(FeatureDetector): Descriptors in the Context of Robot Navigation" http://www.jamris.org/01_2013/saveas.php?QUEST=JAMRIS_No01_2013_P_11-20.pdf + Examples + -------- + >>> from skimage.data import lena + >>> from skimage.color import rgb2gray + >>> from skimage.feature import CenSurE + >>> img = rgb2gray(lena()[100:300, 100:300]) + >>> keypoints, scales = CenSurE().detect(img) + >>> keypoints + array([[ 71, 148], + [ 77, 186], + [ 78, 189], + [ 89, 174], + [127, 134], + [131, 133], + [134, 125], + [137, 125], + [149, 36], + [162, 165], + [168, 167], + [170, 5], + [171, 29], + [179, 20], + [194, 65]]) + >>> scales + array([2, 4, 2, 3, 4, 2, 2, 3, 4, 6, 3, 2, 3, 4, 2]) + """ def __init__(self, min_scale=1, max_scale=7, mode='DoB', From 23a1e22bd466d71b1e708068bfab1c98bd261e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 11:06:34 +0100 Subject: [PATCH 113/145] Fix corner doctests --- skimage/feature/corner.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 33f9b39a..665bfcf1 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -339,9 +339,9 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1): Examples -------- >>> from skimage.feature import corner_harris, corner_peaks - >>> square = np.zeros([10, 10], dtype=int) + >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 - >>> square + >>> square.astype(int) array([[0, 0, 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, 1, 0, 0], @@ -409,9 +409,9 @@ def corner_shi_tomasi(image, sigma=1): Examples -------- >>> from skimage.feature import corner_shi_tomasi, corner_peaks - >>> square = np.zeros([10, 10], dtype=int) + >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 - >>> square + >>> square.astype(int) array([[0, 0, 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, 1, 0, 0], @@ -609,10 +609,10 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): Examples -------- >>> from skimage.feature import corner_harris, corner_peaks, corner_subpix - >>> img = np.zeros((10, 10), dtype=int) + >>> img = np.zeros((10, 10)) >>> img[:5, :5] = 1 >>> img[5:, 5:] = 1 - >>> img + >>> img.astype(int) array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], From 9d585aedef94173cab5150917f80bf66133215cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 11:17:02 +0100 Subject: [PATCH 114/145] Add test cases for feature utilities --- skimage/feature/tests/test_util.py | 37 +++++++++++++++++++++++++++++- skimage/feature/util.py | 6 ----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 6e56d0dd..6f3f78e1 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -1,5 +1,40 @@ import numpy as np -from numpy.testing import assert_array_equal +from numpy.testing import assert_equal, assert_raises +from skimage.feature.util import (FeatureDetector, DescriptorExtractor, + _prepare_grayscale_input_2D, + _mask_border_keypoints) + + +def test_feature_detector(): + assert_raises(NotImplementedError, FeatureDetector().detect, None) + + +def test_descriptor_extractor(): + assert_raises(NotImplementedError, DescriptorExtractor().extract, + None, None) + + +def test_prepare_grayscale_input_2D(): + assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 3, 3))) + assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 1))) + assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 1, 1))) + img = _prepare_grayscale_input_2D(np.zeros((3, 3))) + img = _prepare_grayscale_input_2D(np.zeros((3, 3, 1))) + img = _prepare_grayscale_input_2D(np.zeros((1, 3, 3))) + + +def test_mask_border_keypoints(): + keypoints = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]]) + assert_equal(_mask_border_keypoints((10, 10), keypoints, 0), + [1, 1, 1, 1, 1]) + assert_equal(_mask_border_keypoints((10, 10), keypoints, 2), + [0, 0, 1, 1, 1]) + assert_equal(_mask_border_keypoints((4, 4), keypoints, 2), + [0, 0, 1, 0, 0]) + assert_equal(_mask_border_keypoints((10, 10), keypoints, 5), + [0, 0, 0, 0, 0]) + assert_equal(_mask_border_keypoints((10, 10), keypoints, 4), + [0, 0, 0, 0, 1]) if __name__ == '__main__': diff --git a/skimage/feature/util.py b/skimage/feature/util.py index d3945554..3d84298a 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -5,9 +5,6 @@ from skimage.util import img_as_float class FeatureDetector(object): - def __init__(self): - raise NotImplementedError() - def detect(self, image): """Detect keypoints in image. @@ -22,9 +19,6 @@ class FeatureDetector(object): class DescriptorExtractor(object): - def __init__(self): - raise NotImplementedError() - def extract(self, image, keypoints): """Extract feature descriptors in image for given keypoints. From 89233d8409e2972d6c97406f4e18010ba9f7aa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 11:45:24 +0100 Subject: [PATCH 115/145] Fix imports for Python 3 --- skimage/feature/corner.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 665bfcf1..48f74c27 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -1,13 +1,12 @@ import numpy as np from scipy import ndimage from scipy import stats + from skimage.color import rgb2grey from skimage.util import img_as_float, pad from skimage.feature import peak_local_max - -from .util import _prepare_grayscale_input_2D - -from corner_cy import _corner_fast +from skimage.feature.util import _prepare_grayscale_input_2D +from skimage.feature.corner_cy import _corner_fast def _compute_derivatives(image, mode='constant', cval=0): From 87131db7353d9b4208514d9e1394f2507f6622ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 11:48:48 +0100 Subject: [PATCH 116/145] Add test for unsupported mode --- skimage/feature/tests/test_brief.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 414be70c..61cde324 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -57,6 +57,10 @@ def test_uniform_mode(): assert_array_equal(descriptors, expected) +def test_unsupported_mode(): + assert_raises(ValueError, BRIEF, mode='foobar') + + def test_border(): img = np.zeros((100, 100)) keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]]) From abd1295a8d99ec490a68ba06775e4644cb62f9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 11:55:26 +0100 Subject: [PATCH 117/145] Add short description to ORB example --- doc/examples/{plot_orb_matching.py => plot_orb.py} | 14 ++++++++++++++ 1 file changed, 14 insertions(+) rename doc/examples/{plot_orb_matching.py => plot_orb.py} (80%) diff --git a/doc/examples/plot_orb_matching.py b/doc/examples/plot_orb.py similarity index 80% rename from doc/examples/plot_orb_matching.py rename to doc/examples/plot_orb.py index 2fc5027e..e7d1a828 100644 --- a/doc/examples/plot_orb_matching.py +++ b/doc/examples/plot_orb.py @@ -1,3 +1,17 @@ +""" +========================================== +ORB feature detector and binary descriptor +========================================== + +This example demonstrates the ORB feature detection and binary description +algorithm. It uses an oriented FAST detection method and the rotated BRIEF +descriptors. + +ORB is comparatively scale- and rotation-invariant. As a binary descriptor it +allows to employ the very efficient Hamming distance metric for matching and +is thus preferred for real-time applications. + +""" import numpy as np from skimage import data from skimage import transform as tf From 8fbc81eaacec16b182e3871e4e00c153d1888201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 12:40:26 +0100 Subject: [PATCH 118/145] Add convenience function for plotting matches --- doc/examples/plot_orb.py | 53 ++++++---------------- skimage/feature/__init__.py | 4 +- skimage/feature/util.py | 87 +++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index e7d1a828..d8b5cfbf 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -16,62 +16,35 @@ import numpy as np from skimage import data from skimage import transform as tf from skimage.feature import (match_descriptors, corner_harris, - corner_peaks, ORB) + corner_peaks, ORB, plot_matches) from skimage.color import rgb2gray from skimage import img_as_float import matplotlib.pyplot as plt -img1_color = data.lena() -img2_color = tf.rotate(img1_color, 180) +img1 = rgb2gray(data.lena()) +img2 = tf.rotate(img1, 180) tform = tf.AffineTransform(scale=(1.3, 1.1), rotation=0.5, translation=(0, -200)) -img3_color = tf.warp(img1_color, tform) -img1 = rgb2gray(img1_color) -img2 = rgb2gray(img2_color) -img3 = rgb2gray(img3_color) +img3 = tf.warp(img1, tform) descriptor_extractor = ORB(n_keypoints=200) keypoints1, descriptors1 = descriptor_extractor.detect_and_extract(img1) keypoints2, descriptors2 = descriptor_extractor.detect_and_extract(img2) keypoints3, descriptors3 = descriptor_extractor.detect_and_extract(img3) -idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) -src12 = keypoints1[idxs1] -dst12 = keypoints2[idxs2] - -idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) -src13 = keypoints1[idxs1] -dst13 = keypoints3[idxs3] - -img12 = np.concatenate((img_as_float(img1_color), - img_as_float(img2_color)), axis=1) -img13 = np.concatenate((img_as_float(img1_color), - img_as_float(img3_color)), axis=1) - -imgs = (img12, img13) -srcs = (src12, src13) -dsts = (dst12, dst13) - -offset = img1.shape - fig, ax = plt.subplots(nrows=2, ncols=1) -for i in range(2): +plt.gray() - ax[i].imshow(imgs[i], interpolation='nearest') - ax[i].axis('off') - ax[i].axis((0, 2 * offset[1], offset[0], 0)) +idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) +plot_matches(ax[0], img1, img2, keypoints1, keypoints2, + idxs1, idxs2) +ax[0].axis('off') - src = srcs[i] - dst = dsts[i] - - for m in range(len(src)): - color = np.random.rand(3, 1) - ax[i].plot((src[m, 1], dst[m, 1] + offset[1]), (src[m, 0], dst[m, 0]), - '-', color=color) - ax[i].scatter(src[m, 1], src[m, 0], facecolors='none', edgecolors=color) - ax[i].scatter(dst[m, 1] + offset[1], dst[m, 0], facecolors='none', - edgecolors=color) +idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) +plot_matches(ax[1], img1, img3, keypoints1, keypoints3, + idxs1, idxs3) +ax[1].axis('off') plt.show() diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 4344c311..2402d0ea 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -13,6 +13,7 @@ from .brief import BRIEF from .censure import CenSurE from .orb import ORB from .match import match_descriptors +from .util import plot_matches __all__ = ['daisy', @@ -38,4 +39,5 @@ __all__ = ['daisy', 'BRIEF', 'CenSurE', 'ORB', - 'match_descriptors'] + 'match_descriptors', + 'plot_matches'] diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 3d84298a..2d33261f 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -33,6 +33,93 @@ class DescriptorExtractor(object): raise NotImplementedError() +def plot_matches(ax, image1, image2, keypoints1, keypoints2, + indices1, indices2, keypoints_color='k', matches_color=None, + only_matches=False): + + """Plot matched features. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Matches and image are drawn in this ax. + image1 : (N, M [, 3]) array + First grayscale or color image. + image2 : (N, M [, 3]) array + Second grayscale or color image. + keypoints : (K1, 2) array + First keypoint coordinates as ``(row, col)``. + keypoints : (K2, 2) array + Second keypoint coordinates as ``(row, col)``. + keypoints : (K1, 2) array + Keypoint coordinates as ``(row, col)``. + indices1 : (Q, ) array + Indices of corresponding matches for first set of keypoints. + indices2 : (Q, ) array + Indices of corresponding matches for second set of keypoints. + keypoints_color : matplotlib color + Color for keypoint locations. + matches_color : matplotlib color + Color for lines which connect keypoint matches. By default the + color is chosen randomly. + only_matches : bool + Whether to only plot matches and not plot the keypoint locations. + + """ + + image1 = img_as_float(image1) + image2 = img_as_float(image2) + + new_shape1 = image1.shape + new_shape2 = image2.shape + + if image1.shape[0] < image2.shape[0]: + new_shape1[0] = image2.shape[0] + elif image1.shape[0] > image2.shape[0]: + new_shape2[0] = image1.shape[0] + + if image1.shape[1] < image2.shape[1]: + new_shape1[1] = image2.shape[1] + elif image1.shape[1] > image2.shape[1]: + new_shape2[1] = image1.shape[1] + + if new_shape1 != image1.shape: + new_image1 = np.zeros(new_shape1, dtype=image1.dtype) + new_image1[:image1.shape[0], :image1.shape[1]] = image1 + image1 = new_image1 + + if new_shape2 != image2.shape: + new_image2 = np.zeros(new_shape2, dtype=image2.dtype) + new_image2[:image2.shape[0], :image2.shape[1]] = image2 + image2 = new_image2 + + image = np.concatenate([image1, image2], axis=1) + + offset = image1.shape + + if not only_matches: + ax.scatter(keypoints1[:, 1], keypoints1[:, 0], + facecolors='none', edgecolors=keypoints_color) + ax.scatter(keypoints2[:, 1] + offset[1], keypoints2[:, 0], + facecolors='none', edgecolors=keypoints_color) + + ax.imshow(image) + ax.axis((0, 2 * offset[1], offset[0], 0)) + + for i in range(len(indices1)): + idx1 = indices1[i] + idx2 = indices2[i] + + if matches_color is None: + color = np.random.rand(3, 1) + else: + color = matches_color + + ax.plot((keypoints1[idx1, 1], keypoints2[idx2, 1] + offset[1]), + (keypoints1[idx1, 0], keypoints2[idx2, 0]), + '-', color=color) + + def _prepare_grayscale_input_2D(image): image = np.squeeze(image) if image.ndim != 2: From ae1c2a261d0ba9a860272a1d024f9af9c85f9afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 12:58:06 +0100 Subject: [PATCH 119/145] Use new plot_matches function for plot_matching example script --- doc/examples/plot_matching.py | 28 +++++++++++----------------- skimage/feature/util.py | 4 +++- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/doc/examples/plot_matching.py b/doc/examples/plot_matching.py index bae6d6e2..9fa0b41c 100644 --- a/doc/examples/plot_matching.py +++ b/doc/examples/plot_matching.py @@ -27,7 +27,8 @@ from matplotlib import pyplot as plt from skimage import data from skimage.util import img_as_float -from skimage.feature import corner_harris, corner_subpix, corner_peaks +from skimage.feature import (corner_harris, corner_subpix, corner_peaks, + plot_matches) from skimage.transform import warp, AffineTransform from skimage.exposure import rescale_intensity from skimage.color import rgb2gray @@ -117,28 +118,21 @@ print(tform.scale, tform.translation, tform.rotation) print(model.scale, model.translation, model.rotation) print(model_robust.scale, model_robust.translation, model_robust.rotation) - -# visualize correspondences -img_combined = np.concatenate((img_orig_gray, img_warped_gray), axis=1) - +# visualize correspondence fig, ax = plt.subplots(nrows=2, ncols=1) + plt.gray() -ax[0].imshow(img_combined, interpolation='nearest') +inlier_idxs = np.nonzero(inliers) +plot_matches(ax[0], img_orig_gray, img_warped_gray, src, dst, + inlier_idxs, inlier_idxs, matches_color='b') ax[0].axis('off') -ax[0].axis((0, 400, 200, 0)) ax[0].set_title('Correct correspondences') -ax[1].imshow(img_combined, interpolation='nearest') + +outlier_idxs = np.nonzero(outliers) +plot_matches(ax[1], img_orig_gray, img_warped_gray, src, dst, + outlier_idxs, outlier_idxs, matches_color='r') ax[1].axis('off') -ax[1].axis((0, 400, 200, 0)) ax[1].set_title('Faulty correspondences') - -for ax_idx, (m, color) in enumerate(((inliers, 'g'), (outliers, 'r'))): - ax[ax_idx].plot((src[m, 1], dst[m, 1] + 200), (src[m, 0], dst[m, 0]), '-', - color=color) - ax[ax_idx].plot(src[m, 1], src[m, 0], '.', markersize=10, color=color) - ax[ax_idx].plot(dst[m, 1] + 200, dst[m, 0], '.', markersize=10, - color=color) - plt.show() diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 2d33261f..a27ab1ba 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -36,7 +36,6 @@ class DescriptorExtractor(object): def plot_matches(ax, image1, image2, keypoints1, keypoints2, indices1, indices2, keypoints_color='k', matches_color=None, only_matches=False): - """Plot matched features. Parameters @@ -70,6 +69,9 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, image1 = img_as_float(image1) image2 = img_as_float(image2) + indices1 = np.squeeze(indices1) + indices2 = np.squeeze(indices2) + new_shape1 = image1.shape new_shape2 = image2.shape From 0559105f83fb28482f66e9c8f696e0d0059c646c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 14:54:31 +0100 Subject: [PATCH 120/145] Remove prints in test function --- skimage/feature/tests/test_match.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index 3b17ed4d..a028b585 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -48,9 +48,6 @@ def test_binary_descriptors_lena_rotation_crosscheck_false(): m1, m2 = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=False) - print m1 - print m2 - expected_mask1 = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, From d00b851204726a5a14dd9a3fe9b0623bea9d275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 15:11:56 +0100 Subject: [PATCH 121/145] Fix wrong parameter name in doc string --- skimage/feature/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index a27ab1ba..b8afed57 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -137,7 +137,7 @@ def _mask_border_keypoints(image_shape, keypoints, distance): ---------- image_shape : (2, ) array_like Shape of the image as ``(rows, cols)``. - coords : (N, 2) array + keypoints : (N, 2) array Keypoint coordinates as ``(rows, cols)``. distance : int Image border distance. From 23f94996e0bd47f0c2e0bcc7f011402d07dfe360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 18:07:14 +0100 Subject: [PATCH 122/145] Convert shape to list to allow for in place modification --- skimage/feature/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index b8afed57..7e31dace 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -72,8 +72,8 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, indices1 = np.squeeze(indices1) indices2 = np.squeeze(indices2) - new_shape1 = image1.shape - new_shape2 = image2.shape + new_shape1 = list(image1.shape) + new_shape2 = list(image2.shape) if image1.shape[0] < image2.shape[0]: new_shape1[0] = image2.shape[0] From 0b34d0147223403ad7eb0505bbaf883ec007cb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 18:07:34 +0100 Subject: [PATCH 123/145] Add example script for BRIEF --- doc/examples/plot_brief.py | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 doc/examples/plot_brief.py diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py new file mode 100644 index 00000000..390da1e5 --- /dev/null +++ b/doc/examples/plot_brief.py @@ -0,0 +1,62 @@ +""" +======================= +BRIEF binary descriptor +======================= + +This example demonstrates the BRIEF binary description algorithm. + +The descriptor consists of relatively few bits and can be computed using +a set of intensity difference tests. The short binary descriptor results +in low memory footprint and very efficient matching based on the Hamming +distance metric. + +However, BRIEF does not provide rotation-invariance and scale scale-invariance +can be achieved by detecting and extracting features at different scales. + +The ORB feature detection and binary description algorithm is an extension to +the BRIEF method and provides rotation and scale-invariance, see +`skimage.feature.ORB`. + +""" +import numpy as np +from skimage import data +from skimage import transform as tf +from skimage.feature import (match_descriptors, corner_peaks, corner_harris, + plot_matches, BRIEF) +from skimage.color import rgb2gray +from skimage import img_as_float +import matplotlib.pyplot as plt + + +img1 = rgb2gray(data.lena()) +tform = tf.AffineTransform(scale=(1.2, 1.2), translation=(0, -100)) +img2 = tf.warp(img1, tform) +img3 = tf.rotate(img1, 25) + +keypoints1 = corner_peaks(corner_harris(img1), min_distance=5) +keypoints2 = corner_peaks(corner_harris(img2), min_distance=5) +keypoints3 = corner_peaks(corner_harris(img3), min_distance=5) + +extractor = BRIEF() + +descriptors1, mask1 = extractor.extract(img1, keypoints1) +descriptors2, mask2 = extractor.extract(img2, keypoints2) +descriptors3, mask3 = extractor.extract(img3, keypoints3) + +keypoints1 = keypoints1[mask1] +keypoints2 = keypoints2[mask2] +keypoints3 = keypoints3[mask3] + +fig, ax = plt.subplots(nrows=2, ncols=1) + +plt.gray() + +idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) +plot_matches(ax[0], img1, img2, keypoints1, keypoints2, idxs1, idxs2) +ax[0].axis('off') + +idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) +plot_matches(ax[1], img1, img3, keypoints1, keypoints3, idxs1, idxs3) +ax[1].axis('off') + +plt.show() From 9871ed704fb277c53af846dad6564f993337d292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 18:25:56 +0100 Subject: [PATCH 124/145] Add example script for CenSurE --- doc/examples/plot_brief.py | 2 -- doc/examples/plot_censure.py | 41 ++++++++++++++++++++++++++++++++++++ doc/examples/plot_orb.py | 2 -- 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 doc/examples/plot_censure.py diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index 390da1e5..526c4acd 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -18,13 +18,11 @@ the BRIEF method and provides rotation and scale-invariance, see `skimage.feature.ORB`. """ -import numpy as np from skimage import data from skimage import transform as tf from skimage.feature import (match_descriptors, corner_peaks, corner_harris, plot_matches, BRIEF) from skimage.color import rgb2gray -from skimage import img_as_float import matplotlib.pyplot as plt diff --git a/doc/examples/plot_censure.py b/doc/examples/plot_censure.py new file mode 100644 index 00000000..468b07d2 --- /dev/null +++ b/doc/examples/plot_censure.py @@ -0,0 +1,41 @@ +""" +======================== +CenSurE feature detector +======================== + +The CenSurE feature detector is a scale-invariant center-surround detectors +(CenSurE) that claims to outperform other detectors and is capable of real-time +implementation. + +""" +from skimage import data +from skimage import transform as tf +from skimage.feature import CenSurE +from skimage.color import rgb2gray +import matplotlib.pyplot as plt + + +img1 = rgb2gray(data.lena()) +tform = tf.AffineTransform(scale=(1.5, 1.5), rotation=0.5, + translation=(150, -200)) +img2 = tf.warp(img1, tform) + +detector = CenSurE() +keypoints1, scales1 = detector.detect(img1) +keypoints2, scales2 = detector.detect(img2) + +fig, ax = plt.subplots(nrows=1, ncols=2) + +plt.gray() + +ax[0].imshow(img1) +ax[0].axis('off') +ax[0].scatter(keypoints1[:, 1], keypoints1[:, 0], 2 ** scales1, + facecolors='none', edgecolors='r') + +ax[1].imshow(img2) +ax[1].axis('off') +ax[1].scatter(keypoints2[:, 1], keypoints2[:, 0], 2 ** scales2, + facecolors='none', edgecolors='r') + +plt.show() diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index d8b5cfbf..534c4634 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -12,13 +12,11 @@ allows to employ the very efficient Hamming distance metric for matching and is thus preferred for real-time applications. """ -import numpy as np from skimage import data from skimage import transform as tf from skimage.feature import (match_descriptors, corner_harris, corner_peaks, ORB, plot_matches) from skimage.color import rgb2gray -from skimage import img_as_float import matplotlib.pyplot as plt From d609eb1a80b44d70a5baf37cb1333d6e9bbb9167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 30 Nov 2013 22:21:12 +0100 Subject: [PATCH 125/145] Attempt to fix Python 3 error --- skimage/feature/orb.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index ac9631db..7512054a 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -110,6 +110,11 @@ class ORB(FeatureDetector, DescriptorExtractor): self.fast_threshold) keypoints = corner_peaks(fast_response, min_distance=1) + if len(keypoints) == 0: + return (np.zeros((0, 2), dtype=np.double), + np.zeros((0, ), dtype=np.double), + np.zeros((0, ), dtype=np.double)) + mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=16) keypoints = keypoints[mask] @@ -293,6 +298,12 @@ class ORB(FeatureDetector, DescriptorExtractor): keypoints, orientations, responses = \ self._detect_octave(octave_image) + if len(keypoints) == 0: + keypoints_list.append(keypoints) + responses_list.append(responses) + descriptors_list.append(np.zeros((0, 256), dtype=np.bool)) + continue + descriptors, mask = self._extract_octave(octave_image, keypoints, orientations) From cba85c2948122ef2111b5f6545c5d2ee1434115a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 10:58:15 +0100 Subject: [PATCH 126/145] Add tests for plot_matches --- skimage/feature/tests/test_util.py | 32 +++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 6f3f78e1..31ca826a 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -1,8 +1,10 @@ import numpy as np +import matplotlib.pyplot as plt from numpy.testing import assert_equal, assert_raises + from skimage.feature.util import (FeatureDetector, DescriptorExtractor, _prepare_grayscale_input_2D, - _mask_border_keypoints) + _mask_border_keypoints, plot_matches) def test_feature_detector(): @@ -37,6 +39,34 @@ def test_mask_border_keypoints(): [0, 0, 0, 0, 1]) +def test_plot_matches(): + fig, ax = plt.subplots(nrows=1, ncols=1) + + shapes = (((10, 10), (10, 10)), + ((10, 10), (12, 10)), + ((10, 10), (10, 12)), + ((10, 10), (12, 12)), + ((12, 10), (10, 10)), + ((10, 12), (10, 10)), + ((12, 12), (10, 10))) + + keypoints1 = 10 * np.random.rand(10, 2) + keypoints2 = 10 * np.random.rand(10, 2) + idxs1 = np.random.randint(10, size=10) + idxs2 = np.random.randint(10, size=10) + + for shape1, shape2 in shapes: + img1 = np.zeros(shape1) + img2 = np.zeros(shape2) + plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2) + plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + only_matches=True) + plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + keypoints_color='r') + plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + matches_color='r') + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From e0e4f697de8fc7f32ef3e998e18002f7543a0261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 10:59:21 +0100 Subject: [PATCH 127/145] Fix parameter list in doc string of plot_matches --- skimage/feature/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 7e31dace..6d824ef3 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -46,12 +46,10 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, First grayscale or color image. image2 : (N, M [, 3]) array Second grayscale or color image. - keypoints : (K1, 2) array + keypoints1 : (K1, 2) array First keypoint coordinates as ``(row, col)``. - keypoints : (K2, 2) array + keypoints2 : (K2, 2) array Second keypoint coordinates as ``(row, col)``. - keypoints : (K1, 2) array - Keypoint coordinates as ``(row, col)``. indices1 : (Q, ) array Indices of corresponding matches for first set of keypoints. indices2 : (Q, ) array From ed33dbad6e5350ee4608f368a9f9362112e0a6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 11:14:18 +0100 Subject: [PATCH 128/145] Combine match indices in one array --- doc/examples/plot_brief.py | 9 +++--- doc/examples/plot_orb.py | 11 +++---- skimage/feature/brief.py | 13 +++++--- skimage/feature/match.py | 10 +++--- skimage/feature/orb.py | 14 ++++++--- skimage/feature/tests/test_match.py | 47 ++++++++++++++--------------- skimage/feature/tests/test_util.py | 9 +++--- skimage/feature/util.py | 22 ++++++-------- 8 files changed, 69 insertions(+), 66 deletions(-) diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index 526c4acd..b4e7d8cd 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -45,16 +45,17 @@ keypoints1 = keypoints1[mask1] keypoints2 = keypoints2[mask2] keypoints3 = keypoints3[mask3] +matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) +matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True) + fig, ax = plt.subplots(nrows=2, ncols=1) plt.gray() -idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) -plot_matches(ax[0], img1, img2, keypoints1, keypoints2, idxs1, idxs2) +plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12) ax[0].axis('off') -idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) -plot_matches(ax[1], img1, img3, keypoints1, keypoints3, idxs1, idxs3) +plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13) ax[1].axis('off') plt.show() diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index 534c4634..a3c6bf10 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -31,18 +31,17 @@ keypoints1, descriptors1 = descriptor_extractor.detect_and_extract(img1) keypoints2, descriptors2 = descriptor_extractor.detect_and_extract(img2) keypoints3, descriptors3 = descriptor_extractor.detect_and_extract(img3) +matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) +matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True) + fig, ax = plt.subplots(nrows=2, ncols=1) plt.gray() -idxs1, idxs2 = match_descriptors(descriptors1, descriptors2, cross_check=True) -plot_matches(ax[0], img1, img2, keypoints1, keypoints2, - idxs1, idxs2) +plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12) ax[0].axis('off') -idxs1, idxs3 = match_descriptors(descriptors1, descriptors3, cross_check=True) -plot_matches(ax[1], img1, img3, keypoints1, keypoints3, - idxs1, idxs3) +plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13) ax[1].axis('off') plt.show() diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index cac7f347..45c9fa11 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -77,15 +77,18 @@ class BRIEF(DescriptorExtractor): >>> extractor = BRIEF(patch_size=5) >>> descs1, _ = extractor.extract(square1, keypoints1) >>> descs2, _ = extractor.extract(square2, keypoints2) - >>> idxs1, idxs2 = match_descriptors(descs1, descs2) - >>> idxs1, idxs2 - (array([0, 1, 2, 3]), array([0, 1, 2, 3])) - >>> keypoints1[idxs1] + >>> matches = match_descriptors(descs1, descs2) + >>> matches + array([[0, 0], + [1, 1], + [2, 2], + [3, 3]]) + >>> keypoints1[matches[:, 0]] array([[2, 2], [2, 5], [5, 2], [5, 5]]) - >>> keypoints2[idxs2] + >>> keypoints2[matches[:, 1]] array([[2, 2], [2, 6], [6, 2], diff --git a/skimage/feature/match.py b/skimage/feature/match.py index c6f23f6b..ccd8c035 100644 --- a/skimage/feature/match.py +++ b/skimage/feature/match.py @@ -35,10 +35,10 @@ def match_descriptors(descriptors1, descriptors2, metric=None, p=2, Returns ------- - indices1 : (Q, ) array - Indices of corresponding matches for first set of descriptors. - indices2 : (Q, ) array - Indices of corresponding matches for second set of descriptors. + matches : (Q, 2) array + Indices of corresponding matches in first and second set of + descriptors, where ``matches[:, 0]`` denote the indices in the first + and ``matches[:, 1]`` the indices in the second set of descriptors. """ @@ -62,4 +62,4 @@ def match_descriptors(descriptors1, descriptors2, metric=None, p=2, indices1 = indices1[mask] indices2 = indices2[mask] - return indices1, indices2 + return np.column_stack((indices1, indices2)) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index 7512054a..a743962b 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -72,16 +72,20 @@ class ORB(FeatureDetector, DescriptorExtractor): >>> detector_extractor = ORB(n_keypoints=5) >>> keypoints1, descriptors1 = detector_extractor.detect_and_extract(img1) >>> keypoints2, descriptors2 = detector_extractor.detect_and_extract(img2) - >>> idxs1, idxs2 = match_descriptors(descriptors1, descriptors2) - >>> idxs1, idxs2 - (array([0, 1, 2, 3, 4]), array([0, 1, 2, 3, 4])) - >>> keypoints1[idxs1] + >>> matches = match_descriptors(descriptors1, descriptors2) + >>> matches + array([[0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4]]) + >>> keypoints1[matches[:, 0]] array([[ 42., 40.], [ 47., 58.], [ 44., 40.], [ 59., 42.], [ 45., 44.]]) - >>> keypoints2[idxs2] + >>> keypoints2[matches[:, 1]] array([[ 55., 53.], [ 60., 71.], [ 57., 53.], diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index a028b585..e8b7c1ad 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -9,21 +9,20 @@ from skimage.feature import (BRIEF, match_descriptors, def test_binary_descriptors_unequal_descriptor_sizes_error(): """Sizes of descriptors of keypoints to be matched should be equal.""" - des1 = np.array([[True, True, False, True], + descs1 = np.array([[True, True, False, True], [False, True, False, True]]) - des2 = np.array([[True, False, False, True, False], + descs2 = np.array([[True, False, False, True, False], [False, True, True, True, False]]) - assert_raises(ValueError, match_descriptors, des1, des2) + assert_raises(ValueError, match_descriptors, descs1, descs2) def test_binary_descriptors(): - des1 = np.array([[True, True, False, True, True], + descs1 = np.array([[True, True, False, True, True], [False, True, False, True, True]]) - des2 = np.array([[True, False, False, True, False], + descs2 = np.array([[True, False, False, True, False], [False, False, True, True, True]]) - indices1, indices2 = match_descriptors(des1, des2) - assert_equal(indices1, [0, 1]) - assert_equal(indices2, [0, 1]) + matches = match_descriptors(descs1, descs2) + assert_equal(matches, [[0, 0], [1, 1]]) def test_binary_descriptors_lena_rotation_crosscheck_false(): @@ -45,19 +44,19 @@ def test_binary_descriptors_lena_rotation_crosscheck_false(): descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) keypoints2 = keypoints1[mask2] - m1, m2 = match_descriptors(descriptors1, descriptors2, threshold=0.13, + matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=False) - expected_mask1 = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46]) - expected_mask2 = np.array([33, 0, 35, 7, 1, 35, 3, 2, 3, 6, 4, 9, - 11, 10, 28, 7, 8, 5, 31, 14, 13, 15, 21, 16, - 16, 13, 17, 18, 19, 21, 22, 23, 0, 24, 1, 24, - 23, 0, 26, 27, 25, 34, 28, 14, 29, 30, 21]) - assert_equal(m1, expected_mask1) - assert_equal(m2, expected_mask2) + exp_matches1 = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46]) + exp_matches2 = np.array([33, 0, 35, 7, 1, 35, 3, 2, 3, 6, 4, 9, + 11, 10, 28, 7, 8, 5, 31, 14, 13, 15, 21, 16, + 16, 13, 17, 18, 19, 21, 22, 23, 0, 24, 1, 24, + 23, 0, 26, 27, 25, 34, 28, 14, 29, 30, 21]) + assert_equal(matches[:, 0], exp_matches1) + assert_equal(matches[:, 1], exp_matches2) def test_binary_descriptors_lena_rotation_crosscheck_true(): @@ -79,17 +78,17 @@ def test_binary_descriptors_lena_rotation_crosscheck_true(): descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) keypoints2 = keypoints1[mask2] - m1, m2 = match_descriptors(descriptors1, descriptors2, threshold=0.13, + matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=True) - expected_mask1 = np.array([ 0, 1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 15, + exp_matches1 = np.array([ 0, 1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 15, 16, 17, 19, 20, 21, 24, 26, 27, 28, 29, 30, 35, 36, 38, 39, 40, 42, 44, 45]) - expected_mask2 = np.array([33, 0, 35, 1, 3, 2, 6, 4, 9, 11, 10, 7, + exp_matches2 = np.array([33, 0, 35, 1, 3, 2, 6, 4, 9, 11, 10, 7, 8, 5, 14, 13, 15, 16, 17, 18, 19, 21, 22, 24, 23, 26, 27, 25, 28, 29, 30]) - assert_equal(m1, expected_mask1) - assert_equal(m2, expected_mask2) + assert_equal(matches[:, 0], exp_matches1) + assert_equal(matches[:, 1], exp_matches2) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 31ca826a..2b601b94 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -54,16 +54,17 @@ def test_plot_matches(): keypoints2 = 10 * np.random.rand(10, 2) idxs1 = np.random.randint(10, size=10) idxs2 = np.random.randint(10, size=10) + matches = np.column_stack((idxs1, idxs2)) for shape1, shape2 in shapes: img1 = np.zeros(shape1) img2 = np.zeros(shape2) - plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2) - plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + plot_matches(ax, img1, img2, keypoints1, keypoints2, matches) + plot_matches(ax, img1, img2, keypoints1, keypoints2, matches, only_matches=True) - plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + plot_matches(ax, img1, img2, keypoints1, keypoints2, matches, keypoints_color='r') - plot_matches(ax, img1, img2, keypoints1, keypoints2, idxs1, idxs2, + plot_matches(ax, img1, img2, keypoints1, keypoints2, matches, matches_color='r') diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 6d824ef3..c3970766 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -33,9 +33,8 @@ class DescriptorExtractor(object): raise NotImplementedError() -def plot_matches(ax, image1, image2, keypoints1, keypoints2, - indices1, indices2, keypoints_color='k', matches_color=None, - only_matches=False): +def plot_matches(ax, image1, image2, keypoints1, keypoints2, matches, + keypoints_color='k', matches_color=None, only_matches=False): """Plot matched features. Parameters @@ -50,10 +49,10 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, First keypoint coordinates as ``(row, col)``. keypoints2 : (K2, 2) array Second keypoint coordinates as ``(row, col)``. - indices1 : (Q, ) array - Indices of corresponding matches for first set of keypoints. - indices2 : (Q, ) array - Indices of corresponding matches for second set of keypoints. + matches : (Q, 2) array + Indices of corresponding matches in first and second set of + descriptors, where ``matches[:, 0]`` denote the indices in the first + and ``matches[:, 1]`` the indices in the second set of descriptors. keypoints_color : matplotlib color Color for keypoint locations. matches_color : matplotlib color @@ -67,9 +66,6 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, image1 = img_as_float(image1) image2 = img_as_float(image2) - indices1 = np.squeeze(indices1) - indices2 = np.squeeze(indices2) - new_shape1 = list(image1.shape) new_shape2 = list(image2.shape) @@ -106,9 +102,9 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, ax.imshow(image) ax.axis((0, 2 * offset[1], offset[0], 0)) - for i in range(len(indices1)): - idx1 = indices1[i] - idx2 = indices2[i] + for i in range(matches.shape[0]): + idx1 = matches[i, 0] + idx2 = matches[i, 1] if matches_color is None: color = np.random.rand(3, 1) From 57016f4db0bb4b11985c8f9b90c60e15a5b513a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 11:15:39 +0100 Subject: [PATCH 129/145] Recover hidden test cases caused by duplicate function names --- skimage/feature/tests/test_corner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 200c9e2a..bdfbb808 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -36,7 +36,7 @@ def test_structure_tensor(): [ 0, 0, 0, 0, 0]])) -def test_structure_tensor(): +def test_hessian_matrix(): square = np.zeros((5, 5)) square[2, 2] = 1 Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) @@ -74,7 +74,7 @@ def test_structure_tensor_eigvals(): [0, 0, 0, 0, 0]])) -def test_structure_tensor_eigvals(): +def test_hessian_matrix_eigvals(): square = np.zeros((5, 5)) square[2, 2] = 1 Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1) From 4406c0f0b43a47cc9a37d65dee3a90bf4cfe06e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 11:31:40 +0100 Subject: [PATCH 130/145] Speedup corner orientations --- skimage/feature/corner_cy.pyx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 3fd860a2..86ad3c43 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -240,7 +240,8 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1: raise ValueError("Size of mask must be uneven.") - cdef char[:, ::1] cmask = np.ascontiguousarray(mask != 0, dtype=np.uint8) + cdef unsigned char[:, ::1] cmask = np.ascontiguousarray(mask != 0, + dtype=np.uint8) cdef Py_ssize_t i, r, c, r0, c0 cdef Py_ssize_t mrows = mask.shape[0] @@ -251,7 +252,7 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): constant_values=0) cdef double[:] orientations = np.zeros(corners.shape[0], dtype=np.double) cdef double curr_pixel - cdef double m01, m10 + cdef double m01, m10, m01_tmp for i in range(corners.shape[0]): r0 = corners[i, 0] @@ -261,11 +262,13 @@ def corner_orientations(image, Py_ssize_t[:, :] corners, mask): m10 = 0 for r in range(mrows): + m01_tmp = 0 for c in range(mcols): if cmask[r, c]: curr_pixel = cimage[r0 + r, c0 + c] m10 += curr_pixel * (c - mcols2) - m01 += curr_pixel * (r - mrows2) + m01_tmp += curr_pixel + m01 += m01_tmp * (r - mrows2) orientations[i] = atan2(m01, m10) From 23f5ae5bb7c8deb98d08376f94100b19497c46cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 1 Dec 2013 11:34:59 +0100 Subject: [PATCH 131/145] Fix plot_matching example --- doc/examples/plot_matching.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/examples/plot_matching.py b/doc/examples/plot_matching.py index 9fa0b41c..5812fa37 100644 --- a/doc/examples/plot_matching.py +++ b/doc/examples/plot_matching.py @@ -123,15 +123,15 @@ fig, ax = plt.subplots(nrows=2, ncols=1) plt.gray() -inlier_idxs = np.nonzero(inliers) +inlier_idxs = np.nonzero(inliers)[0] plot_matches(ax[0], img_orig_gray, img_warped_gray, src, dst, - inlier_idxs, inlier_idxs, matches_color='b') + np.column_stack((inlier_idxs, inlier_idxs)), matches_color='b') ax[0].axis('off') ax[0].set_title('Correct correspondences') -outlier_idxs = np.nonzero(outliers) +outlier_idxs = np.nonzero(outliers)[0] plot_matches(ax[1], img_orig_gray, img_warped_gray, src, dst, - outlier_idxs, outlier_idxs, matches_color='r') + np.column_stack((outlier_idxs, outlier_idxs)), matches_color='r') ax[1].axis('off') ax[1].set_title('Faulty correspondences') From 121fa8f0dbb1dde9a9e83b404f74d6e23f81399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 2 Dec 2013 10:12:49 +0100 Subject: [PATCH 132/145] Fix description --- doc/examples/plot_brief.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index b4e7d8cd..e5ffaaf2 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -10,8 +10,8 @@ a set of intensity difference tests. The short binary descriptor results in low memory footprint and very efficient matching based on the Hamming distance metric. -However, BRIEF does not provide rotation-invariance and scale scale-invariance -can be achieved by detecting and extracting features at different scales. +However, BRIEF does not provide rotation-invariance and scale-invariance can be +achieved by detecting and extracting features at different scales. The ORB feature detection and binary description algorithm is an extension to the BRIEF method and provides rotation and scale-invariance, see From a5e2b99e722bb92bac6a5fd0ac996bd793162403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 2 Dec 2013 10:13:23 +0100 Subject: [PATCH 133/145] Fix typo --- doc/examples/plot_censure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/plot_censure.py b/doc/examples/plot_censure.py index 468b07d2..15cbbd13 100644 --- a/doc/examples/plot_censure.py +++ b/doc/examples/plot_censure.py @@ -3,7 +3,7 @@ CenSurE feature detector ======================== -The CenSurE feature detector is a scale-invariant center-surround detectors +The CenSurE feature detector is a scale-invariant center-surround detector (CenSurE) that claims to outperform other detectors and is capable of real-time implementation. From f6ba14b49499bdb2cdca87c489356bdadbac103c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 2 Dec 2013 11:13:44 +0100 Subject: [PATCH 134/145] Add missing optional description for kwargs --- skimage/feature/brief.py | 10 +++++----- skimage/feature/censure.py | 10 +++++----- skimage/feature/orb.py | 12 ++++++------ skimage/feature/util.py | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index 45c9fa11..b7744b4f 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -24,22 +24,22 @@ class BRIEF(DescriptorExtractor): Parameters ---------- - descriptor_size : int + descriptor_size : int, optional Size of BRIEF descriptor for each keypoint. Sizes 128, 256 and 512 recommended by the authors. Default is 256. - patch_size : int + patch_size : int, optional Length of the two dimensional square patch sampling region around the keypoints. Default is 49. - mode : {'normal', 'uniform'} + mode : {'normal', 'uniform'}, optional Probability distribution for sampling location of decision pixel-pairs around keypoints. - sample_seed : int + sample_seed : int, optional 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. - sigma : float + sigma : float, optional 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. diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index f201e12f..991875df 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -115,15 +115,15 @@ class CenSurE(FeatureDetector): """CenSurE keypoint detector. - min_scale : int + min_scale : int, optional Minimum scale to extract keypoints from. - max_scale : int + max_scale : int, optional Maximum scale to extract keypoints from. The keypoints will be extracted from all the scales except the first and the last i.e. from the scales in the range [min_scale + 1, max_scale - 1]. The filter sizes for different scales is such that the two adjacent scales comprise of an octave. - mode : {'DoB', 'Octagon', 'STAR'} + mode : {'DoB', 'Octagon', 'STAR'}, optional Type of bi-level filter used to get the scales of the input image. Possible values are 'DoB', 'Octagon' and 'STAR'. The three modes represent the shape of the bi-level filters i.e. box(square), octagon @@ -132,10 +132,10 @@ class CenSurE(FeatureDetector): weights being uniformly negative in both the inner octagon while uniformly positive in the difference region. Use STAR and Octagon for better features and DoB for better performance. - non_max_threshold : float + non_max_threshold : float, optional Threshold value used to suppress maximas and minimas with a weak magnitude response obtained after Non-Maximal Suppression. - line_threshold : float + line_threshold : float, optional Threshold for rejecting interest points which have ratio of principal curvatures greater than this value. diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index a743962b..e7ab22e1 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -25,32 +25,32 @@ class ORB(FeatureDetector, DescriptorExtractor): Parameters ---------- - n_keypoints : int + n_keypoints : int, optional Number of keypoints to be returned. The function will return the best ``n_keypoints`` according to the Harris corner response if more than ``n_keypoints`` are detected. If not, then all the detected keypoints are returned. - fast_n : int + fast_n : int, optional The ``n`` parameter in ``feature.corner_fast``. Minimum number of consecutive pixels out of 16 pixels on the circle that should all be either brighter or darker w.r.t test-pixel. A point c on the circle is darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner detector. - fast_threshold : float + fast_threshold : float, optional The ``threshold`` parameter in ``feature.corner_fast``. Threshold used to decide whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. - harris_k : float + harris_k : float, optional The ``k`` parameter in ``feature.corner_harris``. Sensitivity factor to separate corners from edges, typically in range ``[0, 0.2]``. Small values of k result in detection of sharp corners. - downscale : float + downscale : float, optional Downscale factor for the image pyramid. Default value 1.2 is chosen so that there are more dense scales which enable robust scale invariance for a subsequent feature description. - n_scales : int + n_scales : int, optional Maximum number of scales from the bottom of the image pyramid to extract the features from. diff --git a/skimage/feature/util.py b/skimage/feature/util.py index c3970766..b5faa565 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -53,12 +53,12 @@ def plot_matches(ax, image1, image2, keypoints1, keypoints2, matches, Indices of corresponding matches in first and second set of descriptors, where ``matches[:, 0]`` denote the indices in the first and ``matches[:, 1]`` the indices in the second set of descriptors. - keypoints_color : matplotlib color + keypoints_color : matplotlib color, optional Color for keypoint locations. - matches_color : matplotlib color + matches_color : matplotlib color, optional Color for lines which connect keypoint matches. By default the color is chosen randomly. - only_matches : bool + only_matches : bool, optional Whether to only plot matches and not plot the keypoint locations. """ From 336f0ca26605bb74d60838c3f82c0be7d6e6b410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 19 Jan 2014 09:38:36 -0500 Subject: [PATCH 135/145] Save feature information in attributes for consistency --- doc/examples/plot_brief.py | 16 +++-- doc/examples/plot_censure.py | 14 +++-- doc/examples/plot_orb.py | 15 ++++- skimage/feature/brief.py | 38 +++++------ skimage/feature/censure.py | 18 +++--- skimage/feature/orb.py | 90 +++++++++++++++++---------- skimage/feature/tests/test_brief.py | 14 ++--- skimage/feature/tests/test_censure.py | 20 +++--- skimage/feature/tests/test_orb.py | 54 ++++++++-------- skimage/feature/util.py | 6 ++ 10 files changed, 171 insertions(+), 114 deletions(-) diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index e5ffaaf2..2ef473bb 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -37,13 +37,17 @@ keypoints3 = corner_peaks(corner_harris(img3), min_distance=5) extractor = BRIEF() -descriptors1, mask1 = extractor.extract(img1, keypoints1) -descriptors2, mask2 = extractor.extract(img2, keypoints2) -descriptors3, mask3 = extractor.extract(img3, keypoints3) +extractor.extract(img1, keypoints1) +keypoints1 = keypoints1[extractor.mask_] +descriptors1 = extractor.descriptors_ -keypoints1 = keypoints1[mask1] -keypoints2 = keypoints2[mask2] -keypoints3 = keypoints3[mask3] +extractor.extract(img2, keypoints2) +keypoints2 = keypoints2[extractor.mask_] +descriptors2 = extractor.descriptors_ + +extractor.extract(img3, keypoints3) +keypoints3 = keypoints3[extractor.mask_] +descriptors3 = extractor.descriptors_ matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True) diff --git a/doc/examples/plot_censure.py b/doc/examples/plot_censure.py index 15cbbd13..06afb8e8 100644 --- a/doc/examples/plot_censure.py +++ b/doc/examples/plot_censure.py @@ -21,21 +21,23 @@ tform = tf.AffineTransform(scale=(1.5, 1.5), rotation=0.5, img2 = tf.warp(img1, tform) detector = CenSurE() -keypoints1, scales1 = detector.detect(img1) -keypoints2, scales2 = detector.detect(img2) fig, ax = plt.subplots(nrows=1, ncols=2) plt.gray() +detector.detect(img1) + ax[0].imshow(img1) ax[0].axis('off') -ax[0].scatter(keypoints1[:, 1], keypoints1[:, 0], 2 ** scales1, - facecolors='none', edgecolors='r') +ax[0].scatter(detector.keypoints_[:, 1], detector.keypoints_[:, 0], + 2 ** detector.scales_, facecolors='none', edgecolors='r') + +detector.detect(img2) ax[1].imshow(img2) ax[1].axis('off') -ax[1].scatter(keypoints2[:, 1], keypoints2[:, 0], 2 ** scales2, - facecolors='none', edgecolors='r') +ax[1].scatter(detector.keypoints_[:, 1], detector.keypoints_[:, 0], + 2 ** detector.scales_, facecolors='none', edgecolors='r') plt.show() diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index a3c6bf10..6576fbb7 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -27,9 +27,18 @@ tform = tf.AffineTransform(scale=(1.3, 1.1), rotation=0.5, img3 = tf.warp(img1, tform) descriptor_extractor = ORB(n_keypoints=200) -keypoints1, descriptors1 = descriptor_extractor.detect_and_extract(img1) -keypoints2, descriptors2 = descriptor_extractor.detect_and_extract(img2) -keypoints3, descriptors3 = descriptor_extractor.detect_and_extract(img3) + +descriptor_extractor.detect_and_extract(img1) +keypoints1 = descriptor_extractor.keypoints_ +descriptors1 = descriptor_extractor.descriptors_ + +descriptor_extractor.detect_and_extract(img2) +keypoints2 = descriptor_extractor.keypoints_ +descriptors2 = descriptor_extractor.descriptors_ + +descriptor_extractor.detect_and_extract(img3) +keypoints3 = descriptor_extractor.keypoints_ +descriptors3 = descriptor_extractor.descriptors_ matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index b7744b4f..b697ec3a 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -74,21 +74,23 @@ class BRIEF(DescriptorExtractor): [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32) >>> 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 = match_descriptors(descs1, descs2) + >>> extractor1 = BRIEF(patch_size=5) + >>> extractor2 = BRIEF(patch_size=5) + >>> extractor1.extract(square1, keypoints1) + >>> extractor2.extract(square2, keypoints2) + >>> matches = match_descriptors(extractor1.descriptors_, + ... extractor2.descriptors_) >>> matches array([[0, 0], [1, 1], [2, 2], [3, 3]]) - >>> keypoints1[matches[:, 0]] + >>> extractor1.keypoints_[matches[:, 0]] array([[2, 2], [2, 5], [5, 2], [5, 5]]) - >>> keypoints2[matches[:, 1]] + >>> extractor2.keypoints_[matches[:, 1]] array([[2, 2], [2, 6], [6, 2], @@ -119,15 +121,15 @@ class BRIEF(DescriptorExtractor): keypoints : (N, 2) array Keypoint coordinates as ``(row, col)``. - Returns - ------- - descriptors : (Q, `descriptor_size`) array of dtype bool + Attributes + ---------- + 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_ : (N, ) array of dtype bool Mask indicating whether a keypoint has been filtered out (``False``) or is described in the `descriptors` array (``True``). @@ -163,14 +165,14 @@ class BRIEF(DescriptorExtractor): # Removing keypoints that are within (patch_size / 2) distance from the # image border - mask = _mask_border_keypoints(image.shape, keypoints, patch_size // 2) + self.mask_ = _mask_border_keypoints(image.shape, keypoints, + patch_size // 2) - keypoints = np.array(keypoints[mask, :], dtype=np.intp, order='C', - copy=False) + keypoints = np.array(keypoints[self.mask_, :], dtype=np.intp, + order='C', copy=False) - descriptors = np.zeros((keypoints.shape[0], desc_size), - dtype=bool, order='C') + self.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 + _brief_loop(image, self.descriptors_.view(np.uint8), keypoints, + pos1, pos2) diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index 991875df..f31c5445 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -157,8 +157,9 @@ class CenSurE(FeatureDetector): >>> from skimage.color import rgb2gray >>> from skimage.feature import CenSurE >>> img = rgb2gray(lena()[100:300, 100:300]) - >>> keypoints, scales = CenSurE().detect(img) - >>> keypoints + >>> censure = CenSurE() + >>> censure.detect(img) + >>> censure.keypoints_ array([[ 71, 148], [ 77, 186], [ 78, 189], @@ -174,7 +175,7 @@ class CenSurE(FeatureDetector): [171, 29], [179, 20], [194, 65]]) - >>> scales + >>> censure.scales_ array([2, 4, 2, 3, 4, 2, 2, 3, 4, 6, 3, 2, 3, 4, 2]) """ @@ -204,8 +205,8 @@ class CenSurE(FeatureDetector): image : 2D ndarray Input image. - Returns - ------- + Attributes + ---------- keypoints : (N, 2) array Keypoint coordinates as ``(row, col)``. scales : (N, ) array @@ -257,7 +258,9 @@ class CenSurE(FeatureDetector): scales = scales + self.min_scale + 1 if self.mode == 'dob': - return keypoints, scales + self.keypoints_ = keypoints + self.scales_ = scales + return cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) @@ -276,4 +279,5 @@ class CenSurE(FeatureDetector): _mask_border_keypoints(image.shape, keypoints, c) & (scales == i)) - return keypoints[cumulative_mask], scales[cumulative_mask] + self.keypoints_ = keypoints[cumulative_mask] + self.scales_ = scales[cumulative_mask] diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index e7ab22e1..b0010d77 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -69,23 +69,25 @@ class ORB(FeatureDetector, DescriptorExtractor): >>> square = np.random.rand(20, 20) >>> img1[40:60, 40:60] = square >>> img2[53:73, 53:73] = square - >>> detector_extractor = ORB(n_keypoints=5) - >>> keypoints1, descriptors1 = detector_extractor.detect_and_extract(img1) - >>> keypoints2, descriptors2 = detector_extractor.detect_and_extract(img2) - >>> matches = match_descriptors(descriptors1, descriptors2) + >>> detector_extractor1 = ORB(n_keypoints=5) + >>> detector_extractor2 = ORB(n_keypoints=5) + >>> detector_extractor1.detect_and_extract(img1) + >>> detector_extractor2.detect_and_extract(img2) + >>> matches = match_descriptors(detector_extractor1.descriptors_, + ... detector_extractor2.descriptors_) >>> matches array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]]) - >>> keypoints1[matches[:, 0]] + >>> detector_extractor1.keypoints_[matches[:, 0]] array([[ 42., 40.], [ 47., 58.], [ 44., 40.], [ 59., 42.], [ 45., 44.]]) - >>> keypoints2[matches[:, 1]] + >>> detector_extractor2.keypoints_[matches[:, 1]] array([[ 55., 53.], [ 60., 71.], [ 57., 53.], @@ -140,15 +142,15 @@ class ORB(FeatureDetector, DescriptorExtractor): image : 2D array Input image. - Returns - ------- - keypoints : (N, 2) array + Attributes + ---------- + keypoints_ : (N, 2) array Keypoint coordinates as ``(row, col)``. - scales : (N, ) array + scales_ : (N, ) array Corresponding scales. - orientations : (N, ) array + orientations_ : (N, ) array Corresponding orientations in radians. - responses : (N, ) array + responses_ : (N, ) array Corresponding Harris corner responses. """ @@ -157,7 +159,7 @@ class ORB(FeatureDetector, DescriptorExtractor): keypoints_list = [] orientations_list = [] - octave_list = [] + scales_list = [] responses_list = [] for octave in range(len(pyramid)): @@ -169,22 +171,27 @@ class ORB(FeatureDetector, DescriptorExtractor): keypoints_list.append(keypoints * self.downscale ** octave) orientations_list.append(orientations) - octave_list.append(self.downscale ** octave + scales_list.append(self.downscale ** octave * np.ones(keypoints.shape[0], dtype=np.intp)) responses_list.append(responses) keypoints = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) - scales = np.hstack(octave_list) + scales = np.hstack(scales_list) responses = np.hstack(responses_list) if keypoints.shape[0] < self.n_keypoints: - return keypoints, scales, orientations, responses + self.keypoints_ = keypoints + self.scales_ = scales + self.orientations_ = orientations + self.responses_ = 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]) + self.keypoints_ = keypoints[best_indices] + self.scales_ = scales[best_indices] + self.orientations_ = orientations[best_indices] + self.responses_ = responses[best_indices] def _extract_octave(self, octave_image, keypoints, orientations): mask = _mask_border_keypoints(octave_image.shape, keypoints, @@ -217,15 +224,15 @@ class ORB(FeatureDetector, DescriptorExtractor): orientations : (N, ) array Corresponding orientations in radians. - Returns - ------- - descriptors : (Q, `descriptor_size`) array of dtype bool + Attributes + ---------- + 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_ : (N, ) array of dtype bool Mask indicating whether a keypoint has been filtered out (``False``) or is described in the `descriptors` array (``True``). @@ -260,10 +267,8 @@ class ORB(FeatureDetector, DescriptorExtractor): descriptors_list.append(descriptors) mask_list.append(mask) - descriptors = np.vstack(descriptors_list).view(np.bool) - mask = np.hstack(mask_list) - - return descriptors, mask + self.descriptors_ = np.vstack(descriptors_list).view(np.bool) + self.mask_ = np.hstack(mask_list) def detect_and_extract(self, image): """Detect oriented FAST keypoints and extract rBRIEF descriptors. @@ -276,11 +281,17 @@ class ORB(FeatureDetector, DescriptorExtractor): image : 2D array Input image. - Returns - ------- - keypoints : (Q, 2) array + Attributes + ---------- + keypoints_ : (N, 2) array Keypoint coordinates as ``(row, col)``. - descriptors : (Q, `descriptor_size`) array of dtype bool + scales_ : (N, ) array + Corresponding scales. + orientations_ : (N, ) array + Corresponding orientations in radians. + responses_ : (N, ) array + Corresponding Harris corner responses. + 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 @@ -293,6 +304,8 @@ class ORB(FeatureDetector, DescriptorExtractor): keypoints_list = [] responses_list = [] + scales_list = [] + orientations_list = [] descriptors_list = [] for octave in range(len(pyramid)): @@ -313,15 +326,28 @@ class ORB(FeatureDetector, DescriptorExtractor): keypoints_list.append(keypoints[mask] * self.downscale ** octave) responses_list.append(responses[mask]) + orientations_list.append(orientations[mask]) + scales_list.append(self.downscale ** octave + * np.ones(keypoints.shape[0], dtype=np.intp)) descriptors_list.append(descriptors) keypoints = np.vstack(keypoints_list) responses = np.hstack(responses_list) + scales = np.hstack(scales_list) + orientations = np.hstack(orientations_list) descriptors = np.vstack(descriptors_list).view(np.bool) if keypoints.shape[0] < self.n_keypoints: - return keypoints, descriptors + self.keypoints_ = keypoints + self.scales_ = scales + self.orientations_ = orientations + self.responses_ = responses + self.descriptors_ = 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] + self.keypoints_ = keypoints[best_indices] + self.scales_ = scales[best_indices] + self.orientations_ = orientations[best_indices] + self.responses_ = responses[best_indices] + self.descriptors_ = descriptors[best_indices] diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 61cde324..e847c967 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -21,7 +21,7 @@ def test_normal_mode(): extractor = BRIEF(descriptor_size=8, sigma=2) - descriptors, mask = extractor.extract(img, keypoints[:8]) + extractor.extract(img, keypoints[:8]) expected = np.array([[ True, False, True, False, True, True, False, False], [False, False, False, False, True, False, False, False], @@ -32,7 +32,7 @@ def test_normal_mode(): [False, True, True, True, False, False, True, False], [False, False, False, False, True, False, False, False]], dtype=bool) - assert_array_equal(descriptors, expected) + assert_array_equal(extractor.descriptors_, expected) def test_uniform_mode(): @@ -43,7 +43,7 @@ def test_uniform_mode(): extractor = BRIEF(descriptor_size=8, sigma=2, mode='uniform') - descriptors, mask = extractor.extract(img, keypoints[:8]) + extractor.extract(img, keypoints[:8]) expected = np.array([[ True, False, True, False, False, True, False, False], [False, True, False, False, True, True, True, True], @@ -54,7 +54,7 @@ def test_uniform_mode(): [False, False, True, True, False, False, True, True], [ True, True, False, False, False, False, False, False]], dtype=bool) - assert_array_equal(descriptors, expected) + assert_array_equal(extractor.descriptors_, expected) def test_unsupported_mode(): @@ -66,10 +66,10 @@ def test_border(): keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]]) extractor = BRIEF(patch_size=41) - descs, mask = extractor.extract(img, keypoints) + extractor.extract(img, keypoints) - assert descs.shape[0] == 3 - assert_array_equal(mask, (False, True, True, True)) + assert extractor.descriptors_.shape[0] == 3 + assert_array_equal(extractor.mask_, (False, True, True, True)) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_censure.py b/skimage/feature/tests/test_censure.py index 611604b6..608290d0 100644 --- a/skimage/feature/tests/test_censure.py +++ b/skimage/feature/tests/test_censure.py @@ -28,7 +28,7 @@ def test_keypoints_censure_moon_image_dob(): """Verify the actual Censure keypoints and their corresponding scale with the expected values for DoB filter.""" detector = CenSurE() - keypoints, scales = detector.detect(img) + detector.detect(img) expected_keypoints = np.array([[ 21, 497], [ 36, 46], [119, 350], @@ -40,8 +40,8 @@ def test_keypoints_censure_moon_image_dob(): [467, 260]]) expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) - assert_array_equal(expected_keypoints, keypoints) - assert_array_equal(expected_scales, scales) + assert_array_equal(expected_keypoints, detector.keypoints_) + assert_array_equal(expected_scales, detector.scales_) def test_keypoints_censure_moon_image_octagon(): @@ -49,7 +49,7 @@ def test_keypoints_censure_moon_image_octagon(): the expected values for Octagon filter.""" detector = CenSurE(mode='octagon') - keypoints, scales = detector.detect(img) + detector.detect(img) expected_keypoints = np.array([[ 21, 496], [ 35, 46], [287, 250], @@ -58,15 +58,15 @@ def test_keypoints_censure_moon_image_octagon(): expected_scales = np.array([3, 4, 2, 2, 2]) - assert_array_equal(expected_keypoints, keypoints) - assert_array_equal(expected_scales, scales) + assert_array_equal(expected_keypoints, detector.keypoints_) + assert_array_equal(expected_scales, detector.scales_) def test_keypoints_censure_moon_image_star(): """Verify the actual Censure keypoints and their corresponding scale with the expected values for STAR filter.""" detector = CenSurE(mode='star') - keypoints, scales = detector.detect(img) + detector.detect(img) expected_keypoints = np.array([[ 21, 497], [ 36, 46], [117, 356], @@ -78,10 +78,10 @@ def test_keypoints_censure_moon_image_star(): [463, 116], [467, 260]]) - expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) + expected_scales = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) - assert_array_equal(expected_keypoints, keypoints) - assert_array_equal(expected_scale, scales) + assert_array_equal(expected_keypoints, detector.keypoints_) + assert_array_equal(expected_scales, detector.scales_) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index 9c2a1d6e..9943aeb3 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -10,7 +10,7 @@ img = rgb2gray(lena()) 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) + detector_extractor.detect(img) exp_rows = np.array([ 435. , 435.6 , 376. , 455. , 434.88, 269. , 375.6 , 310.8 , 413. , 311.04]) @@ -29,22 +29,23 @@ def test_keypoints_orb_desired_no_of_keypoints(): 0.39154173, 0.39084861, 0.39063076, 0.37602487]) - 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) + assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) + assert_almost_equal(exp_scales, detector_extractor.scales_) + assert_almost_equal(exp_response, detector_extractor.responses_) + assert_almost_equal(exp_orientations, + np.rad2deg(detector_extractor.orientations_), 5) - keypoints, _ = detector_extractor.detect_and_extract(img) - assert_almost_equal(exp_rows, keypoints[:, 0]) - assert_almost_equal(exp_cols, keypoints[:, 1]) + detector_extractor.detect_and_extract(img) + assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) def test_keypoints_orb_less_than_desired_no_of_keypoints(): img = rgb2gray(lena()) 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) + detector_extractor.detect(img) exp_rows = np.array([ 67., 247., 269., 413., 435., 230., 264., 330., 372.]) @@ -61,15 +62,16 @@ 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_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) + assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) + assert_almost_equal(exp_scales, detector_extractor.scales_) + assert_almost_equal(exp_response, detector_extractor.responses_) + assert_almost_equal(exp_orientations, + np.rad2deg(detector_extractor.orientations_), 5) - keypoints, _ = detector_extractor.detect_and_extract(img) - assert_almost_equal(exp_rows, keypoints[:, 0]) - assert_almost_equal(exp_cols, keypoints[:, 1]) + detector_extractor.detect_and_extract(img) + assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) def test_descriptor_orb(): @@ -96,14 +98,16 @@ def test_descriptor_orb(): [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]) + detector_extractor.detect(img) + detector_extractor.extract(img, detector_extractor.keypoints_, + detector_extractor.scales_, + detector_extractor.orientations_) + assert_array_equal(exp_descriptors, + detector_extractor.descriptors_[100:120, 10:20]) - keypoints2, descriptors2 = detector_extractor.detect_and_extract(img) - assert_array_equal(exp_descriptors, descriptors2[100:120, 10:20]) + detector_extractor.detect_and_extract(img) + assert_array_equal(exp_descriptors, + detector_extractor.descriptors_[100:120, 10:20]) if __name__ == '__main__': diff --git a/skimage/feature/util.py b/skimage/feature/util.py index b5faa565..8ee2baf8 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -5,6 +5,9 @@ from skimage.util import img_as_float class FeatureDetector(object): + def __init__(self): + self.keypoints_ = np.array([]) + def detect(self, image): """Detect keypoints in image. @@ -19,6 +22,9 @@ class FeatureDetector(object): class DescriptorExtractor(object): + def __init__(self): + self.descriptors_ = np.array([]) + def extract(self, image, keypoints): """Extract feature descriptors in image for given keypoints. From 6307e24372bcc34f4e19eeb6369e8b21ff18196c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 19 Jan 2014 10:33:29 -0500 Subject: [PATCH 136/145] Fix match tests --- skimage/feature/tests/test_match.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index e8b7c1ad..3bec4eb6 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -34,15 +34,15 @@ def test_binary_descriptors_lena_rotation_crosscheck_false(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - descriptor = BRIEF(descriptor_size=512) + extractor = BRIEF(descriptor_size=512) keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, mask1 = descriptor.extract(img, keypoints1) - keypoints1 = keypoints1[mask1] + extractor.extract(img, keypoints1) + descriptors1 = extractor.descriptors_ keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) - keypoints2 = keypoints1[mask2] + extractor.extract(rotated_img, keypoints2) + descriptors2 = extractor.descriptors_ matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=False) @@ -68,15 +68,15 @@ def test_binary_descriptors_lena_rotation_crosscheck_true(): tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0)) rotated_img = tf.warp(img, tform) - descriptor = BRIEF(descriptor_size=512) + extractor = BRIEF(descriptor_size=512) keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, mask1 = descriptor.extract(img, keypoints1) - keypoints1 = keypoints1[mask1] + extractor.extract(img, keypoints1) + descriptors1 = extractor.descriptors_ keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - descriptors2, mask2 = descriptor.extract(rotated_img, keypoints2) - keypoints2 = keypoints1[mask2] + extractor.extract(rotated_img, keypoints2) + descriptors2 = extractor.descriptors_ matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=True) From b5792dcda946c3e2defa88d8de7bb425dfe5e6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 19 Jan 2014 22:55:12 -0500 Subject: [PATCH 137/145] Fix BRIEF doc test --- skimage/feature/brief.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index b697ec3a..bba07926 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -74,23 +74,23 @@ class BRIEF(DescriptorExtractor): [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32) >>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1) >>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1) - >>> extractor1 = BRIEF(patch_size=5) - >>> extractor2 = BRIEF(patch_size=5) - >>> extractor1.extract(square1, keypoints1) - >>> extractor2.extract(square2, keypoints2) - >>> matches = match_descriptors(extractor1.descriptors_, - ... extractor2.descriptors_) + >>> extractor = BRIEF(patch_size=5) + >>> extractor.extract(square1, keypoints1) + >>> descriptors1 = extractor.descriptors_ + >>> extractor.extract(square2, keypoints2) + >>> descriptors2 = extractor.descriptors_ + >>> matches = match_descriptors(descriptors1, descriptors2) >>> matches array([[0, 0], [1, 1], [2, 2], [3, 3]]) - >>> extractor1.keypoints_[matches[:, 0]] + >>> keypoints1[matches[:, 0]] array([[2, 2], [2, 5], [5, 2], [5, 5]]) - >>> extractor2.keypoints_[matches[:, 1]] + >>> keypoints2[matches[:, 1]] array([[2, 2], [2, 6], [6, 2], From a1052c02a11539d34a7c12c7a86d103c2b445b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 18:49:05 -0500 Subject: [PATCH 138/145] Fix and improve BRIEF example --- doc/examples/plot_brief.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index 2ef473bb..e01d431a 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -10,12 +10,8 @@ a set of intensity difference tests. The short binary descriptor results in low memory footprint and very efficient matching based on the Hamming distance metric. -However, BRIEF does not provide rotation-invariance and scale-invariance can be -achieved by detecting and extracting features at different scales. - -The ORB feature detection and binary description algorithm is an extension to -the BRIEF method and provides rotation and scale-invariance, see -`skimage.feature.ORB`. +BRIEF does not provide rotation-invariance. Scale-invariance can be achieved by +detecting and extracting features at different scales. """ from skimage import data From 72f2963ca78dc67dfe7738c0527a868f04446d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 18:50:05 -0500 Subject: [PATCH 139/145] Improve ORB example --- doc/examples/plot_orb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index 6576fbb7..899d14e0 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -7,9 +7,9 @@ This example demonstrates the ORB feature detection and binary description algorithm. It uses an oriented FAST detection method and the rotated BRIEF descriptors. -ORB is comparatively scale- and rotation-invariant. As a binary descriptor it -allows to employ the very efficient Hamming distance metric for matching and -is thus preferred for real-time applications. +Unlike BRIEF, ORB is comparatively scale- and rotation-invariant while still +employing the very efficient Hamming distance metric for matching. As such, it +is preferred for real-time applications. """ from skimage import data From 279d89278901f730dbb297df67932b8e544a7b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 18:50:39 -0500 Subject: [PATCH 140/145] Improve BRIEF doc string --- skimage/feature/brief.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index bba07926..3421156b 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -12,15 +12,15 @@ 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 - relatively few bits and can be computed using simple intensity difference + feature point descriptor. It is highly discriminative even when using + relatively few bits and is computed using simple intensity difference tests. - For each keypoint intensity comparisons are carried out for a specifically + 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. + length N. For binary descriptors the Hamming distance can be used for + feature matching, which leads to lower computational cost in comparison to + the L2 norm. Parameters ---------- @@ -35,12 +35,12 @@ class BRIEF(DescriptorExtractor): around keypoints. sample_seed : int, optional 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. + 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. sigma : float, optional - Standard deviation of the Gaussian low pass filter applied to the image + 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. @@ -139,7 +139,7 @@ class BRIEF(DescriptorExtractor): image = _prepare_grayscale_input_2D(image) - # Gaussian Low pass filtering to alleviate noise sensitivity + # Gaussian low-pass filtering to alleviate noise sensitivity image = np.ascontiguousarray(gaussian_filter(image, self.sigma)) # Sampling pairs of decision pixels in patch_size x patch_size window From c3cf88edd3dafcf93ca63a7622906b9cfbe96476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 19:04:57 -0500 Subject: [PATCH 141/145] Get rid of trailing underscores --- doc/examples/plot_brief.py | 12 +-- doc/examples/plot_censure.py | 8 +- skimage/feature/brief.py | 39 ++++----- skimage/feature/censure.py | 29 ++++--- skimage/feature/orb.py | 109 +++++++++++--------------- skimage/feature/tests/test_brief.py | 8 +- skimage/feature/tests/test_censure.py | 12 +-- skimage/feature/tests/test_orb.py | 38 ++++----- 8 files changed, 122 insertions(+), 133 deletions(-) diff --git a/doc/examples/plot_brief.py b/doc/examples/plot_brief.py index e01d431a..47c9ad5f 100644 --- a/doc/examples/plot_brief.py +++ b/doc/examples/plot_brief.py @@ -34,16 +34,16 @@ keypoints3 = corner_peaks(corner_harris(img3), min_distance=5) extractor = BRIEF() extractor.extract(img1, keypoints1) -keypoints1 = keypoints1[extractor.mask_] -descriptors1 = extractor.descriptors_ +keypoints1 = keypoints1[extractor.mask] +descriptors1 = extractor.descriptors extractor.extract(img2, keypoints2) -keypoints2 = keypoints2[extractor.mask_] -descriptors2 = extractor.descriptors_ +keypoints2 = keypoints2[extractor.mask] +descriptors2 = extractor.descriptors extractor.extract(img3, keypoints3) -keypoints3 = keypoints3[extractor.mask_] -descriptors3 = extractor.descriptors_ +keypoints3 = keypoints3[extractor.mask] +descriptors3 = extractor.descriptors matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True) diff --git a/doc/examples/plot_censure.py b/doc/examples/plot_censure.py index 06afb8e8..41f20496 100644 --- a/doc/examples/plot_censure.py +++ b/doc/examples/plot_censure.py @@ -30,14 +30,14 @@ detector.detect(img1) ax[0].imshow(img1) ax[0].axis('off') -ax[0].scatter(detector.keypoints_[:, 1], detector.keypoints_[:, 0], - 2 ** detector.scales_, facecolors='none', edgecolors='r') +ax[0].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0], + 2 ** detector.scales, facecolors='none', edgecolors='r') detector.detect(img2) ax[1].imshow(img2) ax[1].axis('off') -ax[1].scatter(detector.keypoints_[:, 1], detector.keypoints_[:, 0], - 2 ** detector.scales_, facecolors='none', edgecolors='r') +ax[1].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0], + 2 ** detector.scales, facecolors='none', edgecolors='r') plt.show() diff --git a/skimage/feature/brief.py b/skimage/feature/brief.py index 3421156b..d1626f17 100644 --- a/skimage/feature/brief.py +++ b/skimage/feature/brief.py @@ -44,6 +44,18 @@ class BRIEF(DescriptorExtractor): to alleviate noise sensitivity, which is strongly recommended to obtain discriminative and good descriptors. + Attributes + ---------- + 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``). + Examples -------- >>> from skimage.feature import (corner_harris, corner_peaks, BRIEF, @@ -76,9 +88,9 @@ class BRIEF(DescriptorExtractor): >>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1) >>> extractor = BRIEF(patch_size=5) >>> extractor.extract(square1, keypoints1) - >>> descriptors1 = extractor.descriptors_ + >>> descriptors1 = extractor.descriptors >>> extractor.extract(square2, keypoints2) - >>> descriptors2 = extractor.descriptors_ + >>> descriptors2 = extractor.descriptors >>> matches = match_descriptors(descriptors1, descriptors2) >>> matches array([[0, 0], @@ -111,6 +123,9 @@ class BRIEF(DescriptorExtractor): self.sigma = sigma self.sample_seed = sample_seed + self.descriptors = None + self.mask = None + def extract(self, image, keypoints): """Extract BRIEF binary descriptors for given keypoints in image. @@ -121,18 +136,6 @@ class BRIEF(DescriptorExtractor): keypoints : (N, 2) array Keypoint coordinates as ``(row, col)``. - Attributes - ---------- - 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``). - """ np.random.seed(self.sample_seed) @@ -165,14 +168,14 @@ class BRIEF(DescriptorExtractor): # Removing keypoints that are within (patch_size / 2) distance from the # image border - self.mask_ = _mask_border_keypoints(image.shape, keypoints, + self.mask = _mask_border_keypoints(image.shape, keypoints, patch_size // 2) - keypoints = np.array(keypoints[self.mask_, :], dtype=np.intp, + keypoints = np.array(keypoints[self.mask, :], dtype=np.intp, order='C', copy=False) - self.descriptors_ = np.zeros((keypoints.shape[0], desc_size), + self.descriptors = np.zeros((keypoints.shape[0], desc_size), dtype=bool, order='C') - _brief_loop(image, self.descriptors_.view(np.uint8), keypoints, + _brief_loop(image, self.descriptors.view(np.uint8), keypoints, pos1, pos2) diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index f31c5445..c111bc0f 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -139,6 +139,13 @@ class CenSurE(FeatureDetector): Threshold for rejecting interest points which have ratio of principal curvatures greater than this value. + Attributes + ---------- + keypoints : (N, 2) array + Keypoint coordinates as ``(row, col)``. + scales : (N, ) array + Corresponding scales. + References ---------- .. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas @@ -159,7 +166,7 @@ class CenSurE(FeatureDetector): >>> img = rgb2gray(lena()[100:300, 100:300]) >>> censure = CenSurE() >>> censure.detect(img) - >>> censure.keypoints_ + >>> censure.keypoints array([[ 71, 148], [ 77, 186], [ 78, 189], @@ -175,7 +182,7 @@ class CenSurE(FeatureDetector): [171, 29], [179, 20], [194, 65]]) - >>> censure.scales_ + >>> censure.scales array([2, 4, 2, 3, 4, 2, 2, 3, 4, 6, 3, 2, 3, 4, 2]) """ @@ -197,6 +204,9 @@ class CenSurE(FeatureDetector): self.non_max_threshold = non_max_threshold self.line_threshold = line_threshold + self.keypoints = None + self.scales = None + def detect(self, image): """Detect CenSurE keypoints along with the corresponding scale. @@ -205,13 +215,6 @@ class CenSurE(FeatureDetector): image : 2D ndarray Input image. - Attributes - ---------- - keypoints : (N, 2) array - Keypoint coordinates as ``(row, col)``. - scales : (N, ) array - Corresponding scales. - """ # (1) First we generate the required scales on the input grayscale @@ -258,8 +261,8 @@ class CenSurE(FeatureDetector): scales = scales + self.min_scale + 1 if self.mode == 'dob': - self.keypoints_ = keypoints - self.scales_ = scales + self.keypoints = keypoints + self.scales = scales return cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) @@ -279,5 +282,5 @@ class CenSurE(FeatureDetector): _mask_border_keypoints(image.shape, keypoints, c) & (scales == i)) - self.keypoints_ = keypoints[cumulative_mask] - self.scales_ = scales[cumulative_mask] + self.keypoints = keypoints[cumulative_mask] + self.scales = scales[cumulative_mask] diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index b0010d77..fa24c59f 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -54,6 +54,23 @@ class ORB(FeatureDetector, DescriptorExtractor): Maximum number of scales from the bottom of the image pyramid to extract the features from. + Attributes + ---------- + 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. + 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)``. + References ---------- .. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski @@ -73,21 +90,21 @@ class ORB(FeatureDetector, DescriptorExtractor): >>> detector_extractor2 = ORB(n_keypoints=5) >>> detector_extractor1.detect_and_extract(img1) >>> detector_extractor2.detect_and_extract(img2) - >>> matches = match_descriptors(detector_extractor1.descriptors_, - ... detector_extractor2.descriptors_) + >>> matches = match_descriptors(detector_extractor1.descriptors, + ... detector_extractor2.descriptors) >>> matches array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]]) - >>> detector_extractor1.keypoints_[matches[:, 0]] + >>> detector_extractor1.keypoints[matches[:, 0]] array([[ 42., 40.], [ 47., 58.], [ 44., 40.], [ 59., 42.], [ 45., 44.]]) - >>> detector_extractor2.keypoints_[matches[:, 1]] + >>> detector_extractor2.keypoints[matches[:, 1]] array([[ 55., 53.], [ 60., 71.], [ 57., 53.], @@ -106,6 +123,12 @@ class ORB(FeatureDetector, DescriptorExtractor): self.fast_threshold = fast_threshold self.harris_k = harris_k + self.keypoints = None + self.scales = None + self.responses = None + self.orientations = None + self.descriptors = None + def _build_pyramid(self, image): image = _prepare_grayscale_input_2D(image) return list(pyramid_gaussian(image, self.n_scales - 1, self.downscale)) @@ -142,17 +165,6 @@ class ORB(FeatureDetector, DescriptorExtractor): image : 2D array Input image. - Attributes - ---------- - 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. - """ pyramid = self._build_pyramid(image) @@ -181,17 +193,17 @@ class ORB(FeatureDetector, DescriptorExtractor): responses = np.hstack(responses_list) if keypoints.shape[0] < self.n_keypoints: - self.keypoints_ = keypoints - self.scales_ = scales - self.orientations_ = orientations - self.responses_ = responses + self.keypoints = keypoints + self.scales = scales + self.orientations = orientations + self.responses = responses else: # Choose best n_keypoints according to Harris corner response best_indices = responses.argsort()[::-1][:self.n_keypoints] - self.keypoints_ = keypoints[best_indices] - self.scales_ = scales[best_indices] - self.orientations_ = orientations[best_indices] - self.responses_ = responses[best_indices] + self.keypoints = keypoints[best_indices] + self.scales = scales[best_indices] + self.orientations = orientations[best_indices] + self.responses = responses[best_indices] def _extract_octave(self, octave_image, keypoints, orientations): mask = _mask_border_keypoints(octave_image.shape, keypoints, @@ -224,18 +236,6 @@ class ORB(FeatureDetector, DescriptorExtractor): orientations : (N, ) array Corresponding orientations in radians. - Attributes - ---------- - 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) @@ -267,7 +267,7 @@ class ORB(FeatureDetector, DescriptorExtractor): descriptors_list.append(descriptors) mask_list.append(mask) - self.descriptors_ = np.vstack(descriptors_list).view(np.bool) + self.descriptors = np.vstack(descriptors_list).view(np.bool) self.mask_ = np.hstack(mask_list) def detect_and_extract(self, image): @@ -281,23 +281,6 @@ class ORB(FeatureDetector, DescriptorExtractor): image : 2D array Input image. - Attributes - ---------- - 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. - 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) @@ -338,16 +321,16 @@ class ORB(FeatureDetector, DescriptorExtractor): descriptors = np.vstack(descriptors_list).view(np.bool) if keypoints.shape[0] < self.n_keypoints: - self.keypoints_ = keypoints - self.scales_ = scales - self.orientations_ = orientations - self.responses_ = responses - self.descriptors_ = descriptors + self.keypoints = keypoints + self.scales = scales + self.orientations = orientations + self.responses = responses + self.descriptors = descriptors else: # Choose best n_keypoints according to Harris corner response best_indices = responses.argsort()[::-1][:self.n_keypoints] - self.keypoints_ = keypoints[best_indices] - self.scales_ = scales[best_indices] - self.orientations_ = orientations[best_indices] - self.responses_ = responses[best_indices] - self.descriptors_ = descriptors[best_indices] + self.keypoints = keypoints[best_indices] + self.scales = scales[best_indices] + self.orientations = orientations[best_indices] + self.responses = responses[best_indices] + self.descriptors = descriptors[best_indices] diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index e847c967..554301b2 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -32,7 +32,7 @@ def test_normal_mode(): [False, True, True, True, False, False, True, False], [False, False, False, False, True, False, False, False]], dtype=bool) - assert_array_equal(extractor.descriptors_, expected) + assert_array_equal(extractor.descriptors, expected) def test_uniform_mode(): @@ -54,7 +54,7 @@ def test_uniform_mode(): [False, False, True, True, False, False, True, True], [ True, True, False, False, False, False, False, False]], dtype=bool) - assert_array_equal(extractor.descriptors_, expected) + assert_array_equal(extractor.descriptors, expected) def test_unsupported_mode(): @@ -68,8 +68,8 @@ def test_border(): extractor = BRIEF(patch_size=41) extractor.extract(img, keypoints) - assert extractor.descriptors_.shape[0] == 3 - assert_array_equal(extractor.mask_, (False, True, True, True)) + assert extractor.descriptors.shape[0] == 3 + assert_array_equal(extractor.mask, (False, True, True, True)) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_censure.py b/skimage/feature/tests/test_censure.py index 608290d0..05348d43 100644 --- a/skimage/feature/tests/test_censure.py +++ b/skimage/feature/tests/test_censure.py @@ -40,8 +40,8 @@ def test_keypoints_censure_moon_image_dob(): [467, 260]]) expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) - assert_array_equal(expected_keypoints, detector.keypoints_) - assert_array_equal(expected_scales, detector.scales_) + assert_array_equal(expected_keypoints, detector.keypoints) + assert_array_equal(expected_scales, detector.scales) def test_keypoints_censure_moon_image_octagon(): @@ -58,8 +58,8 @@ def test_keypoints_censure_moon_image_octagon(): expected_scales = np.array([3, 4, 2, 2, 2]) - assert_array_equal(expected_keypoints, detector.keypoints_) - assert_array_equal(expected_scales, detector.scales_) + assert_array_equal(expected_keypoints, detector.keypoints) + assert_array_equal(expected_scales, detector.scales) def test_keypoints_censure_moon_image_star(): @@ -80,8 +80,8 @@ def test_keypoints_censure_moon_image_star(): expected_scales = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) - assert_array_equal(expected_keypoints, detector.keypoints_) - assert_array_equal(expected_scales, detector.scales_) + assert_array_equal(expected_keypoints, detector.keypoints) + assert_array_equal(expected_scales, detector.scales) if __name__ == '__main__': diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index 9943aeb3..30394d07 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -29,16 +29,16 @@ def test_keypoints_orb_desired_no_of_keypoints(): 0.39154173, 0.39084861, 0.39063076, 0.37602487]) - assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) - assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) - assert_almost_equal(exp_scales, detector_extractor.scales_) - assert_almost_equal(exp_response, detector_extractor.responses_) + assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1]) + assert_almost_equal(exp_scales, detector_extractor.scales) + assert_almost_equal(exp_response, detector_extractor.responses) assert_almost_equal(exp_orientations, - np.rad2deg(detector_extractor.orientations_), 5) + np.rad2deg(detector_extractor.orientations), 5) detector_extractor.detect_and_extract(img) - assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) - assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) + assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1]) def test_keypoints_orb_less_than_desired_no_of_keypoints(): @@ -62,16 +62,16 @@ 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_rows, detector_extractor.keypoints_[:, 0]) - assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) - assert_almost_equal(exp_scales, detector_extractor.scales_) - assert_almost_equal(exp_response, detector_extractor.responses_) + assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1]) + assert_almost_equal(exp_scales, detector_extractor.scales) + assert_almost_equal(exp_response, detector_extractor.responses) assert_almost_equal(exp_orientations, - np.rad2deg(detector_extractor.orientations_), 5) + np.rad2deg(detector_extractor.orientations), 5) detector_extractor.detect_and_extract(img) - assert_almost_equal(exp_rows, detector_extractor.keypoints_[:, 0]) - assert_almost_equal(exp_cols, detector_extractor.keypoints_[:, 1]) + assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0]) + assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1]) def test_descriptor_orb(): @@ -99,15 +99,15 @@ def test_descriptor_orb(): [ True, False, True, False, False, False, True, True, False, False]], dtype=bool) detector_extractor.detect(img) - detector_extractor.extract(img, detector_extractor.keypoints_, - detector_extractor.scales_, - detector_extractor.orientations_) + detector_extractor.extract(img, detector_extractor.keypoints, + detector_extractor.scales, + detector_extractor.orientations) assert_array_equal(exp_descriptors, - detector_extractor.descriptors_[100:120, 10:20]) + detector_extractor.descriptors[100:120, 10:20]) detector_extractor.detect_and_extract(img) assert_array_equal(exp_descriptors, - detector_extractor.descriptors_[100:120, 10:20]) + detector_extractor.descriptors[100:120, 10:20]) if __name__ == '__main__': From 13c63a9bf6893f04999b4f03a8ec3b0fd54b2211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 19:08:36 -0500 Subject: [PATCH 142/145] Fix parameter notation in doc string --- skimage/feature/orb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index fa24c59f..2ddcf4f3 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -27,11 +27,11 @@ class ORB(FeatureDetector, DescriptorExtractor): ---------- n_keypoints : int, optional Number of keypoints to be returned. The function will return the best - ``n_keypoints`` according to the Harris corner response if more than - ``n_keypoints`` are detected. If not, then all the detected keypoints + `n_keypoints` according to the Harris corner response if more than + `n_keypoints` are detected. If not, then all the detected keypoints are returned. fast_n : int, optional - The ``n`` parameter in ``feature.corner_fast``. Minimum number of + The `n` parameter in `skimage.feature.corner_fast`. Minimum number of consecutive pixels out of 16 pixels on the circle that should all be either brighter or darker w.r.t test-pixel. A point c on the circle is darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if @@ -43,9 +43,9 @@ class ORB(FeatureDetector, DescriptorExtractor): similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. harris_k : float, optional - The ``k`` parameter in ``feature.corner_harris``. Sensitivity factor to - separate corners from edges, typically in range ``[0, 0.2]``. Small - values of k result in detection of sharp corners. + The `k` parameter in `skimage.feature.corner_harris`. Sensitivity + factor to separate corners from edges, typically in range ``[0, 0.2]``. + Small values of `k` result in detection of sharp corners. downscale : float, optional Downscale factor for the image pyramid. Default value 1.2 is chosen so that there are more dense scales which enable robust scale invariance From 172cbd58e0bb8d7d3ee26c18b90e4754181eeac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 19:34:48 -0500 Subject: [PATCH 143/145] Rename CenSurE to CENSURE --- doc/examples/plot_censure.py | 10 +++++----- skimage/feature/__init__.py | 4 ++-- skimage/feature/censure.py | 12 ++++++------ skimage/feature/tests/test_censure.py | 14 +++++++------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/examples/plot_censure.py b/doc/examples/plot_censure.py index 41f20496..c7d70ea5 100644 --- a/doc/examples/plot_censure.py +++ b/doc/examples/plot_censure.py @@ -1,16 +1,16 @@ """ ======================== -CenSurE feature detector +CENSURE feature detector ======================== -The CenSurE feature detector is a scale-invariant center-surround detector -(CenSurE) that claims to outperform other detectors and is capable of real-time +The CENSURE feature detector is a scale-invariant center-surround detector +(CENSURE) that claims to outperform other detectors and is capable of real-time implementation. """ from skimage import data from skimage import transform as tf -from skimage.feature import CenSurE +from skimage.feature import CENSURE from skimage.color import rgb2gray import matplotlib.pyplot as plt @@ -20,7 +20,7 @@ tform = tf.AffineTransform(scale=(1.5, 1.5), rotation=0.5, translation=(150, -200)) img2 = tf.warp(img1, tform) -detector = CenSurE() +detector = CENSURE() fig, ax = plt.subplots(nrows=1, ncols=2) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 2402d0ea..1b1cb189 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -10,7 +10,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, from .corner_cy import corner_moravec, corner_orientations from .template import match_template from .brief import BRIEF -from .censure import CenSurE +from .censure import CENSURE from .orb import ORB from .match import match_descriptors from .util import plot_matches @@ -37,7 +37,7 @@ __all__ = ['daisy', 'corner_orientations', 'match_template', 'BRIEF', - 'CenSurE', + 'CENSURE', 'ORB', 'match_descriptors', 'plot_matches'] diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index c111bc0f..24d7e62d 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -111,9 +111,9 @@ def _suppress_lines(feature_mask, image, sigma, line_threshold): -class CenSurE(FeatureDetector): +class CENSURE(FeatureDetector): - """CenSurE keypoint detector. + """CENSURE keypoint detector. min_scale : int, optional Minimum scale to extract keypoints from. @@ -149,7 +149,7 @@ class CenSurE(FeatureDetector): References ---------- .. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas - "CenSurE: Center Surround Extremas for Realtime Feature + "CENSURE: Center Surround Extremas for Realtime Feature Detection and Matching", http://link.springer.com/content/pdf/10.1007%2F978-3-540-88693-8_8.pdf @@ -162,9 +162,9 @@ class CenSurE(FeatureDetector): -------- >>> from skimage.data import lena >>> from skimage.color import rgb2gray - >>> from skimage.feature import CenSurE + >>> from skimage.feature import CENSURE >>> img = rgb2gray(lena()[100:300, 100:300]) - >>> censure = CenSurE() + >>> censure = CENSURE() >>> censure.detect(img) >>> censure.keypoints array([[ 71, 148], @@ -208,7 +208,7 @@ class CenSurE(FeatureDetector): self.scales = None def detect(self, image): - """Detect CenSurE keypoints along with the corresponding scale. + """Detect CENSURE keypoints along with the corresponding scale. Parameters ---------- diff --git a/skimage/feature/tests/test_censure.py b/skimage/feature/tests/test_censure.py index 05348d43..f1d88f3d 100644 --- a/skimage/feature/tests/test_censure.py +++ b/skimage/feature/tests/test_censure.py @@ -1,7 +1,7 @@ import numpy as np from numpy.testing import assert_array_equal, assert_raises from skimage.data import moon -from skimage.feature import CenSurE +from skimage.feature import CENSURE img = moon() @@ -9,25 +9,25 @@ img = moon() def test_keypoints_censure_color_image_unsupported_error(): """Censure keypoints can be extracted from gray-scale images only.""" - assert_raises(ValueError, CenSurE().detect, np.zeros((20, 20, 3))) + assert_raises(ValueError, CENSURE().detect, np.zeros((20, 20, 3))) def test_keypoints_censure_mode_validity_error(): """Mode argument in keypoints_censure can be either DoB, Octagon or STAR.""" - assert_raises(ValueError, CenSurE, mode='dummy') + assert_raises(ValueError, CENSURE, mode='dummy') def test_keypoints_censure_scale_range_error(): """Difference between the the max_scale and min_scale parameters in keypoints_censure should be greater than or equal to two.""" - assert_raises(ValueError, CenSurE, min_scale=1, max_scale=2) + assert_raises(ValueError, CENSURE, min_scale=1, max_scale=2) def test_keypoints_censure_moon_image_dob(): """Verify the actual Censure keypoints and their corresponding scale with the expected values for DoB filter.""" - detector = CenSurE() + detector = CENSURE() detector.detect(img) expected_keypoints = np.array([[ 21, 497], [ 36, 46], @@ -48,7 +48,7 @@ def test_keypoints_censure_moon_image_octagon(): """Verify the actual Censure keypoints and their corresponding scale with the expected values for Octagon filter.""" - detector = CenSurE(mode='octagon') + detector = CENSURE(mode='octagon') detector.detect(img) expected_keypoints = np.array([[ 21, 496], [ 35, 46], @@ -65,7 +65,7 @@ def test_keypoints_censure_moon_image_octagon(): def test_keypoints_censure_moon_image_star(): """Verify the actual Censure keypoints and their corresponding scale with the expected values for STAR filter.""" - detector = CenSurE(mode='star') + detector = CENSURE(mode='star') detector.detect(img) expected_keypoints = np.array([[ 21, 497], [ 36, 46], From 56966984fa194a1480b218f298a127535ecf36e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 21:37:54 -0500 Subject: [PATCH 144/145] Fix match tests --- skimage/feature/tests/test_match.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index 3bec4eb6..f9496c0c 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -38,11 +38,11 @@ def test_binary_descriptors_lena_rotation_crosscheck_false(): keypoints1 = corner_peaks(corner_harris(img), min_distance=5) extractor.extract(img, keypoints1) - descriptors1 = extractor.descriptors_ + descriptors1 = extractor.descriptors keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) extractor.extract(rotated_img, keypoints2) - descriptors2 = extractor.descriptors_ + descriptors2 = extractor.descriptors matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=False) @@ -72,11 +72,11 @@ def test_binary_descriptors_lena_rotation_crosscheck_true(): keypoints1 = corner_peaks(corner_harris(img), min_distance=5) extractor.extract(img, keypoints1) - descriptors1 = extractor.descriptors_ + descriptors1 = extractor.descriptors keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) extractor.extract(rotated_img, keypoints2) - descriptors2 = extractor.descriptors_ + descriptors2 = extractor.descriptors matches = match_descriptors(descriptors1, descriptors2, threshold=0.13, cross_check=True) From b80b9f10564c0c9b2ea481d7c6d9dc7be85834e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 22 Jan 2014 23:12:10 -0500 Subject: [PATCH 145/145] Fix ORB example script --- doc/examples/plot_orb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/examples/plot_orb.py b/doc/examples/plot_orb.py index 899d14e0..1a73fc7f 100644 --- a/doc/examples/plot_orb.py +++ b/doc/examples/plot_orb.py @@ -29,16 +29,16 @@ img3 = tf.warp(img1, tform) descriptor_extractor = ORB(n_keypoints=200) descriptor_extractor.detect_and_extract(img1) -keypoints1 = descriptor_extractor.keypoints_ -descriptors1 = descriptor_extractor.descriptors_ +keypoints1 = descriptor_extractor.keypoints +descriptors1 = descriptor_extractor.descriptors descriptor_extractor.detect_and_extract(img2) -keypoints2 = descriptor_extractor.keypoints_ -descriptors2 = descriptor_extractor.descriptors_ +keypoints2 = descriptor_extractor.keypoints +descriptors2 = descriptor_extractor.descriptors descriptor_extractor.detect_and_extract(img3) -keypoints3 = descriptor_extractor.keypoints_ -descriptors3 = descriptor_extractor.descriptors_ +keypoints3 = descriptor_extractor.keypoints +descriptors3 = descriptor_extractor.descriptors matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True) matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True)