From a53d93e0f7ae3b2dd628843efbad8f790d075fa7 Mon Sep 17 00:00:00 2001 From: Ankit Agrawal Date: Sat, 21 Sep 2013 03:13:30 +0530 Subject: [PATCH] Improved match_binary_descriptors function --- bento.info | 3 ++ skimage/feature/_brief.py | 50 -------------------- skimage/feature/match.py | 88 ++++++++++++++++++++++++++++++++++++ skimage/feature/match_cy.pyx | 21 +++++++++ skimage/feature/orb_cy.pyx | 2 +- skimage/feature/setup.py | 3 ++ 6 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 skimage/feature/match.py create mode 100644 skimage/feature/match_cy.pyx diff --git a/bento.info b/bento.info index 725d1c06..f819d7c0 100644 --- a/bento.info +++ b/bento.info @@ -96,6 +96,9 @@ Library: Extension: skimage.feature.censure_cy Sources: skimage/feature/censure_cy.pyx + Extension: skimage.feature.match_cy + Sources: + skimage/feature/match_cy.pyx Extension: skimage.feature.orb_cy Sources: skimage/feature/orb_cy.pyx diff --git a/skimage/feature/_brief.py b/skimage/feature/_brief.py index 9e0475d3..be7e4ac9 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/_brief.py @@ -173,53 +173,3 @@ def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49, _brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2) return descriptors, keypoints - - -def match_keypoints_brief(keypoints1, descriptors1, keypoints2, - descriptors2, threshold=0.15): - """**Experimental function**. - - Match keypoints described using BRIEF descriptors in one image to - those in second image. - - Parameters - ---------- - keypoints1 : (M, 2) ndarray - M Keypoints from the first image described using skimage.feature.brief - descriptors1 : (M, P) ndarray - BRIEF descriptors of size P about M keypoints in the first image. - keypoints2 : (N, 2) ndarray - N Keypoints from the second image described using skimage.feature.brief - descriptors2 : (N, P) ndarray - BRIEF descriptors of size P about N keypoints in the second image. - threshold : float in range [0, 1] - Maximum allowable hamming distance between descriptors of two keypoints - in separate images to be regarded as a match. Default is 0.15. - - Returns - ------- - match_keypoints_brief : (Q, 2, 2) ndarray - Location of Q matched keypoint pairs from two images. - - """ - if (keypoints1.shape[0] != descriptors1.shape[0] - or keypoints2.shape[0] != descriptors2.shape[0]): - raise ValueError("The number of keypoints and number of described " - "keypoints do not match. Make the optional parameter " - "return_keypoints True to get described keypoints.") - - if descriptors1.shape[1] != descriptors2.shape[1]: - raise ValueError("Descriptor sizes for matching keypoints in both " - "the images should be equal.") - - # Get hamming distances between keeypoints1 and keypoints2 - distance = pairwise_hamming_distance(descriptors1, descriptors2) - - temp = distance > threshold - row_check = np.any(~temp, axis=1) - matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] - matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp) - matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] - matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] - - return matched_keypoint_pairs diff --git a/skimage/feature/match.py b/skimage/feature/match.py new file mode 100644 index 00000000..d9f95c90 --- /dev/null +++ b/skimage/feature/match.py @@ -0,0 +1,88 @@ +import numpy as np + +from .util import pairwise_hamming_distance +from .match_cy import _binary_cross_check_loop + + +def match_binary_descriptors(keypoints1, descriptors1, keypoints2, + descriptors2, threshold=0.40, cross_check=True, + return_mask=True): + """Match keypoints described using binary descriptors in one image to + those in second image. + + Parameters + ---------- + keypoints1 : (M, 2) ndarray + M Keypoints from the first image described using skimage.feature.brief + descriptors1 : (M, P) ndarray + BRIEF descriptors of size P about M keypoints in the first image. + keypoints2 : (N, 2) ndarray + N Keypoints from the second image described using skimage.feature.brief + descriptors2 : (N, P) ndarray + BRIEF descriptors of size P about N keypoints in the second image. + threshold : float in range [0, 1] + Maximum allowable hamming distance between descriptors of two keypoints + in separate images to be regarded as a match. + cross_check : bool + Cross check if True. + return_mask : bool + Return index masks mask1 and mask2 for matched keypoints from + keypoints1 and keypoints2 respectively. + + Returns + ------- + match_keypoints_pairs : (Q, 2, 2) ndarray + Location of Q matched keypoint pairs from two images. + mask1 : (Q,) ndarray + Indices of keypoints in keypoints1 that have been matched. Returned + only when return_mask is True. + mask2 : (Q,) ndarray + Indices of keypoints in keypoints2 that have been matched. Returned + only when return_mask is True. + + """ + if (keypoints1.shape[0] != descriptors1.shape[0] + or keypoints2.shape[0] != descriptors2.shape[0]): + raise ValueError("The number of keypoints and number of described " + "keypoints do not match.") + + if descriptors1.shape[1] != descriptors2.shape[1]: + raise ValueError("Descriptor sizes for matching keypoints in both " + "the images should be equal.") + + # Get hamming distances between keypoints1 and keypoints2 + distance = pairwise_hamming_distance(descriptors1, descriptors2) + + if cross_check: + matched_keypoints1_index = np.argmin(distance, axis=1) + matched_keypoints2_index = np.argmin(distance, axis=0) + + matched_index = _binary_cross_check_loop(matched_keypoints1_index, + matched_keypoints2_index, + distance, threshold) + + matched_keypoint_pairs = np.zeros((matched_index.shape[0], 2, 2), + dtype=np.intp) + mask1 = matched_index[:, 0] + mask2 = matched_index[:, 1] + matched_keypoint_pairs[:, 0, :] = keypoints1[mask1] + matched_keypoint_pairs[:, 1, :] = keypoints2[mask2] + if return_mask: + return (matched_keypoint_pairs, mask1, mask2) + else: + return matched_keypoint_pairs + + else: + temp = distance > threshold + row_check = np.any(~temp, axis=1) + matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)] + matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), + dtype=np.intp) + matched_keypoint_pairs[:, 0, :] = keypoints1[row_check] + matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check] + mask1 = np.where(row_check == True) + mask2 = np.argmin(distance, axis=1)[row_check] + if return_mask: + return (matched_keypoint_pairs, mask1, mask2) + else: + return matched_keypoint_pairs diff --git a/skimage/feature/match_cy.pyx b/skimage/feature/match_cy.pyx new file mode 100644 index 00000000..8a1bb461 --- /dev/null +++ b/skimage/feature/match_cy.pyx @@ -0,0 +1,21 @@ +import numpy as np + + +def _binary_cross_check_loop(Py_ssize_t[:] matched_keypoints1_index, + Py_ssize_t[:] matched_keypoints2_index, + double[:, ::1] distance, double threshold): + cdef Py_ssize_t i + #matched_index = [] + + cdef Py_ssize_t count = 0 + cdef Py_ssize_t[:, ::1] matched_index = np.zeros((len(matched_keypoints1_index), 2), dtype=np.intp) + + for i in range(len(matched_keypoints1_index)): + if (matched_keypoints2_index[matched_keypoints1_index[i]] == i and + distance[i, matched_keypoints1_index[i]] < threshold): + #matched_index.append([i, matched_keypoints1_index[i]]) + matched_index[count, 0] = i + matched_index[count, 1] = matched_keypoints1_index[i] + count += 1 + + return np.asarray(matched_index[:count, :]) diff --git a/skimage/feature/orb_cy.pyx b/skimage/feature/orb_cy.pyx index bb317c7f..e7197797 100644 --- a/skimage/feature/orb_cy.pyx +++ b/skimage/feature/orb_cy.pyx @@ -9,7 +9,7 @@ import numpy as np from libc.math cimport sin, cos, M_PI, round -pos = np.loadtxt('orb_descriptor_positions.txt', dtype=np.int8) +pos = np.loadtxt("orb_descriptor_positions.txt", dtype=np.int8) pos0 = np.ascontiguousarray(pos[:, :2]) pos1 = np.ascontiguousarray(pos[:, 2:]) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 4f54faeb..9bd72ca3 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -16,6 +16,7 @@ def configuration(parent_package='', top_path=None): cython(['censure_cy.pyx'], working_path=base_path) cython(['orb_cy.pyx'], working_path=base_path) cython(['_brief_cy.pyx'], working_path=base_path) + cython(['match_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) @@ -27,6 +28,8 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('_brief_cy', sources=['_brief_cy.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('match_cy', sources=['match_cy.c'], + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_texture', sources=['_texture.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('_template', sources=['_template.c'],