From 11a2883c50e65ecc09ecbe2613e3795d9d6df7e9 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Wed, 10 Jul 2013 22:41:21 +0800 Subject: [PATCH] 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