1 #!/usr/bin/env python 2 3 ''' 4 Feature-based image matching sample. 5 6 Note, that you will need the https://github.com/Itseez/opencv_contrib repo for SIFT and SURF 7 8 USAGE 9 find_obj.py [--feature=<sift|surf|orb|akaze|brisk>[-flann]] [ <image1> <image2> ] 10 11 --feature - Feature to use. Can be sift, surf, orb or brisk. Append '-flann' 12 to feature name to use Flann-based matcher instead bruteforce. 13 14 Press left mouse button on a feature point to see its matching point. 15 ''' 16 17 import numpy as np 18 import cv2 19 from common import anorm, getsize 20 21 FLANN_INDEX_KDTREE = 1 # bug: flann enums are missing 22 FLANN_INDEX_LSH = 6 23 24 25 def init_feature(name): 26 chunks = name.split('-') 27 if chunks[0] == 'sift': 28 detector = cv2.xfeatures2d.SIFT_create() 29 norm = cv2.NORM_L2 30 elif chunks[0] == 'surf': 31 detector = cv2.xfeatures2d.SURF_create(800) 32 norm = cv2.NORM_L2 33 elif chunks[0] == 'orb': 34 detector = cv2.ORB_create(400) 35 norm = cv2.NORM_HAMMING 36 elif chunks[0] == 'akaze': 37 detector = cv2.AKAZE_create() 38 norm = cv2.NORM_HAMMING 39 elif chunks[0] == 'brisk': 40 detector = cv2.BRISK_create() 41 norm = cv2.NORM_HAMMING 42 else: 43 return None, None 44 if 'flann' in chunks: 45 if norm == cv2.NORM_L2: 46 flann_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) 47 else: 48 flann_params= dict(algorithm = FLANN_INDEX_LSH, 49 table_number = 6, # 12 50 key_size = 12, # 20 51 multi_probe_level = 1) #2 52 matcher = cv2.FlannBasedMatcher(flann_params, {}) # bug : need to pass empty dict (#1329) 53 else: 54 matcher = cv2.BFMatcher(norm) 55 return detector, matcher 56 57 58 def filter_matches(kp1, kp2, matches, ratio = 0.75): 59 mkp1, mkp2 = [], [] 60 for m in matches: 61 if len(m) == 2 and m[0].distance < m[1].distance * ratio: 62 m = m[0] 63 mkp1.append( kp1[m.queryIdx] ) 64 mkp2.append( kp2[m.trainIdx] ) 65 p1 = np.float32([kp.pt for kp in mkp1]) 66 p2 = np.float32([kp.pt for kp in mkp2]) 67 kp_pairs = zip(mkp1, mkp2) 68 return p1, p2, kp_pairs 69 70 def explore_match(win, img1, img2, kp_pairs, status = None, H = None): 71 h1, w1 = img1.shape[:2] 72 h2, w2 = img2.shape[:2] 73 vis = np.zeros((max(h1, h2), w1+w2), np.uint8) 74 vis[:h1, :w1] = img1 75 vis[:h2, w1:w1+w2] = img2 76 vis = cv2.cvtColor(vis, cv2.COLOR_GRAY2BGR) 77 78 if H is not None: 79 corners = np.float32([[0, 0], [w1, 0], [w1, h1], [0, h1]]) 80 corners = np.int32( cv2.perspectiveTransform(corners.reshape(1, -1, 2), H).reshape(-1, 2) + (w1, 0) ) 81 cv2.polylines(vis, [corners], True, (255, 255, 255)) 82 83 if status is None: 84 status = np.ones(len(kp_pairs), np.bool_) 85 p1 = np.int32([kpp[0].pt for kpp in kp_pairs]) 86 p2 = np.int32([kpp[1].pt for kpp in kp_pairs]) + (w1, 0) 87 88 green = (0, 255, 0) 89 red = (0, 0, 255) 90 white = (255, 255, 255) 91 kp_color = (51, 103, 236) 92 for (x1, y1), (x2, y2), inlier in zip(p1, p2, status): 93 if inlier: 94 col = green 95 cv2.circle(vis, (x1, y1), 2, col, -1) 96 cv2.circle(vis, (x2, y2), 2, col, -1) 97 else: 98 col = red 99 r = 2 100 thickness = 3 101 cv2.line(vis, (x1-r, y1-r), (x1+r, y1+r), col, thickness) 102 cv2.line(vis, (x1-r, y1+r), (x1+r, y1-r), col, thickness) 103 cv2.line(vis, (x2-r, y2-r), (x2+r, y2+r), col, thickness) 104 cv2.line(vis, (x2-r, y2+r), (x2+r, y2-r), col, thickness) 105 vis0 = vis.copy() 106 for (x1, y1), (x2, y2), inlier in zip(p1, p2, status): 107 if inlier: 108 cv2.line(vis, (x1, y1), (x2, y2), green) 109 110 cv2.imshow(win, vis) 111 def onmouse(event, x, y, flags, param): 112 cur_vis = vis 113 if flags & cv2.EVENT_FLAG_LBUTTON: 114 cur_vis = vis0.copy() 115 r = 8 116 m = (anorm(p1 - (x, y)) < r) | (anorm(p2 - (x, y)) < r) 117 idxs = np.where(m)[0] 118 kp1s, kp2s = [], [] 119 for i in idxs: 120 (x1, y1), (x2, y2) = p1[i], p2[i] 121 col = (red, green)[status[i]] 122 cv2.line(cur_vis, (x1, y1), (x2, y2), col) 123 kp1, kp2 = kp_pairs[i] 124 kp1s.append(kp1) 125 kp2s.append(kp2) 126 cur_vis = cv2.drawKeypoints(cur_vis, kp1s, flags=4, color=kp_color) 127 cur_vis[:,w1:] = cv2.drawKeypoints(cur_vis[:,w1:], kp2s, flags=4, color=kp_color) 128 129 cv2.imshow(win, cur_vis) 130 cv2.setMouseCallback(win, onmouse) 131 return vis 132 133 134 if __name__ == '__main__': 135 print __doc__ 136 137 import sys, getopt 138 opts, args = getopt.getopt(sys.argv[1:], '', ['feature=']) 139 opts = dict(opts) 140 feature_name = opts.get('--feature', 'sift') 141 try: 142 fn1, fn2 = args 143 except: 144 fn1 = '../data/box.png' 145 fn2 = '../data/box_in_scene.png' 146 147 img1 = cv2.imread(fn1, 0) 148 img2 = cv2.imread(fn2, 0) 149 detector, matcher = init_feature(feature_name) 150 151 if img1 is None: 152 print 'Failed to load fn1:', fn1 153 sys.exit(1) 154 155 if img2 is None: 156 print 'Failed to load fn2:', fn2 157 sys.exit(1) 158 159 if detector is None: 160 print 'unknown feature:', feature_name 161 sys.exit(1) 162 163 print 'using', feature_name 164 165 kp1, desc1 = detector.detectAndCompute(img1, None) 166 kp2, desc2 = detector.detectAndCompute(img2, None) 167 print 'img1 - %d features, img2 - %d features' % (len(kp1), len(kp2)) 168 169 def match_and_draw(win): 170 print 'matching...' 171 raw_matches = matcher.knnMatch(desc1, trainDescriptors = desc2, k = 2) #2 172 p1, p2, kp_pairs = filter_matches(kp1, kp2, raw_matches) 173 if len(p1) >= 4: 174 H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 5.0) 175 print '%d / %d inliers/matched' % (np.sum(status), len(status)) 176 else: 177 H, status = None, None 178 print '%d matches found, not enough for homography estimation' % len(p1) 179 180 vis = explore_match(win, img1, img2, kp_pairs, status, H) 181 182 match_and_draw('find_obj') 183 cv2.waitKey() 184 cv2.destroyAllWindows() 185