This is a very quick post showing how to instantiate and compute descriptors in OpenCV. I include 4 binary descriptors (FREAK, BRIEF, BRISK, and ORB) and two non-binary descriptors (SIFT and SURF).
Environment:
import numpy as npimport matplotlib.pyplot as pltimport cv2
I took two pictures of my cat Charlie at slightly different angles. Then I selected by hand a pair of matching keypoints. Of course, I could have employed a detector for this task (FAST, AGAST, ORB, etc.), but I wanted to keep this post short and focused solely on descriptors. The two JPG images:
# first cat imageC1 = cv2.imread('cat1.jpg')C1_gr = cv2.cvtColor(C1, cv2.COLOR_BGR2GRAY)# second cat imageC2 = cv2.imread('cat2.jpg')C2_gr = cv2.cvtColor(C2, cv2.COLOR_BGR2GRAY)# hand-pick a keypointkp1 = (719, 769)kp2 = (903, 728)# show imagesmarker_style = {'markersize': 15,'markeredgewidth': 3,'markeredgecolor': 'w','markerfacecolor': 'None',}f, ax = plt.subplots(1, 2, figsize=(14, 7))ax[0].imshow(C1_gr, cmap='hot', interpolation='none')ax[0].plot(*kp1, 'o', **marker_style)ax[0].set_title('cat 1')ax[1].imshow(C2_gr, cmap='hot', interpolation='none')ax[1].plot(*kp2, 'o', **marker_style)ax[1].set_title('cat 2')
Then, for comparison, I generated a small assortment of random points for each image and paired them.
def rand_coords(n, img_shape):# usage: rand_coords(20, C1_gr.shape)return (np.random.rand(n, 2) @ np.diag(img_shape)).astype(int)# 8 random points in "cat1.jpg"rnd_pts_1 = np.array([[ 266, 1147],[ 896, 884],[ 385, 566],[ 468, 141],[ 889, 1084],[ 549, 1029],[ 987, 145],[ 419, 931]])# 8 random points in "cat2.jpg"rnd_pts_2 = np.array([[ 811, 980],[1176, 716],[ 259, 340],[ 745, 952],[ 265, 730],[ 852, 774],[1019, 1127],[ 660, 1110]])# show imagesf, ax = plt.subplots(1, 2, figsize=(14, 7))ax[0].imshow(C1_gr, cmap='gray', interpolation='none')ax[0].set_title('cat 1')ax[1].imshow(C2_gr, cmap='gray', interpolation='none')ax[1].set_title('cat 2')for i in range(rnd_pts_1.shape[0]):label = 'rand-' + str(i+1)_marker_style = dict(marker_style)_marker_style['markeredgecolor'] = 'C' + str(i if i < 7 else i+1)ax[0].plot(*rnd_pts_1[i], 'o', label=label, **_marker_style)ax[1].plot(*rnd_pts_2[i], 'o', label=label, **_marker_style)ax[1].legend(loc='upper left',bbox_to_anchor=(1.05, 1),borderaxespad=0.0,labelspacing=1,borderpad=1)
Everything is all prepared to compute descriptors and measure their distance from one another.
Code:
def get_bin_desc(extractor, img, kp, size=30):# extract a binary descriptor from the imaged = extractor.compute(img, [cv2.KeyPoint(*kp, size)])[1][0]return np.unpackbits(d)def get_hamming_dist(d1, d2):# return the Hamming distancereturn np.sum(np.logical_xor(d1, d2))def run_bin_test(extractor):# print Hamming distances between keypoints using the binary extractord1 = get_bin_desc(extractor, C1_gr, kp1)d2 = get_bin_desc(extractor, C2_gr, kp2)dist = get_hamming_dist(d1, d2)print('Hamming distance of pairs (out of {})\n'.format(len(d1)))print('Hand-picked: {:3d}'.format(dist))for i in range(rnd_pts_1.shape[0]):d1 = get_bin_desc(extractor, C1_gr, rnd_pts_1[i])d2 = get_bin_desc(extractor, C2_gr, rnd_pts_2[i])dist = get_hamming_dist(d1, d2)print('Random {}: {:3d}'.format(i+1, dist))extractor = cv2.xfeatures2d_FREAK.create()run_bin_test(extractor)
Output:
Hamming distance of pairs (out of 512)Hand-picked: 67Random 1: 220Random 2: 170Random 3: 207Random 4: 367Random 5: 165Random 6: 292Random 7: 272Random 8: 257
Code:
extractor = cv2.xfeatures2d_BriefDescriptorExtractor.create()run_bin_test(extractor)
Output:
Hamming distance of pairs (out of 256)Hand-picked: 37Random 1: 139Random 2: 161Random 3: 55Random 4: 95Random 5: 134Random 6: 158Random 7: 86Random 8: 123
Code:
extractor = cv2.BRISK.create()run_bin_test(extractor)
Output:
Hamming distance of pairs (out of 512)Hand-picked: 101Random 1: 296Random 2: 180Random 3: 171Random 4: 289Random 5: 210Random 6: 311Random 7: 192Random 8: 241
Code:
extractor = cv2.ORB.create()run_bin_test(extractor)
Output:
Hamming distance of pairs (out of 256)Hand-picked: 35Random 1: 167Random 2: 170Random 3: 92Random 4: 130Random 5: 102Random 6: 183Random 7: 91Random 8: 136
Code:
def get_non_bin_desc(extractor, img, kp, size=30):# extract a non-binary descriptor from the imagereturn extractor.compute(img, [cv2.KeyPoint(*kp, size)])[1][0]def get_l2_dist(d1, d2):# return the L2 distancereturn np.linalg.norm(d2-d1)def run_l2_test(extractor):# print L2 distances between keypoints using the non-binary extractord1 = get_non_bin_desc(extractor, C1_gr, kp1)d2 = get_non_bin_desc(extractor, C2_gr, kp2)dist = get_l2_dist(d1, d2)print('L2 distance of pairs\n')print('Hand-picked: {:.3f}'.format(dist))for i in range(rnd_pts_1.shape[0]):d1 = get_non_bin_desc(extractor, C1_gr, rnd_pts_1[i])d2 = get_non_bin_desc(extractor, C2_gr, rnd_pts_2[i])dist = get_l2_dist(d1, d2)print('Random {}: {:.3f}'.format(i+1, dist))extractor = cv2.xfeatures2d_SIFT.create()run_l2_test(extractor)
Output:
L2 distance of pairsHand-picked: 231.288Random 1: 413.424Random 2: 557.979Random 3: 425.087Random 4: 427.973Random 5: 520.956Random 6: 367.421Random 7: 523.169Random 8: 548.730
Code:
extractor = cv2.xfeatures2d_SURF.create()run_l2_test(extractor)
Output:
L2 distance of pairsHand-picked: 0.483Random 1: 0.741Random 2: 0.784Random 3: 0.940Random 4: 1.009Random 5: 1.009Random 6: 1.074Random 7: 0.778Random 8: 0.753
GET IN TOUCH
Have a project in mind?
Reach out directly to hello@humaticlabs.com or use the contact form.