From d67f7008cf6c78a7edadfe44e3fee7ba58de6649 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 16 Jun 2013 01:54:55 +0800 Subject: [PATCH 01/26] First quick implementation of BRIEF Feature descriptor --- skimage/feature/_brief.py | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 skimage/feature/_brief.py diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py new file mode 100644 index 00000000..64a05373 --- /dev/null +++ b/skimage/feature/_brief.py @@ -0,0 +1,63 @@ +# TODO Normal sampling from image patch of size 49 x 49 +# TODO Tests, example, doc + +import numpy as np +from skimage.color import rgb2gray +from scipy.ndimage.filters import gaussian_filter + +KERNEL_SIZE = (9, 9) +PATCH_SIZE = (49, 49) + + +def _remove_border_keypoints(image, keypoints, dist): + + width = image.shape[0] + height = image.shape[1] + for i, j in keypoints: + if i > width - dist[0] or i < dist[0] or j < dist[1] or j > height - dist[0]: + keypoints.remove((i, j)) + return keypoints + + +def brief(image, keypoints, descriptor_size=32, mode='uniform'): + + if descriptor_size not in (16, 32, 64): + raise ValueError('Descriptor size should be either 16, 32 or 64 bytes') + + if np.squeeze(image).ndim == 3: + image = rgb2gray(image) + + keypoints = _remove_border_keypoints(image, keypoints, (PATCH_SIZE[0] / 2, PATCH_SIZE[1] / 2)) + + descriptor = np.zeros((len(keypoints), descriptor_size * 8), dtype=int) + + image = gaussian_filter(image, 2) + + if mode == 'uniform': + np.random.seed(1) + first = np.random.randint(-24, 25, (descriptor_size * 8, 2)) + np.random.seed(2) + second = np.random.randint(-24, 25, (descriptor_size * 8, 2)) + else: + #TODO mode='normal' + pass + + for i in range(len(keypoints)): + set_1 = first + keypoints[i] + set_2 = second + keypoints[i] + + for j in range(descriptor_size * 8): + if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: + descriptor[i][j] = 1 + else: + descriptor[i][j] = 0 + + return descriptor + +def hamming_distance(descriptor_1, descriptor_2): + + distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=int) + for i in range(len(descriptor_1)): + for j in range(len(descriptor_2)): + distance[i, j] = sum(np.bitwise_xor(descriptor_1[i][:], descriptor_2[j][:])) + return distance / descriptor_1.shape[1] From a1df85d511ba906072f74f144e67dd5f71d1f543 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 18 Jun 2013 01:07:07 +0800 Subject: [PATCH 02/26] Allowing arbitrary descriptor sizes in BRIEF --- skimage/feature/_brief.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 64a05373..a02b5c49 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -21,9 +21,6 @@ def _remove_border_keypoints(image, keypoints, dist): def brief(image, keypoints, descriptor_size=32, mode='uniform'): - if descriptor_size not in (16, 32, 64): - raise ValueError('Descriptor size should be either 16, 32 or 64 bytes') - if np.squeeze(image).ndim == 3: image = rgb2gray(image) From 566006bdaf4e6aebf7fb5187c7ecacf4f53baa2f Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 18 Jun 2013 03:18:38 +0800 Subject: [PATCH 03/26] Removing hard-coding in BRIEF code --- skimage/feature/_brief.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index a02b5c49..46bd5e92 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -32,9 +32,9 @@ def brief(image, keypoints, descriptor_size=32, mode='uniform'): if mode == 'uniform': np.random.seed(1) - first = np.random.randint(-24, 25, (descriptor_size * 8, 2)) + first = np.random.randint(-PATCH_SIZE / 2, (PATCH_SIZE / 2) + 1, (descriptor_size * 8, 2)) np.random.seed(2) - second = np.random.randint(-24, 25, (descriptor_size * 8, 2)) + second = np.random.randint(-PATCH_SIZE / 2, (PATCH_SIZE / 2) + 1, (descriptor_size * 8, 2)) else: #TODO mode='normal' pass From e931739502417be5ff95c788f6d0cf4a21f29c27 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 29 Jun 2013 12:09:56 +0800 Subject: [PATCH 04/26] Adding custom sampling seed, Normal sampling mode --- skimage/feature/_brief.py | 42 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 46bd5e92..40ad204b 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -5,45 +5,53 @@ import numpy as np from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter -KERNEL_SIZE = (9, 9) -PATCH_SIZE = (49, 49) - - def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - for i, j in keypoints: + + keypoints_list = keypoints.tolist() + + for i, j in keypoints_list: if i > width - dist[0] or i < dist[0] or j < dist[1] or j > height - dist[0]: - keypoints.remove((i, j)) + keypoints.remove([i, j]) + + keypoints = np.asarray(keypoints_list) return keypoints -def brief(image, keypoints, descriptor_size=32, mode='uniform'): +def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): if np.squeeze(image).ndim == 3: image = rgb2gray(image) - keypoints = _remove_border_keypoints(image, keypoints, (PATCH_SIZE[0] / 2, PATCH_SIZE[1] / 2)) + keypoints = np.round(keypoints) - descriptor = np.zeros((len(keypoints), descriptor_size * 8), dtype=int) + keypoints = _remove_border_keypoints(image, keypoints, (patch_size / 2, patch_size / 2)) + descriptor = np.zeros((len(keypoints), descriptor_size), dtype=int) + + # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity image = gaussian_filter(image, 2) - if mode == 'uniform': - np.random.seed(1) - first = np.random.randint(-PATCH_SIZE / 2, (PATCH_SIZE / 2) + 1, (descriptor_size * 8, 2)) - np.random.seed(2) - second = np.random.randint(-PATCH_SIZE / 2, (PATCH_SIZE / 2) + 1, (descriptor_size * 8, 2)) + # Sampling pairs of decision pixels in patch_size x patch_size window + if mode == 'normal': + np.random.seed(sample_seed) + samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) + samples = samples[samples < (patch_size / 2)] + samples = samples[samples > - (patch_size - 1) / 2] + first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) + second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) else: - #TODO mode='normal' - pass + np.random.seed(sample_seed) + samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) + first, second = np.split(samples, 2) for i in range(len(keypoints)): set_1 = first + keypoints[i] set_2 = second + keypoints[i] - for j in range(descriptor_size * 8): + for j in range(descriptor_size): if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: descriptor[i][j] = 1 else: From 95352a586cb5d3e853ed405e6febae4ebe4396f6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 30 Jun 2013 14:43:55 +0800 Subject: [PATCH 05/26] Added docs and used numpy trickery to filter keypoints --- skimage/feature/_brief.py | 66 ++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 40ad204b..22f62e71 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -4,32 +4,63 @@ import numpy as np from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter +from scipy.spatial.distance import hamming + def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - keypoints_list = keypoints.tolist() - - for i, j in keypoints_list: - if i > width - dist[0] or i < dist[0] or j < dist[1] or j > height - dist[0]: - keypoints.remove([i, j]) - - keypoints = np.asarray(keypoints_list) + keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & + (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] return keypoints def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): + """Extract BRIEF Descriptor about given keypoints for a given image. + + Parameters + ---------- + image : ndarray + Input image. + keypoints : (P, 2) ndarray + Array of keypoint locations. + descriptor_size : int + Size of BRIEF descriptor about each keypoint. Sizes 128, 256 and 512 + preferred by the authors. Default is 256. + mode : string + Probability distribution for sampling location of decision pixel-pairs + around keypoints. Default is 'normal' otherwise uniform. + 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. Default is 1. + + Returns + ------- + descriptor : ndarray with dtype bool + 2D ndarray of dimensions (no_of_keypoints, descriptor_size) 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. + + References + ---------- + .. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha, and Pascal Fua + "BRIEF : Binary robust independent elementary features", + http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf + + """ if np.squeeze(image).ndim == 3: image = rgb2gray(image) - keypoints = np.round(keypoints) + # Removing keypoints that are (patch_size / 2) distance from the image border + keypoints = np.array(keypoints + 0.5, dtype=np.intp) + keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - keypoints = _remove_border_keypoints(image, keypoints, (patch_size / 2, patch_size / 2)) - - descriptor = np.zeros((len(keypoints), descriptor_size), dtype=int) + descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity image = gaussian_filter(image, 2) @@ -38,8 +69,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s if mode == 'normal': np.random.seed(sample_seed) samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) - samples = samples[samples < (patch_size / 2)] - samples = samples[samples > - (patch_size - 1) / 2] + samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) else: @@ -47,22 +77,22 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) first, second = np.split(samples, 2) + # Intensity comparison tests for building the descriptor for i in range(len(keypoints)): set_1 = first + keypoints[i] set_2 = second + keypoints[i] for j in range(descriptor_size): if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: - descriptor[i][j] = 1 - else: - descriptor[i][j] = 0 + descriptor[i][j] = True return descriptor + def hamming_distance(descriptor_1, descriptor_2): distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=int) for i in range(len(descriptor_1)): for j in range(len(descriptor_2)): - distance[i, j] = sum(np.bitwise_xor(descriptor_1[i][:], descriptor_2[j][:])) - return distance / descriptor_1.shape[1] + distance[i, j] = hamming(descriptor_1[i][:], descriptor_2[j][:]) + return distance From a746834e09af8dfb18ff332288591d4617a692e2 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 30 Jun 2013 21:04:36 +0800 Subject: [PATCH 06/26] Changed dtype of hamming dist matrix and added docs --- skimage/feature/_brief.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 22f62e71..a5ce37b7 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,6 +1,3 @@ -# TODO Normal sampling from image patch of size 49 x 49 -# TODO Tests, example, doc - import numpy as np from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter @@ -20,7 +17,6 @@ def _remove_border_keypoints(image, keypoints, dist): def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): """Extract BRIEF Descriptor about given keypoints for a given image. - Parameters ---------- image : ndarray @@ -48,7 +44,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s References ---------- - .. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha, and Pascal Fua + .. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha and Pascal Fua "BRIEF : Binary robust independent elementary features", http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf @@ -56,8 +52,9 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s if np.squeeze(image).ndim == 3: image = rgb2gray(image) - # Removing keypoints that are (patch_size / 2) distance from the image border keypoints = np.array(keypoints + 0.5, dtype=np.intp) + + # Removing keypoints that are (patch_size / 2) distance from the image border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) @@ -90,8 +87,34 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s def hamming_distance(descriptor_1, descriptor_2): + """A dissimilarity measure used for matching keypoints in different images + using binary feature descriptors like BRIEF etc. - distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=int) + Parameters + ---------- + descriptor_1 : ndarray with dtype bool + Binary feature descriptor for keypoints in the first image. + 2D ndarray of dimensions (no_of_keypoints_in_image_1, descriptor_size) + 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. + descriptor_2 : ndarray with dtype bool + Binary feature descriptor for keypoints in the second image. + 2D ndarray of dimensions (no_of_keypoints_in_image_2, descriptor_size) + 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. + + Returns + ------- + distance : ndarray + 2D ndarray of dimensions (no_of_rows_in_descripto_1, no_of_rows_in_descripto_2) + with value at an index (i, j) between the range [0, 1] representing the + extent of dissimilarity between ith keypoint of in first image and jth + keypoint in second image. + + """ + distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=float) for i in range(len(descriptor_1)): for j in range(len(descriptor_2)): distance[i, j] = hamming(descriptor_1[i][:], descriptor_2[j][:]) From f3b827d21b6686839f2a117a3ed4dd57e2419969 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 30 Jun 2013 21:12:54 +0800 Subject: [PATCH 07/26] Fixing indentation --- skimage/feature/_brief.py | 108 +++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index a5ce37b7..b12d827b 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -6,16 +6,16 @@ from scipy.spatial.distance import hamming def _remove_border_keypoints(image, keypoints, dist): - width = image.shape[0] - height = image.shape[1] + width = image.shape[0] + height = image.shape[1] - keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & - (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] - return keypoints + keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & + (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] + return keypoints def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): - """Extract BRIEF Descriptor about given keypoints for a given image. + """Extract BRIEF Descriptor about given keypoints for a given image. Parameters ---------- @@ -49,73 +49,73 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf """ - if np.squeeze(image).ndim == 3: - image = rgb2gray(image) + if np.squeeze(image).ndim == 3: + image = rgb2gray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp) + keypoints = np.array(keypoints + 0.5, dtype=np.intp) - # Removing keypoints that are (patch_size / 2) distance from the image border - keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) + # Removing keypoints that are (patch_size / 2) distance from the image border + keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) + descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) - # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity - image = gaussian_filter(image, 2) + # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity + image = gaussian_filter(image, 2) - # Sampling pairs of decision pixels in patch_size x patch_size window - if mode == 'normal': - np.random.seed(sample_seed) - samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) - samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] - first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) - second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) - else: - np.random.seed(sample_seed) - samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) - first, second = np.split(samples, 2) + # Sampling pairs of decision pixels in patch_size x patch_size window + if mode == 'normal': + np.random.seed(sample_seed) + samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) + samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] + first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) + second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) + else: + np.random.seed(sample_seed) + samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) + first, second = np.split(samples, 2) - # Intensity comparison tests for building the descriptor - for i in range(len(keypoints)): - set_1 = first + keypoints[i] - set_2 = second + keypoints[i] + # Intensity comparison tests for building the descriptor + for i in range(len(keypoints)): + set_1 = first + keypoints[i] + set_2 = second + keypoints[i] - for j in range(descriptor_size): - if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: - descriptor[i][j] = True + for j in range(descriptor_size): + if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: + descriptor[i][j] = True - return descriptor + return descriptor def hamming_distance(descriptor_1, descriptor_2): - """A dissimilarity measure used for matching keypoints in different images - using binary feature descriptors like BRIEF etc. + """A dissimilarity measure used for matching keypoints in different images + using binary feature descriptors like BRIEF etc. Parameters ---------- descriptor_1 : ndarray with dtype bool - Binary feature descriptor for keypoints in the first image. - 2D ndarray of dimensions (no_of_keypoints_in_image_1, descriptor_size) - 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. + Binary feature descriptor for keypoints in the first image. + 2D ndarray of dimensions (no_of_keypoints_in_image_1, descriptor_size) + 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. descriptor_2 : ndarray with dtype bool - Binary feature descriptor for keypoints in the second image. - 2D ndarray of dimensions (no_of_keypoints_in_image_2, descriptor_size) - 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. + Binary feature descriptor for keypoints in the second image. + 2D ndarray of dimensions (no_of_keypoints_in_image_2, descriptor_size) + 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. Returns ------- distance : ndarray - 2D ndarray of dimensions (no_of_rows_in_descripto_1, no_of_rows_in_descripto_2) - with value at an index (i, j) between the range [0, 1] representing the - extent of dissimilarity between ith keypoint of in first image and jth - keypoint in second image. + 2D ndarray of dimensions (no_of_rows_in_descripto_1, no_of_rows_in_descripto_2) + with value at an index (i, j) between the range [0, 1] representing the + extent of dissimilarity between ith keypoint of in first image and jth + keypoint in second image. """ - distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=float) - for i in range(len(descriptor_1)): - for j in range(len(descriptor_2)): - distance[i, j] = hamming(descriptor_1[i][:], descriptor_2[j][:]) - return distance + distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=float) + for i in range(len(descriptor_1)): + for j in range(len(descriptor_2)): + distance[i, j] = hamming(descriptor_1[i][:], descriptor_2[j][:]) + return distance From ac7880b640fc6601680551a344f9145b1a047bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 2 Jul 2013 21:59:49 +0200 Subject: [PATCH 08/26] Outsource inner brief loop into cython file --- skimage/feature/__init__.py | 4 +- skimage/feature/_brief.py | 78 ++++++++++++++++++++++++------------- skimage/feature/setup.py | 3 ++ 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 09ac4540..3e9050c0 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -6,6 +6,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks) from .corner_cy import corner_moravec from .template import match_template +from ._brief import brief __all__ = ['daisy', @@ -21,4 +22,5 @@ __all__ = ['daisy', 'corner_subpix', 'corner_peaks', 'corner_moravec', - 'match_template'] + 'match_template', + 'brief'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index b12d827b..8cca5abc 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,20 +1,28 @@ import numpy as np -from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter from scipy.spatial.distance import hamming +from ..color import rgb2gray +from ..util import img_as_float + +from ._brief_cy import _brief_loop + def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & - (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] + keypoints = keypoints[(dist < keypoints[:, 0]) + & (keypoints[:, 0] < width - dist) + & (dist < keypoints[:, 1]) + & (keypoints[:, 1] < height - dist)] + return keypoints -def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): +def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, + sample_seed=1): """Extract BRIEF Descriptor about given keypoints for a given image. Parameters @@ -49,41 +57,55 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf """ - if np.squeeze(image).ndim == 3: - image = rgb2gray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp) + np.random.seed(sample_seed) - # Removing keypoints that are (patch_size / 2) distance from the image border + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + + # Gaussian Low pass filtering with variance 2 to alleviate noise + # sensitivity + image = gaussian_filter(image, 2) + + image = np.ascontiguousarray(image) + + keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') + + # Removing keypoints that are (patch_size / 2) distance from the image + # border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) - - # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity - image = gaussian_filter(image, 2) + descriptors = np.zeros((keypoints.shape[0], descriptor_size), + dtype=bool, order='C') # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': - np.random.seed(sample_seed) - samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) - samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] - first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) - second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) + + samples = (patch_size / 5) * np.random.randn(descriptor_size * 8) + samples = np.array(samples, dtype=np.int32) + samples = samples[(samples < (patch_size / 2)) + & (samples > - (patch_size - 1) / 2)] + + pos1 = samples[:descriptor_size * 2] + pos1 = pos1.reshape(descriptor_size, 2) + pos2 = samples[descriptor_size * 2:descriptor_size * 4] + pos2 = pos2.reshape(descriptor_size, 2) + else: - np.random.seed(sample_seed) - samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) - first, second = np.split(samples, 2) - # Intensity comparison tests for building the descriptor - for i in range(len(keypoints)): - set_1 = first + keypoints[i] - set_2 = second + keypoints[i] + samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, + (descriptor_size * 2, 2)) + pos1, pos2 = np.split(samples, 2) - for j in range(descriptor_size): - if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: - descriptor[i][j] = True + pos1 = np.ascontiguousarray(pos1) + pos2 = np.ascontiguousarray(pos2) - return descriptor + _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) + + return descriptors def hamming_distance(descriptor_1, descriptor_2): diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index e769621d..f4c62957 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -13,11 +13,14 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['corner_cy.pyx'], working_path=base_path) + cython(['_brief_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) config.add_extension('corner_cy', sources=['corner_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('_brief_cy', sources=['_brief_cy.c'], + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_texture', sources=['_texture.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('_template', sources=['_template.c'], From bcacd06f9c64fe580b031bd780f1ad14cb296691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 2 Jul 2013 21:59:49 +0200 Subject: [PATCH 09/26] Outsource inner brief loop into cython file --- skimage/feature/__init__.py | 4 +- skimage/feature/_brief.py | 78 ++++++++++++++++++++++------------- skimage/feature/_brief_cy.pyx | 24 +++++++++++ skimage/feature/setup.py | 3 ++ 4 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 skimage/feature/_brief_cy.pyx diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 09ac4540..3e9050c0 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -6,6 +6,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks) from .corner_cy import corner_moravec from .template import match_template +from ._brief import brief __all__ = ['daisy', @@ -21,4 +22,5 @@ __all__ = ['daisy', 'corner_subpix', 'corner_peaks', 'corner_moravec', - 'match_template'] + 'match_template', + 'brief'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index b12d827b..8cca5abc 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,20 +1,28 @@ import numpy as np -from skimage.color import rgb2gray from scipy.ndimage.filters import gaussian_filter from scipy.spatial.distance import hamming +from ..color import rgb2gray +from ..util import img_as_float + +from ._brief_cy import _brief_loop + def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - keypoints = keypoints[(dist < keypoints[:, 0]) & (keypoints[:, 0] < width - dist) & - (dist < keypoints[:, 1]) & (keypoints[:, 1] < height - dist)] + keypoints = keypoints[(dist < keypoints[:, 0]) + & (keypoints[:, 0] < width - dist) + & (dist < keypoints[:, 1]) + & (keypoints[:, 1] < height - dist)] + return keypoints -def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, sample_seed=1): +def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, + sample_seed=1): """Extract BRIEF Descriptor about given keypoints for a given image. Parameters @@ -49,41 +57,55 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, s http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf """ - if np.squeeze(image).ndim == 3: - image = rgb2gray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp) + np.random.seed(sample_seed) - # Removing keypoints that are (patch_size / 2) distance from the image border + image = np.squeeze(image) + if image.ndim != 2: + raise ValueError("Only 2-D gray-scale images supported.") + + image = img_as_float(image) + + # Gaussian Low pass filtering with variance 2 to alleviate noise + # sensitivity + image = gaussian_filter(image, 2) + + image = np.ascontiguousarray(image) + + keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') + + # Removing keypoints that are (patch_size / 2) distance from the image + # border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - descriptor = np.zeros((len(keypoints), descriptor_size), dtype=bool) - - # Gaussian Low pass filtering with variance 2 to alleviate noise sensitivity - image = gaussian_filter(image, 2) + descriptors = np.zeros((keypoints.shape[0], descriptor_size), + dtype=bool, order='C') # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': - np.random.seed(sample_seed) - samples = np.round((patch_size / 5) * np.random.randn(descriptor_size * 8)) - samples = samples[(samples < (patch_size / 2)) & (samples > - (patch_size - 1) / 2)] - first = (samples[: descriptor_size * 2]).reshape(descriptor_size, 2) - second = (samples[descriptor_size * 2: descriptor_size * 4]).reshape(descriptor_size, 2) + + samples = (patch_size / 5) * np.random.randn(descriptor_size * 8) + samples = np.array(samples, dtype=np.int32) + samples = samples[(samples < (patch_size / 2)) + & (samples > - (patch_size - 1) / 2)] + + pos1 = samples[:descriptor_size * 2] + pos1 = pos1.reshape(descriptor_size, 2) + pos2 = samples[descriptor_size * 2:descriptor_size * 4] + pos2 = pos2.reshape(descriptor_size, 2) + else: - np.random.seed(sample_seed) - samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, (descriptor_size * 2, 2)) - first, second = np.split(samples, 2) - # Intensity comparison tests for building the descriptor - for i in range(len(keypoints)): - set_1 = first + keypoints[i] - set_2 = second + keypoints[i] + samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, + (descriptor_size * 2, 2)) + pos1, pos2 = np.split(samples, 2) - for j in range(descriptor_size): - if image[set_1[j, 0]][set_1[j, 1]] < image[set_2[j, 0]][set_2[j, 0]]: - descriptor[i][j] = True + pos1 = np.ascontiguousarray(pos1) + pos2 = np.ascontiguousarray(pos2) - return descriptor + _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) + + return descriptors def hamming_distance(descriptor_1, descriptor_2): diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/_brief_cy.pyx new file mode 100644 index 00000000..43d54f7a --- /dev/null +++ b/skimage/feature/_brief_cy.pyx @@ -0,0 +1,24 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +cimport numpy as cnp + + +def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors, + Py_ssize_t[:, ::1] keypoints, + int[:, ::1] pos0, int[:, ::1] pos1): + + cdef Py_ssize_t k, d, kr, kc, pr0, pr1, pc0, pc1 + + for p in range(pos0.shape[0]): + pr0 = pos0[p, 0] + pc0 = pos0[p, 1] + pr1 = pos1[p, 0] + pc1 = pos1[p, 1] + for k in range(keypoints.shape[0]): + kr = keypoints[k, 0] + kc = keypoints[k, 1] + if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]: + descriptors[k, p] = True diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index e769621d..f4c62957 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -13,11 +13,14 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['corner_cy.pyx'], working_path=base_path) + cython(['_brief_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) config.add_extension('corner_cy', sources=['corner_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('_brief_cy', sources=['_brief_cy.c'], + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_texture', sources=['_texture.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('_template', sources=['_template.c'], From 3dcd24e6f36ae841a159ca66d600dbd23493471d Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 3 Jul 2013 23:19:43 +0800 Subject: [PATCH 10/26] Clearing docs and making feature.util --- skimage/feature/__init__.py | 5 ++- skimage/feature/_brief.py | 76 ++++++++++--------------------------- skimage/feature/util.py | 50 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 skimage/feature/util.py diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 3e9050c0..1a755da7 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -7,7 +7,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, from .corner_cy import corner_moravec from .template import match_template from ._brief import brief - +from .util import hamming_distance __all__ = ['daisy', 'hog', @@ -23,4 +23,5 @@ __all__ = ['daisy', 'corner_peaks', 'corner_moravec', 'match_template', - 'brief'] + 'brief', + 'hamming_distance'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 8cca5abc..facb28c0 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,33 +1,20 @@ import numpy as np from scipy.ndimage.filters import gaussian_filter -from scipy.spatial.distance import hamming from ..color import rgb2gray from ..util import img_as_float +from .util import _remove_border_keypoints from ._brief_cy import _brief_loop -def _remove_border_keypoints(image, keypoints, dist): - - width = image.shape[0] - height = image.shape[1] - - keypoints = keypoints[(dist < keypoints[:, 0]) - & (keypoints[:, 0] < width - dist) - & (dist < keypoints[:, 1]) - & (keypoints[:, 1] < height - dist)] - - return keypoints - - def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, - sample_seed=1): + sample_seed=1, variance=2, return_keypoints=False): """Extract BRIEF Descriptor about given keypoints for a given image. Parameters ---------- - image : ndarray + image : 2D ndarray Input image. keypoints : (P, 2) ndarray Array of keypoint locations. @@ -42,13 +29,20 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, the keypoints. Default is 49. sample_seed : int Seed for sampling the decision pixel-pairs. Default is 1. + return_keypoints : bool + If True, return the Q keypoints (after filtering out the border + keypoints) about which the descriptors are extracted. Default is False. Returns ------- - descriptor : ndarray with dtype bool - 2D ndarray of dimensions (no_of_keypoints, descriptor_size) with value - at an index (i, j) either being True or False representing the outcome + descriptors : (Q, descriptor_size) ndarray of dtype bool + 2D ndarray of binary descriptors of size descriptor_size about 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 : (Q, 2) ndarray + Keypoints after removing out those that are near border. + Returned only if return_keypoints is True. References ---------- @@ -66,9 +60,9 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, image = img_as_float(image) - # Gaussian Low pass filtering with variance 2 to alleviate noise + # Gaussian Low pass filtering to alleviate noise # sensitivity - image = gaussian_filter(image, 2) + image = gaussian_filter(image, variance) image = np.ascontiguousarray(image) @@ -105,39 +99,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) - return descriptors - - -def hamming_distance(descriptor_1, descriptor_2): - """A dissimilarity measure used for matching keypoints in different images - using binary feature descriptors like BRIEF etc. - - Parameters - ---------- - descriptor_1 : ndarray with dtype bool - Binary feature descriptor for keypoints in the first image. - 2D ndarray of dimensions (no_of_keypoints_in_image_1, descriptor_size) - 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. - descriptor_2 : ndarray with dtype bool - Binary feature descriptor for keypoints in the second image. - 2D ndarray of dimensions (no_of_keypoints_in_image_2, descriptor_size) - 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. - - Returns - ------- - distance : ndarray - 2D ndarray of dimensions (no_of_rows_in_descripto_1, no_of_rows_in_descripto_2) - with value at an index (i, j) between the range [0, 1] representing the - extent of dissimilarity between ith keypoint of in first image and jth - keypoint in second image. - - """ - distance = np.zeros((len(descriptor_1), len(descriptor_2)), dtype=float) - for i in range(len(descriptor_1)): - for j in range(len(descriptor_2)): - distance[i, j] = hamming(descriptor_1[i][:], descriptor_2[j][:]) - return distance + if return_keypoints: + return descriptors, keypoints + else: + return descriptors diff --git a/skimage/feature/util.py b/skimage/feature/util.py new file mode 100644 index 00000000..5747eb47 --- /dev/null +++ b/skimage/feature/util.py @@ -0,0 +1,50 @@ +import numpy as np +from scipy.spatial.distance import hamming + + +def _remove_border_keypoints(image, keypoints, dist): + """Removes keypoints that are within dist pixels from the image border. + """ + width = image.shape[0] + height = image.shape[1] + + keypoints = keypoints[(dist < keypoints[:, 0]) + & (keypoints[:, 0] < width - dist) + & (dist < keypoints[:, 1]) + & (keypoints[:, 1] < height - dist)] + + return keypoints + + +def hamming_distance(descriptors1, descriptors2): + """A dissimilarity measure used for matching keypoints in different images + using binary feature descriptors like BRIEF etc. + + Parameters + ---------- + descriptors1 : (P1, D) array of dtype bool + Binary feature descriptors for keypoints in the first image. + 2D ndarray with a binary descriptors of size D about P1 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. + descriptors2 : (P2, D) array of dtype bool + Binary feature descriptors for keypoints in the second image. + 2D ndarray with a binary descriptors of size D about P2 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. + + Returns + ------- + distance : (P1, P2) array of dtype float + 2D ndarray with value at an index (i, j) in the range [0, 1] + representing the extent of dissimilarity between ith keypoint of in + first image and jth keypoint in second image. + + """ + distance = np.zeros((descriptors1.shape[0], descriptors2.shape[0]), dtype=float) + for i in range(descriptors1.shape[0]): + for j in range(descriptors2.shape[0]): + distance[i, j] = hamming(descriptors1[i, :], descriptors2[j, :]) + return distance From 56e7ea23c50e67b3ebc3fbf048a4b12c4d396b03 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 3 Jul 2013 23:32:49 +0800 Subject: [PATCH 11/26] Removing unused import and fixing indentation issue --- skimage/feature/_brief.py | 1 - skimage/feature/util.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index facb28c0..23dc63ab 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -1,7 +1,6 @@ import numpy as np from scipy.ndimage.filters import gaussian_filter -from ..color import rgb2gray from ..util import img_as_float from .util import _remove_border_keypoints diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 5747eb47..b634305b 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -3,8 +3,7 @@ from scipy.spatial.distance import hamming def _remove_border_keypoints(image, keypoints, dist): - """Removes keypoints that are within dist pixels from the image border. - """ + """Removes keypoints that are within dist pixels from the image border.""" width = image.shape[0] height = image.shape[1] From 71c0c2ce5832ebc40249e0be6dd9161127642237 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 6 Jul 2013 20:11:44 +0800 Subject: [PATCH 12/26] Added an example in skimage.feature._brief --- skimage/feature/_brief.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 23dc63ab..1637ea5e 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -49,6 +49,19 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, "BRIEF : Binary robust independent elementary features", http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf + Examples + -------- + >>> from skimage.data import camera + >>> from skimage.feature._brief import * + >>> from skimage.feature.corner import * + >>> img = camera() + >>> keypoints = corner_peaks(corner_harris(img), min_distance=10) + >>> keypoints.shape + (556, 2) + >>> descriptors = brief(img, keypoints) + >>> descriptors.shape + (489, 256) + """ np.random.seed(sample_seed) From 50ac1e89af799de23d8c37e3f0b24a3dfad75f0b Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 6 Jul 2013 20:34:32 +0800 Subject: [PATCH 13/26] Fixing bug in _remove_border_keypoints --- skimage/feature/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index b634305b..c77421d7 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -7,10 +7,10 @@ def _remove_border_keypoints(image, keypoints, dist): width = image.shape[0] height = image.shape[1] - keypoints = keypoints[(dist < keypoints[:, 0]) - & (keypoints[:, 0] < width - dist) - & (dist < keypoints[:, 1]) - & (keypoints[:, 1] < height - dist)] + keypoints = keypoints[(dist - 1 < keypoints[:, 0]) + & (keypoints[:, 0] < width - dist + 1) + & (dist - 1 < keypoints[:, 1]) + & (keypoints[:, 1] < height - dist + 1)] return keypoints From f932db666e187eecad711edcda26ccc91c320cc1 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 6 Jul 2013 20:51:01 +0800 Subject: [PATCH 14/26] Added more detailed example in docstrings --- skimage/feature/_brief.py | 57 ++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 1637ea5e..e782e008 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -51,16 +51,55 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, Examples -------- - >>> from skimage.data import camera - >>> from skimage.feature._brief import * >>> from skimage.feature.corner import * - >>> img = camera() - >>> keypoints = corner_peaks(corner_harris(img), min_distance=10) - >>> keypoints.shape - (556, 2) - >>> descriptors = brief(img, keypoints) - >>> descriptors.shape - (489, 256) + >>> from skimage.feature import brief, hamming_distance + >>> square1 = np.zeros([10, 10]) + >>> square1[2:8, 2:8] = 1 + >>> square1 + 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.], + [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], + [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], + [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], + [ 0., 0., 1., 1., 1., 1., 1., 1., 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.]]) + >>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1) + >>> keypoints1 + array([[2, 2], + [2, 7], + [7, 2], + [7, 7]]) + >>> descriptors1 = brief(square1, keypoints1, patch_size = 5) + >>> square2 = np.zeros([12, 12]) + >>> square2[3:9, 3:9] = 1 + >>> square2 + 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.]]) + >>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1) + >>> keypoints2 + array([[3, 3], + [3, 8], + [8, 3], + [8, 8]]) + >>> descriptors2 = brief(square2, keypoints1, patch_size = 5) + >>> hamming_distance(descriptors1, descriptors2) + array([[ 0.02734375, 0.2890625 , 0.32421875, 0.6171875 ], + [ 0.3359375 , 0.05078125, 0.6640625 , 0.37109375], + [ 0.359375 , 0.64453125, 0.03125 , 0.33203125], + [ 0.640625 , 0.40234375, 0.3828125 , 0.01953125]]) """ From df607071a0b3685384bfb0aa09088bd2d9f209c6 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 7 Jul 2013 13:28:31 +0800 Subject: [PATCH 15/26] Adding match_keypoints_brief --- skimage/feature/__init__.py | 5 +-- skimage/feature/_brief.py | 67 +++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 1a755da7..8192e323 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -6,7 +6,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, corner_foerstner, corner_subpix, corner_peaks) from .corner_cy import corner_moravec from .template import match_template -from ._brief import brief +from ._brief import brief, match_keypoints_brief from .util import hamming_distance __all__ = ['daisy', @@ -24,4 +24,5 @@ __all__ = ['daisy', 'corner_moravec', 'match_template', 'brief', - 'hamming_distance'] + 'hamming_distance', + 'match_keypoints_brief'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index e782e008..398559e8 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -2,7 +2,7 @@ import numpy as np from scipy.ndimage.filters import gaussian_filter from ..util import img_as_float -from .util import _remove_border_keypoints +from .util import _remove_border_keypoints, hamming_distance from ._brief_cy import _brief_loop @@ -53,6 +53,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, -------- >>> from skimage.feature.corner import * >>> from skimage.feature import brief, hamming_distance + >>> from skimage.feature._brief import * >>> square1 = np.zeros([10, 10]) >>> square1[2:8, 2:8] = 1 >>> square1 @@ -72,7 +73,12 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [2, 7], [7, 2], [7, 7]]) - >>> descriptors1 = brief(square1, keypoints1, patch_size = 5) + >>> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size = 5, return_keypoints=True) + >>> keypoints1 + array([[2, 2], + [2, 7], + [7, 2], + [7, 7]]) >>> square2 = np.zeros([12, 12]) >>> square2[3:9, 3:9] = 1 >>> square2 @@ -94,12 +100,27 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [3, 8], [8, 3], [8, 8]]) - >>> descriptors2 = brief(square2, keypoints1, patch_size = 5) + >>> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size = 5, return_keypoints=True) + >>> keypoints2 + array([[3, 3], + [3, 8], + [8, 3], + [8, 8]]) >>> hamming_distance(descriptors1, descriptors2) - array([[ 0.02734375, 0.2890625 , 0.32421875, 0.6171875 ], - [ 0.3359375 , 0.05078125, 0.6640625 , 0.37109375], - [ 0.359375 , 0.64453125, 0.03125 , 0.33203125], - [ 0.640625 , 0.40234375, 0.3828125 , 0.01953125]]) + array([[ 0.00390625, 0.33984375, 0.35546875, 0.63671875], + [ 0.3359375 , 0. , 0.65625 , 0.3515625 ], + [ 0.359375 , 0.65625 , 0. , 0.3515625 ], + [ 0.6328125 , 0.3515625 , 0.3515625 , 0. ]]) + >>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2) + array([[[ 2., 2.], + [ 2., 7.], + [ 7., 2.], + [ 7., 7.]], + + [[ 3., 3.], + [ 3., 8.], + [ 8., 3.], + [ 8., 8.]]]) """ @@ -154,3 +175,35 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, return descriptors, keypoints else: return descriptors + +def match_keypoints_brief(keypoints1, descriptors1, keypoints2, + descriptors2, threshold=0.15): + + 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.") + + distance = hamming_distance(descriptors1, descriptors2) + + dist_matched_kp = np.amin(distance, axis=1) + index_matched_kp2 = distance.argmin(axis=1) + + temp = np.zeros((keypoints1.shape[0], 3)) + temp[:, 0] = range(keypoints1.shape[0]) + temp[:, 1] = index_matched_kp2 + temp[:, 2] = dist_matched_kp + temp = temp[temp[:, 2] < threshold] + + matched_kp1 = keypoints1[np.int16(temp[:, 0])] + matched_kp2 = keypoints2[np.int16(temp[:, 1])] + + matched_kp = np.zeros((2, matched_kp1.shape[0], 2)) + matched_kp[0, :, :] = matched_kp1 + matched_kp[1, :, :] = matched_kp2 + + return matched_kp From 0988650fbee7ef54edcb1032117ce3c654b33fdb Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Mon, 8 Jul 2013 18:58:05 +0800 Subject: [PATCH 16/26] Adding docs for match_keypoints_brief --- skimage/feature/_brief.py | 145 ++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 398559e8..f4c20bff 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -28,6 +28,9 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, the keypoints. Default is 49. sample_seed : int Seed for sampling the decision pixel-pairs. Default is 1. + variance : float + Variance of the Gaussian Low Pass filter applied on the image to + alleviate noise sensitivity. Default is 2. return_keypoints : bool If True, return the Q keypoints (after filtering out the border keypoints) about which the descriptors are extracted. Default is False. @@ -46,81 +49,76 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, References ---------- .. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha and Pascal Fua - "BRIEF : Binary robust independent elementary features", - http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf + "BRIEF : Binary robust independent elementary features", + http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf Examples -------- >>> from skimage.feature.corner import * - >>> from skimage.feature import brief, hamming_distance + >>> from skimage.feature import hamming_distance >>> from skimage.feature._brief import * - >>> square1 = np.zeros([10, 10]) - >>> square1[2:8, 2:8] = 1 + >>> 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., 0., 0.], - [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], - [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], - [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], - [ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.], - [ 0., 0., 1., 1., 1., 1., 1., 1., 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.]]) + 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], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [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 array([[2, 2], - [2, 7], - [7, 2], - [7, 7]]) + [2, 5], + [5, 2], + [5, 5]]) >>> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size = 5, return_keypoints=True) >>> keypoints1 array([[2, 2], - [2, 7], - [7, 2], - [7, 7]]) - >>> square2 = np.zeros([12, 12]) - >>> square2[3:9, 3:9] = 1 + [2, 5], + [5, 2], + [5, 5]]) + >>> 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., 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.]]) + 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], + [0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0], + [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 - array([[3, 3], - [3, 8], - [8, 3], - [8, 8]]) + array([[2, 2], + [2, 6], + [6, 2], + [6, 6]]) >>> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size = 5, return_keypoints=True) >>> keypoints2 - array([[3, 3], - [3, 8], - [8, 3], - [8, 8]]) + array([[2, 2], + [2, 6], + [6, 2], + [6, 6]]) >>> hamming_distance(descriptors1, descriptors2) - array([[ 0.00390625, 0.33984375, 0.35546875, 0.63671875], - [ 0.3359375 , 0. , 0.65625 , 0.3515625 ], - [ 0.359375 , 0.65625 , 0. , 0.3515625 ], - [ 0.6328125 , 0.3515625 , 0.3515625 , 0. ]]) + 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]]) >>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2) array([[[ 2., 2.], - [ 2., 7.], - [ 7., 2.], - [ 7., 7.]], + [ 2., 5.], + [ 5., 2.], + [ 5., 5.]], - [[ 3., 3.], - [ 3., 8.], - [ 8., 3.], - [ 8., 8.]]]) + [[ 2., 2.], + [ 2., 6.], + [ 6., 2.], + [ 6., 6.]]]) """ @@ -178,8 +176,30 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, def match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2, threshold=0.15): + """Match keypoints described using BRIEF descriptors. - if keypoints1.shape[0] != descriptors1.shape[0] or keypoints2.shape[0] != descriptors2.shape[0]: + Parameters + ---------- + keypoints1 : (M, 2) ndarray + M Keypoints from the first image described using feature._brief.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 feature._brief.brief + descriptors2 : (N, P) ndarray + BRIEF descriptors of size P about N keypoints in the second image. + threshold : float in range [0, 1] + Threshold for removing matched keypoint pairs with hamming distance + greater than it. Default is 0.15 + + Returns + ------- + match_keypoints_brief : (2, Q, 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.") @@ -187,12 +207,16 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, 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 = hamming_distance(descriptors1, descriptors2) + # For each keypoint in keypoints1, match it with the keypoint in keypoints2 + # that has minimum hamming distance dist_matched_kp = np.amin(distance, axis=1) index_matched_kp2 = distance.argmin(axis=1) + # Remove the matched pairs which have hamming distance greater than the + # threshold temp = np.zeros((keypoints1.shape[0], 3)) temp[:, 0] = range(keypoints1.shape[0]) temp[:, 1] = index_matched_kp2 @@ -202,8 +226,9 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, matched_kp1 = keypoints1[np.int16(temp[:, 0])] matched_kp2 = keypoints2[np.int16(temp[:, 1])] - matched_kp = np.zeros((2, matched_kp1.shape[0], 2)) - matched_kp[0, :, :] = matched_kp1 - matched_kp[1, :, :] = matched_kp2 + # Collecting matched keypoint pairs from their index pairs + matched_keypoint_pairs = np.zeros((2, matched_kp1.shape[0], 2)) + matched_keypoint_pairs[0, :, :] = matched_kp1 + matched_keypoint_pairs[1, :, :] = matched_kp2 - return matched_kp + return matched_keypoint_pairs From b737dc97a6b569fad13273e6cb5970cafb6b7e64 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 9 Jul 2013 23:47:44 +0800 Subject: [PATCH 17/26] Making hamming_distance more generalized; improving docs --- skimage/feature/_brief.py | 19 +++++++++++-------- skimage/feature/util.py | 30 +++++++++++------------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index f4c20bff..7f3a9788 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -27,7 +27,11 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, 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. Default is 1. + 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. variance : float Variance of the Gaussian Low Pass filter applied on the image to alleviate noise sensitivity. Default is 2. @@ -37,8 +41,8 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, Returns ------- - descriptors : (Q, descriptor_size) ndarray of dtype bool - 2D ndarray of binary descriptors of size descriptor_size about Q + descriptors : (Q, `descriptor_size`) ndarray of dtype bool + 2D ndarray of binary descriptors of size `descriptor_size` about 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. @@ -136,14 +140,13 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, image = np.ascontiguousarray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') + keypoints = np.array(keypoints + 0.5, dtype=np.intp) - # Removing keypoints that are (patch_size / 2) distance from the image - # border + # Removing keypoints that are within (patch_size / 2) distance from the + # image border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) - descriptors = np.zeros((keypoints.shape[0], descriptor_size), - dtype=bool, order='C') + descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool) # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': diff --git a/skimage/feature/util.py b/skimage/feature/util.py index c77421d7..67eb93ca 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -15,35 +15,27 @@ def _remove_border_keypoints(image, keypoints, dist): return keypoints -def hamming_distance(descriptors1, descriptors2): +def hamming_distance(array1, array2): """A dissimilarity measure used for matching keypoints in different images using binary feature descriptors like BRIEF etc. Parameters ---------- - descriptors1 : (P1, D) array of dtype bool - Binary feature descriptors for keypoints in the first image. - 2D ndarray with a binary descriptors of size D about P1 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. - descriptors2 : (P2, D) array of dtype bool - Binary feature descriptors for keypoints in the second image. - 2D ndarray with a binary descriptors of size D about P2 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. + array1 : (P1, D) array of dtype bool + P1 vectors of size D with boolean elements. + array2 : (P2, D) array of dtype bool + P2 vectors of size D with boolean elements. Returns ------- distance : (P1, P2) array of dtype float 2D ndarray with value at an index (i, j) in the range [0, 1] - representing the extent of dissimilarity between ith keypoint of in - first image and jth keypoint in second image. + representing the hamming distance between ith vector in + array1 and jth vector in array2. """ - distance = np.zeros((descriptors1.shape[0], descriptors2.shape[0]), dtype=float) - for i in range(descriptors1.shape[0]): - for j in range(descriptors2.shape[0]): - distance[i, j] = hamming(descriptors1[i, :], descriptors2[j, :]) + distance = np.zeros((array1.shape[0], array2.shape[0]), dtype=float) + for i in range(array1.shape[0]): + for j in range(array2.shape[0]): + distance[i, j] = hamming(array1[i, :], array2[j, :]) return distance From 11a2883c50e65ecc09ecbe2613e3795d9d6df7e9 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 10 Jul 2013 22:41:21 +0800 Subject: [PATCH 18/26] Broadcasting in pairwise_hamming_distance; numpy optimization in match_keypoints_brief --- skimage/feature/__init__.py | 4 +- skimage/feature/_brief.py | 82 ++++++++++++++--------------------- skimage/feature/_brief_cy.pyx | 2 +- skimage/feature/util.py | 20 +++------ 4 files changed, 42 insertions(+), 66 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 8192e323..8df1dc10 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -7,7 +7,7 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi, from .corner_cy import corner_moravec from .template import match_template from ._brief import brief, match_keypoints_brief -from .util import hamming_distance +from .util import pairwise_hamming_distance __all__ = ['daisy', 'hog', @@ -24,5 +24,5 @@ __all__ = ['daisy', 'corner_moravec', 'match_template', 'brief', - 'hamming_distance', + 'pairwise_hamming_distance', 'match_keypoints_brief'] diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 7f3a9788..4c580d28 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 img_as_float -from .util import _remove_border_keypoints, hamming_distance +from .util import _remove_border_keypoints, pairwise_hamming_distance from ._brief_cy import _brief_loop def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, - sample_seed=1, variance=2, return_keypoints=False): + sample_seed=1, variance=2): """Extract BRIEF Descriptor about given keypoints for a given image. Parameters @@ -35,9 +35,6 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, variance : float Variance of the Gaussian Low Pass filter applied on the image to alleviate noise sensitivity. Default is 2. - return_keypoints : bool - If True, return the Q keypoints (after filtering out the border - keypoints) about which the descriptors are extracted. Default is False. Returns ------- @@ -59,7 +56,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, Examples -------- >>> from skimage.feature.corner import * - >>> from skimage.feature import hamming_distance + >>> from skimage.feature import pairwise_hamming_distance >>> from skimage.feature._brief import * >>> square1 = np.zeros([8, 8], dtype=np.int32) >>> square1[2:6, 2:6] = 1 @@ -78,7 +75,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, return_keypoints=True) + >>> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size = 5) >>> keypoints1 array([[2, 2], [2, 5], @@ -102,30 +99,29 @@ 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, return_keypoints=True) + >>> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size = 5) >>> keypoints2 array([[2, 2], [2, 6], [6, 2], [6, 6]]) - >>> hamming_distance(descriptors1, descriptors2) + >>> 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]]) >>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2) - array([[[ 2., 2.], - [ 2., 5.], - [ 5., 2.], - [ 5., 5.]], + array([[[2, 2], + [2, 5], + [5, 2], + [5, 5]], - [[ 2., 2.], - [ 2., 6.], - [ 6., 2.], - [ 6., 6.]]]) + [[2, 2], + [2, 6], + [6, 2], + [6, 6]]]) """ - np.random.seed(sample_seed) image = np.squeeze(image) @@ -140,13 +136,15 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, image = np.ascontiguousarray(image) - keypoints = np.array(keypoints + 0.5, dtype=np.intp) + keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C') # Removing keypoints that are within (patch_size / 2) distance from the # image border keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) + keypoints = np.ascontiguousarray(keypoints) - descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool) + descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool, + order='C') # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': @@ -172,28 +170,27 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) - if return_keypoints: - return descriptors, keypoints - else: - return descriptors + return descriptors, keypoints + def match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2, threshold=0.15): - """Match keypoints described using BRIEF descriptors. + """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 feature._brief.brief + 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 feature._brief.brief + 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] - Threshold for removing matched keypoint pairs with hamming distance - greater than it. Default is 0.15 + Maximum allowable hamming distance between descriptors of two keypoints + in separate images to be regarded as a match. Default is 0.15. Returns ------- @@ -210,28 +207,13 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, 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 = hamming_distance(descriptors1, descriptors2) + distance = pairwise_hamming_distance(descriptors1, descriptors2) - # For each keypoint in keypoints1, match it with the keypoint in keypoints2 - # that has minimum hamming distance - dist_matched_kp = np.amin(distance, axis=1) - index_matched_kp2 = distance.argmin(axis=1) - - # Remove the matched pairs which have hamming distance greater than the - # threshold - temp = np.zeros((keypoints1.shape[0], 3)) - temp[:, 0] = range(keypoints1.shape[0]) - temp[:, 1] = index_matched_kp2 - temp[:, 2] = dist_matched_kp - temp = temp[temp[:, 2] < threshold] - - matched_kp1 = keypoints1[np.int16(temp[:, 0])] - matched_kp2 = keypoints2[np.int16(temp[:, 1])] - - # Collecting matched keypoint pairs from their index pairs - matched_keypoint_pairs = np.zeros((2, matched_kp1.shape[0], 2)) - matched_keypoint_pairs[0, :, :] = matched_kp1 - matched_keypoint_pairs[1, :, :] = matched_kp2 + temp = distance > threshold + row_check = ~ np.all(temp, axis = 1) + matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] + matched_keypoint_pairs = np.array([keypoints1[row_check], matched_keypoints2[row_check]]) return matched_keypoint_pairs diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/_brief_cy.pyx index 43d54f7a..c53d85fc 100644 --- a/skimage/feature/_brief_cy.pyx +++ b/skimage/feature/_brief_cy.pyx @@ -21,4 +21,4 @@ def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors, kr = keypoints[k, 0] kc = keypoints[k, 1] if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]: - descriptors[k, p] = True + descriptors[k, p] = True diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 67eb93ca..8b5dd632 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -1,6 +1,3 @@ -import numpy as np -from scipy.spatial.distance import hamming - def _remove_border_keypoints(image, keypoints, dist): """Removes keypoints that are within dist pixels from the image border.""" @@ -15,9 +12,9 @@ def _remove_border_keypoints(image, keypoints, dist): return keypoints -def hamming_distance(array1, array2): - """A dissimilarity measure used for matching keypoints in different images - using binary feature descriptors like BRIEF etc. +def pairwise_hamming_distance(array1, array2): + """Calculate hamming dissimilarity measure between two sets of + boolean vectors. Parameters ---------- @@ -29,13 +26,10 @@ def hamming_distance(array1, array2): Returns ------- distance : (P1, P2) array of dtype float - 2D ndarray with value at an index (i, j) in the range [0, 1] - representing the hamming distance between ith vector in - array1 and jth vector in array2. + 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 = np.zeros((array1.shape[0], array2.shape[0]), dtype=float) - for i in range(array1.shape[0]): - for j in range(array2.shape[0]): - distance[i, j] = hamming(array1[i, :], array2[j, :]) + distance = (array1[:,None] != array2[None]).mean(axis=2) return distance From c8d5f707793f724fe3e47d04f59d19e73e98d55f Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 11 Jul 2013 01:38:17 +0800 Subject: [PATCH 19/26] Added _brief_cy.pyx in bento.info --- bento.info | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bento.info b/bento.info index 4cfa0173..a710dd2f 100644 --- a/bento.info +++ b/bento.info @@ -150,6 +150,9 @@ Library: Extension: skimage.filter.rank._crank16_bilateral Sources: skimage/filter/rank/_crank16_bilateral.pyx + Extension: skimage.feature._brief_cy + Sources: + skimage/feature/_brief_cy.pyx Executable: skivi Module: skimage.scripts.skivi From 8eec4770f402282f0a6b34c40fe715a9f785787a Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Thu, 11 Jul 2013 01:48:07 +0800 Subject: [PATCH 20/26] Moving _brief_cy.pyx changes in bento.info to the features section --- bento.info | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bento.info b/bento.info index a710dd2f..36d0e891 100644 --- a/bento.info +++ b/bento.info @@ -90,6 +90,9 @@ Library: Extension: skimage.morphology._greyreconstruct Sources: skimage/morphology/_greyreconstruct.pyx + Extension: skimage.feature._brief_cy + Sources: + skimage/feature/_brief_cy.pyx Extension: skimage.feature.corner_cy Sources: skimage/feature/corner_cy.pyx @@ -150,9 +153,6 @@ Library: Extension: skimage.filter.rank._crank16_bilateral Sources: skimage/filter/rank/_crank16_bilateral.pyx - Extension: skimage.feature._brief_cy - Sources: - skimage/feature/_brief_cy.pyx Executable: skivi Module: skimage.scripts.skivi From 083c13d7bd37fbaba0a54d73129173ab65fcd7a9 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Fri, 12 Jul 2013 00:30:10 +0800 Subject: [PATCH 21/26] Adding tests for BRIEF and pairwise_hamming_distance --- skimage/feature/tests/test_brief.py | 70 +++++++++++++++++++++++++++++ skimage/feature/tests/test_util.py | 27 +++++++++++ 2 files changed, 97 insertions(+) create mode 100644 skimage/feature/tests/test_brief.py create mode 100644 skimage/feature/tests/test_util.py diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py new file mode 100644 index 00000000..7718e97c --- /dev/null +++ b/skimage/feature/tests/test_brief.py @@ -0,0 +1,70 @@ +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.feature.corner import corner_peaks, corner_harris +from skimage.color import rgb2gray +from skimage.feature import brief, match_keypoints_brief + + +def test_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) + + +def test_match_keypoints_brief_lena_translation(): + """Test matched keypoints between lena image and its translated version.""" + img = data.lena() + img = rgb2gray(img) + img.shape + tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20)) + translated_img = tf.warp(img, tform) + + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + + keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5) + descriptors2, keypoints2 = brief(translated_img, keypoints2, + descriptor_size=512) + + matched_keypoints = match_keypoints_brief(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(): + """Verify matched keypoints result between lena image and its rotated version + with the expected keypoint pairs.""" + 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) + + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, keypoints2 = brief(rotated_img, keypoints2, + descriptor_size=512) + + matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, + keypoints2, descriptors2, + threshold=0.07) + expected = np.array([[[248, 147], + [263, 272], + [271, 120], + [414, 70], + [454, 176]], + + [[232, 171], + [234, 298], + [258, 146], + [405, 111], + [435, 221]]]) + assert_array_equal(matched_keypoints, expected) diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py new file mode 100644 index 00000000..6a397e5b --- /dev/null +++ b/skimage/feature/tests/test_util.py @@ -0,0 +1,27 @@ +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) From e3cd0620a703d85e09882128b7d77f7d675c5b36 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 13 Jul 2013 10:02:12 +0800 Subject: [PATCH 22/26] Fixing bugs and making code compatible with Python 3 --- skimage/feature/_brief.py | 40 ++++++++++++++++------------- skimage/feature/tests/test_brief.py | 29 +++++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 4c580d28..d6f4e72e 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -55,9 +55,9 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, Examples -------- - >>> from skimage.feature.corner import * - >>> from skimage.feature import pairwise_hamming_distance - >>> from skimage.feature._brief import * + >>> import numpy as np + >>> from skimage.feature.corner import corner_peaks, corner_harris + >>> from skimage.feature import pairwise_hamming_distance, brief, match_keypoints_brief >>> square1 = np.zeros([8, 8], dtype=np.int32) >>> square1[2:6, 2:6] = 1 >>> square1 @@ -111,15 +111,17 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [ 0.375 , 0.6328125, 0.0390625, 0.328125 ], [ 0.625 , 0.3671875, 0.34375 , 0.0234375]]) >>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2) - array([[[2, 2], - [2, 5], - [5, 2], - [5, 5]], + array([[[ 2., 2.], + [ 2., 2.]], - [[2, 2], - [2, 6], - [6, 2], - [6, 6]]]) + [[ 2., 5.], + [ 2., 6.]], + + [[ 5., 2.], + [ 6., 2.]], + + [[ 5., 5.], + [ 6., 6.]]]) """ np.random.seed(sample_seed) @@ -140,7 +142,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, # Removing keypoints that are within (patch_size / 2) distance from the # image border - keypoints = _remove_border_keypoints(image, keypoints, patch_size / 2) + keypoints = _remove_border_keypoints(image, keypoints, patch_size // 2) keypoints = np.ascontiguousarray(keypoints) descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool, @@ -149,10 +151,10 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, # Sampling pairs of decision pixels in patch_size x patch_size window if mode == 'normal': - samples = (patch_size / 5) * np.random.randn(descriptor_size * 8) + 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 - 1) / 2)] + samples = samples[(samples < (patch_size // 2)) + & (samples > - (patch_size - 2) // 2)] pos1 = samples[:descriptor_size * 2] pos1 = pos1.reshape(descriptor_size, 2) @@ -161,7 +163,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, else: - samples = np.random.randint(-patch_size / 2, (patch_size / 2) + 1, + samples = np.random.randint(-(patch_size - 2) // 2, (patch_size // 2) + 1, (descriptor_size * 2, 2)) pos1, pos2 = np.split(samples, 2) @@ -194,7 +196,7 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, Returns ------- - match_keypoints_brief : (2, Q, 2) ndarray + match_keypoints_brief : (Q, 2, 2) ndarray Location of Q matched keypoint pairs from two images. """ @@ -214,6 +216,8 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, temp = distance > threshold row_check = ~ np.all(temp, axis = 1) matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] - matched_keypoint_pairs = np.array([keypoints1[row_check], matched_keypoints2[row_check]]) + matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2)) + matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] + matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] return matched_keypoint_pairs diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 7718e97c..b8d26544 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -33,7 +33,7 @@ def test_match_keypoints_brief_lena_translation(): keypoints2, descriptors2, threshold=0.10) - assert_array_equal(matched_keypoints[0,::], matched_keypoints[1,::] + + assert_array_equal(matched_keypoints[:, 0,:], matched_keypoints[:, 1,:] + [20, 15]) @@ -56,15 +56,22 @@ def test_match_keypoints_brief_lena_rotation(): matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2, threshold=0.07) - expected = np.array([[[248, 147], - [263, 272], - [271, 120], - [414, 70], - [454, 176]], + 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.]]]) - [[232, 171], - [234, 298], - [258, 146], - [405, 111], - [435, 221]]]) assert_array_equal(matched_keypoints, expected) From 49e8f28fe90aef9fb1dbdeb7ccf81f33df6249f4 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 13 Jul 2013 19:01:30 +0800 Subject: [PATCH 23/26] Final changes --- skimage/feature/_brief.py | 18 ++++++++-------- skimage/feature/tests/test_brief.py | 33 +++++++++++++++-------------- skimage/feature/util.py | 10 ++++----- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index d6f4e72e..4feb2698 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -111,17 +111,17 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, [ 0.375 , 0.6328125, 0.0390625, 0.328125 ], [ 0.625 , 0.3671875, 0.34375 , 0.0234375]]) >>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2) - 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]]]) """ np.random.seed(sample_seed) @@ -216,7 +216,7 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, temp = distance > threshold row_check = ~ np.all(temp, axis = 1) matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] - matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2)) + 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] diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index b8d26544..65c99af2 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -8,10 +8,10 @@ from skimage.feature import brief, match_keypoints_brief def test_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) + """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) def test_match_keypoints_brief_lena_translation(): @@ -56,22 +56,23 @@ def test_match_keypoints_brief_lena_rotation(): matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2, threshold=0.07) - expected = np.array([[[ 263., 272.], - [ 234., 298.]], + expected = np.array([[[263, 272], + [234, 298]], - [[ 271., 120.], - [ 258., 146.]], + [[271, 120], + [258, 146]], - [[ 323., 164.], - [ 305., 195.]], + [[323, 164], + [305, 195]], - [[ 414., 70.], - [ 405., 111.]], + [[414, 70], + [405, 111]], - [[ 435., 181.], - [ 415., 223.]], + [[435, 181], + [415, 223]], + + [[454, 176], + [435, 221]]]) - [[ 454., 176.], - [ 435., 221.]]]) assert_array_equal(matched_keypoints, expected) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index 8b5dd632..a7a3670f 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -14,14 +14,14 @@ def _remove_border_keypoints(image, keypoints, dist): def pairwise_hamming_distance(array1, array2): """Calculate hamming dissimilarity measure between two sets of - boolean vectors. + vectors. Parameters ---------- - array1 : (P1, D) array of dtype bool - P1 vectors of size D with boolean elements. - array2 : (P2, D) array of dtype bool - P2 vectors of size D with boolean elements. + array1 : (P1, D) array + P1 vectors of size D. + array2 : (P2, D) array + P2 vectors of size D. Returns ------- From cb49e1ce7d2f05ecf38be5d8de0459d7c28c8e81 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sun, 14 Jul 2013 05:27:32 +0800 Subject: [PATCH 24/26] Replacing np.all by equivalent np.any --- 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 4feb2698..30c0934f 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -214,7 +214,7 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, distance = pairwise_hamming_distance(descriptors1, descriptors2) temp = distance > threshold - row_check = ~ np.all(temp, axis = 1) + 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] From 2466df39e1a575626918395eb20a23301f2c754a Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Tue, 16 Jul 2013 20:16:23 +0530 Subject: [PATCH 25/26] Stylistic changes --- skimage/feature/__init__.py | 5 ++- skimage/feature/_brief.py | 25 ++++++----- skimage/feature/tests/test_brief.py | 68 ++++++++++++++--------------- skimage/feature/tests/test_util.py | 40 ++++++++--------- skimage/feature/util.py | 3 +- 5 files changed, 72 insertions(+), 69 deletions(-) diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index 8df1dc10..d0c51fb0 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -2,8 +2,9 @@ from ._daisy import daisy from ._hog import hog 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) +from .corner import (corner_kitchen_rosenfeld, corner_harris, + corner_shi_tomasi, corner_foerstner, corner_subpix, + corner_peaks) from .corner_cy import corner_moravec from .template import match_template from ._brief import brief, match_keypoints_brief diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 30c0934f..22b8e75a 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -31,7 +31,7 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, 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. + building the descriptors. Default is 1. variance : float Variance of the Gaussian Low Pass filter applied on the image to alleviate noise sensitivity. Default is 2. @@ -75,7 +75,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 = brief(square1, keypoints1, patch_size=5) >>> keypoints1 array([[2, 2], [2, 5], @@ -99,7 +99,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 = brief(square2, keypoints2, patch_size=5) >>> keypoints2 array([[2, 2], [2, 6], @@ -163,7 +163,8 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, else: - samples = np.random.randint(-(patch_size - 2) // 2, (patch_size // 2) + 1, + samples = np.random.randint(-(patch_size - 2) // 2, + (patch_size // 2) + 1, (descriptor_size * 2, 2)) pos1, pos2 = np.split(samples, 2) @@ -200,21 +201,21 @@ def match_keypoints_brief(keypoints1, descriptors1, keypoints2, 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 (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.") + 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) + 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] diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 65c99af2..3d126770 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -15,48 +15,49 @@ def test_brief_color_image_unsupported_error(): def test_match_keypoints_brief_lena_translation(): - """Test matched keypoints between lena image and its translated version.""" - img = data.lena() - img = rgb2gray(img) - img.shape - tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20)) - translated_img = tf.warp(img, tform) + """Test matched keypoints between lena image and its translated version.""" + img = data.lena() + img = rgb2gray(img) + img.shape + tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20)) + translated_img = tf.warp(img, tform) - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) - keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5) - descriptors2, keypoints2 = brief(translated_img, keypoints2, - descriptor_size=512) + keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5) + descriptors2, keypoints2 = brief(translated_img, keypoints2, + descriptor_size=512) - matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, - keypoints2, descriptors2, - threshold=0.10) + matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, + keypoints2, descriptors2, + threshold=0.10) - assert_array_equal(matched_keypoints[:, 0,:], matched_keypoints[:, 1,:] + - [20, 15]) + assert_array_equal(matched_keypoints[:, 0, :], matched_keypoints[:, 1, :] + + [20, 15]) def test_match_keypoints_brief_lena_rotation(): - """Verify matched keypoints result between lena image and its rotated version - with the expected keypoint pairs.""" - 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) + """Verify matched keypoints result between lena image and its rotated + version with the expected keypoint pairs.""" + 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) - keypoints1 = corner_peaks(corner_harris(img), min_distance=5) - descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) + keypoints1 = corner_peaks(corner_harris(img), min_distance=5) + descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512) - keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) - descriptors2, keypoints2 = brief(rotated_img, keypoints2, - descriptor_size=512) + keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5) + descriptors2, keypoints2 = brief(rotated_img, keypoints2, + descriptor_size=512) - matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, - keypoints2, descriptors2, - threshold=0.07) - expected = np.array([[[263, 272], + matched_keypoints = match_keypoints_brief(keypoints1, descriptors1, + keypoints2, descriptors2, + threshold=0.07) + + expected = np.array([[[263, 272], [234, 298]], [[271, 120], @@ -74,5 +75,4 @@ def test_match_keypoints_brief_lena_rotation(): [[454, 176], [435, 221]]]) - - assert_array_equal(matched_keypoints, expected) + assert_array_equal(matched_keypoints, expected) diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 6a397e5b..6e2215c5 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -2,26 +2,26 @@ 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)) + """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) + """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) diff --git a/skimage/feature/util.py b/skimage/feature/util.py index a7a3670f..aec4dfc8 100644 --- a/skimage/feature/util.py +++ b/skimage/feature/util.py @@ -1,4 +1,5 @@ + def _remove_border_keypoints(image, keypoints, dist): """Removes keypoints that are within dist pixels from the image border.""" width = image.shape[0] @@ -31,5 +32,5 @@ def pairwise_hamming_distance(array1, array2): vector in array2. """ - distance = (array1[:,None] != array2[None]).mean(axis=2) + distance = (array1[:, None] != array2[None]).mean(axis=2) return distance From 431261e0952b39f9fadb8104a75d9b53cb2b1806 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 17 Jul 2013 15:54:58 +0530 Subject: [PATCH 26/26] Added if __name__ == __main__ in new test files --- skimage/feature/tests/test_brief.py | 5 +++++ skimage/feature/tests/test_util.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/skimage/feature/tests/test_brief.py b/skimage/feature/tests/test_brief.py index 3d126770..b78270f7 100644 --- a/skimage/feature/tests/test_brief.py +++ b/skimage/feature/tests/test_brief.py @@ -76,3 +76,8 @@ def test_match_keypoints_brief_lena_rotation(): [435, 221]]]) assert_array_equal(matched_keypoints, expected) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() diff --git a/skimage/feature/tests/test_util.py b/skimage/feature/tests/test_util.py index 6e2215c5..6e25f51a 100644 --- a/skimage/feature/tests/test_util.py +++ b/skimage/feature/tests/test_util.py @@ -25,3 +25,8 @@ def test_pairwise_hamming_distance_value(): [0.4 , 0.55, 0.5 ], [0.47, 0.48, 0.57]]) assert_array_equal(result, expected) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite()