def assign_spatially_constrained_matches(chip2_dlen_sqrd, kpts1, kpts2, H, fx2_to_fx1, fx2_to_dist, match_xy_thresh, norm_xy_bounds=(0.0, 1.0)): """ Args: chip2_dlen_sqrd (dict): kpts1 (ndarray[float32_t, ndim=2]): keypoints kpts2 (ndarray[float32_t, ndim=2]): keypoints H (ndarray[float64_t, ndim=2]): homography/perspective matrix that maps image1 space into image2 space fx2_to_fx1 (ndarray): image2s nearest feature indices in image1 fx2_to_dist (ndarray): match_xy_thresh (float): norm_xy_bounds (tuple): Returns: tuple: assigntup( fx2_match, - matching feature indices in image 2 fx1_match, - matching feature indices in image 1 fx1_norm, - normmalizing indices in image 1 match_dist, - descriptor distances between fx2_match and fx1_match norm_dist, - descriptor distances between fx2_match and fx1_norm ) CommandLine: python -m vtool.matching --test-assign_spatially_constrained_matches Example: >>> # ENABLE_DOCTEST >>> from vtool.matching import * # NOQA >>> kpts1 = np.array([[ 6., 4., 15.84, 4.66, 7.24, 0. ], ... [ 9., 3., 20.09, 5.76, 6.2 , 0. ], ... [ 1., 1., 12.96, 1.73, 8.77, 0. ],]) >>> kpts2 = np.array([[ 2., 1., 12.11, 0.38, 8.04, 0. ], ... [ 5., 1., 22.4 , 1.31, 5.04, 0. ], ... [ 6., 1., 19.25, 1.74, 4.72, 0. ],]) >>> match_xy_thresh = .37 >>> chip2_dlen_sqrd = 1400 >>> norm_xy_bounds = (0.0, 1.0) >>> H = np.array([[ 2, 0, 0], >>> [ 0, 1, 0], >>> [ 0, 0, 1]]) >>> fx2_to_fx1 = np.array([[2, 1, 0], >>> [0, 1, 2], >>> [2, 0, 1]], dtype=np.int32) >>> fx2_to_dist = np.array([[.40, .80, .85], >>> [.30, .50, .60], >>> [.80, .90, .91]], dtype=np.float32) >>> # verify results >>> assigntup = assign_spatially_constrained_matches(chip2_dlen_sqrd, kpts1, kpts2, H, fx2_to_fx1, fx2_to_dist, match_xy_thresh, norm_xy_bounds) >>> fx2_match, fx1_match, fx1_norm, match_dist, norm_dist = assigntup >>> result = ut.list_str(assigntup, precision=3) >>> print(result) ( np.array([0, 1, 2], dtype=np.int32), np.array([2, 0, 2], dtype=np.int32), np.array([1, 1, 0], dtype=np.int32), np.array([ 0.4, 0.3, 0.8], dtype=np.float32), np.array([ 0.8, 0.5, 0.9], dtype=np.float32), ) Example: assigns spatially constrained vsone match using results of nearest neighbors. """ import vtool as vt index_dtype = fx2_to_fx1.dtype # Find spatial errors of keypoints under current homography (kpts1 mapped into image2 space) fx2_to_xyerr_sqrd = ktool.get_match_spatial_squared_error(kpts1, kpts2, H, fx2_to_fx1) fx2_to_xyerr = np.sqrt(fx2_to_xyerr_sqrd) fx2_to_xyerr_norm = np.divide(fx2_to_xyerr, np.sqrt(chip2_dlen_sqrd)) # Find matches and normalizers that satisfy spatial constraints fx2_to_valid_match = ut.inbounds(fx2_to_xyerr_norm, 0.0, match_xy_thresh, eq=True) fx2_to_valid_normalizer = ut.inbounds(fx2_to_xyerr_norm, *norm_xy_bounds, eq=True) fx2_to_fx1_match_col = vt.find_first_true_indices(fx2_to_valid_match) fx2_to_fx1_norm_col = vt.find_next_true_indices(fx2_to_valid_normalizer, fx2_to_fx1_match_col) assert fx2_to_fx1_match_col != fx2_to_fx1_norm_col, 'normlizers are matches!' fx2_to_hasmatch = [pos is not None for pos in fx2_to_fx1_norm_col] # IMAGE 2 Matching Features fx2_match = np.where(fx2_to_hasmatch)[0].astype(index_dtype) match_col_list = np.array(ut.take(fx2_to_fx1_match_col, fx2_match), dtype=fx2_match.dtype) norm_col_list = np.array(ut.take(fx2_to_fx1_norm_col, fx2_match), dtype=fx2_match.dtype) # We now have 2d coordinates into fx2_to_fx1 # Covnert into 1d coordinates for flat indexing into fx2_to_fx1 _match_index_2d = np.vstack((fx2_match, match_col_list)) _norm_index_2d = np.vstack((fx2_match, norm_col_list)) _shape2d = fx2_to_fx1.shape match_index_1d = np.ravel_multi_index(_match_index_2d, _shape2d) norm_index_1d = np.ravel_multi_index(_norm_index_2d, _shape2d) # Find initial matches # IMAGE 1 Matching Features fx1_match = fx2_to_fx1.take(match_index_1d) fx1_norm = fx2_to_fx1.take(norm_index_1d) # compute constrained ratio score match_dist = fx2_to_dist.take(match_index_1d) norm_dist = fx2_to_dist.take(norm_index_1d) # package and return assigntup = fx2_match, fx1_match, fx1_norm, match_dist, norm_dist return assigntup
def plot_scores(hs, qcx_list, fnum=1): print('[dev] plot_scores(fnum=%r)' % fnum) topN_gt = 1 topN_ranks = 3 qcx2_res = get_qcx2_res(hs, qcx_list) data_scores = [] # matching scores data_qpairs = [] # query info (qcx, cx) data_gtranks = [] # query info (gtrank) # Append all scores to a giant list for res in qcx2_res.itervalues(): # Get gt scores first qcx = res.qcx top_cxs = np.array(res.topN_cxs(hs, N=topN_ranks, only_gt=False)) gt_cxs = np.array(res.topN_cxs(hs, N=topN_gt, only_gt=True)) top_scores = res.cx2_score[top_cxs] np.intersect1d(top_cxs, gt_cxs) # list of ranks if score is ground truth otherwise -1 isgt_ranks = [tx if cx in set(gt_cxs) else -1 for (tx, cx) in enumerate(top_cxs)] qcx_pairs = [(hs.cx2_cid(qcx), hs.cx2_cid(cx)) for cx in top_cxs] # Append all scores data_scores.extend(top_scores.tolist()) data_qpairs.extend(qcx_pairs) data_gtranks.extend(isgt_ranks) data_scores = np.array(data_scores) data_qpairs = np.array(data_qpairs) data_gtranks = np.array(data_gtranks) data_sortx = data_scores.argsort() sorted_scores = data_scores[data_sortx] # Draw and info rank_colorbounds = [ ((-1, 0), df2.GRAY), ((0, 1), df2.TRUE_GREEN), ((1, 5), df2.UNKNOWN_PURP), ((5, None), df2.FALSE_RED), ] print('[dev] matching chipscore stats: ' + utool.stats_str(data_scores)) df2.figure(fnum=fnum, doclf=True, docla=True) # Finds the knee df2.plot(sorted_scores, color=df2.ORANGE, label='all scores') # Plot results with ranks within (low, high) bounds colorbounds_iter = reversed(list(enumerate(rank_colorbounds))) for count, ((low, high), rankX_color) in colorbounds_iter: datarank_flag = utool.inbounds(data_gtranks, low, high) rankX_xs = np.where(datarank_flag[data_sortx])[0] rankX_ys = sorted_scores[rankX_xs] if high is None: rankX_label = '%d <= gt rank' % low else: rankX_label = '%d <= gt rank < %d' % (low, high) if len(rankX_ys) > 0: df2.plot(rankX_xs, rankX_ys, 'o', color=rankX_color, label=rankX_label, alpha=.5) rowid = qcx2_res.itervalues().next().rowid df2.set_logyscale_from_data(data_scores) df2.set_xlabel('chipmatch index') df2.dark_background() df2.set_figtitle('matching scores\n' + rowid) df2.legend(loc='upper left') df2.iup() fnum += 1 score_table = np.vstack((data_scores, data_gtranks, data_qpairs.T)).T score_table = score_table[data_sortx[::-1]] column_labels = ['score', 'gtrank', 'qcid', 'cid'] header = 'score_table\nuid=%r' % rowid column_type = [float, int, int, int] csv_txt = utool.util_csv.numpy_to_csv(score_table, column_labels, header, column_type) print(csv_txt) #utool.embed() return fnum
def assign_spatially_constrained_matches(chip2_dlen_sqrd, kpts1, kpts2, H, fx2_to_fx1, fx2_to_dist, match_xy_thresh, norm_xy_bounds=(0.0, 1.0)): """ Args: chip2_dlen_sqrd (dict): kpts1 (ndarray[float32_t, ndim=2]): keypoints kpts2 (ndarray[float32_t, ndim=2]): keypoints H (ndarray[float64_t, ndim=2]): homography/perspective matrix that maps image1 space into image2 space fx2_to_fx1 (ndarray): image2s nearest feature indices in image1 fx2_to_dist (ndarray): match_xy_thresh (float): norm_xy_bounds (tuple): Returns: tuple: assigntup( fx2_match, - matching feature indices in image 2 fx1_match, - matching feature indices in image 1 fx1_norm, - normmalizing indices in image 1 match_dist, - descriptor distances between fx2_match and fx1_match norm_dist, - descriptor distances between fx2_match and fx1_norm ) CommandLine: python -m vtool.matching --test-assign_spatially_constrained_matches Example: >>> # ENABLE_DOCTEST >>> from vtool.matching import * # NOQA >>> kpts1 = np.array([[ 6., 4., 15.84, 4.66, 7.24, 0. ], ... [ 9., 3., 20.09, 5.76, 6.2 , 0. ], ... [ 1., 1., 12.96, 1.73, 8.77, 0. ],]) >>> kpts2 = np.array([[ 2., 1., 12.11, 0.38, 8.04, 0. ], ... [ 5., 1., 22.4 , 1.31, 5.04, 0. ], ... [ 6., 1., 19.25, 1.74, 4.72, 0. ],]) >>> match_xy_thresh = .37 >>> chip2_dlen_sqrd = 1400 >>> norm_xy_bounds = (0.0, 1.0) >>> H = np.array([[ 2, 0, 0], >>> [ 0, 1, 0], >>> [ 0, 0, 1]]) >>> fx2_to_fx1 = np.array([[2, 1, 0], >>> [0, 1, 2], >>> [2, 0, 1]], dtype=np.int32) >>> fx2_to_dist = np.array([[.40, .80, .85], >>> [.30, .50, .60], >>> [.80, .90, .91]], dtype=np.float32) >>> # verify results >>> assigntup = assign_spatially_constrained_matches(chip2_dlen_sqrd, kpts1, kpts2, H, fx2_to_fx1, fx2_to_dist, match_xy_thresh, norm_xy_bounds) >>> fx2_match, fx1_match, fx1_norm, match_dist, norm_dist = assigntup >>> result = ut.list_str(assigntup, precision=3) >>> print(result) ( np.array([0, 1, 2], dtype=np.int32), np.array([2, 0, 2], dtype=np.int32), np.array([1, 1, 0], dtype=np.int32), np.array([ 0.4, 0.3, 0.8], dtype=np.float32), np.array([ 0.8, 0.5, 0.9], dtype=np.float32), ) Example: assigns spatially constrained vsone match using results of nearest neighbors. """ import vtool as vt index_dtype = fx2_to_fx1.dtype # Find spatial errors of keypoints under current homography (kpts1 mapped into image2 space) fx2_to_xyerr_sqrd = ktool.get_match_spatial_squared_error( kpts1, kpts2, H, fx2_to_fx1) fx2_to_xyerr = np.sqrt(fx2_to_xyerr_sqrd) fx2_to_xyerr_norm = np.divide(fx2_to_xyerr, np.sqrt(chip2_dlen_sqrd)) # Find matches and normalizers that satisfy spatial constraints fx2_to_valid_match = ut.inbounds(fx2_to_xyerr_norm, 0.0, match_xy_thresh, eq=True) fx2_to_valid_normalizer = ut.inbounds(fx2_to_xyerr_norm, *norm_xy_bounds, eq=True) fx2_to_fx1_match_col = vt.find_first_true_indices(fx2_to_valid_match) fx2_to_fx1_norm_col = vt.find_next_true_indices(fx2_to_valid_normalizer, fx2_to_fx1_match_col) assert fx2_to_fx1_match_col != fx2_to_fx1_norm_col, 'normlizers are matches!' fx2_to_hasmatch = [pos is not None for pos in fx2_to_fx1_norm_col] # IMAGE 2 Matching Features fx2_match = np.where(fx2_to_hasmatch)[0].astype(index_dtype) match_col_list = np.array(ut.take(fx2_to_fx1_match_col, fx2_match), dtype=fx2_match.dtype) norm_col_list = np.array(ut.take(fx2_to_fx1_norm_col, fx2_match), dtype=fx2_match.dtype) # We now have 2d coordinates into fx2_to_fx1 # Covnert into 1d coordinates for flat indexing into fx2_to_fx1 _match_index_2d = np.vstack((fx2_match, match_col_list)) _norm_index_2d = np.vstack((fx2_match, norm_col_list)) _shape2d = fx2_to_fx1.shape match_index_1d = np.ravel_multi_index(_match_index_2d, _shape2d) norm_index_1d = np.ravel_multi_index(_norm_index_2d, _shape2d) # Find initial matches # IMAGE 1 Matching Features fx1_match = fx2_to_fx1.take(match_index_1d) fx1_norm = fx2_to_fx1.take(norm_index_1d) # compute constrained ratio score match_dist = fx2_to_dist.take(match_index_1d) norm_dist = fx2_to_dist.take(norm_index_1d) # package and return assigntup = fx2_match, fx1_match, fx1_norm, match_dist, norm_dist return assigntup