def test_homog_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh, ori_thresh, full_homog_checks=True): r""" Test to see which keypoints the homography correctly maps Args: H (ndarray[float64_t, ndim=2]): homography/perspective matrix kpts1 (ndarray[float32_t, ndim=2]): keypoints kpts2 (ndarray[float32_t, ndim=2]): keypoints fm (list): list of feature matches as tuples (qfx, dfx) xy_thresh_sqrd (float): scale_thresh (float): ori_thresh (float): angle in radians full_homog_checks (bool): Returns: tuple: homog_tup1 CommandLine: python -m vtool.spatial_verification --test-test_homog_errors:0 --show python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 --no-full-homog-checks python -m vtool.spatial_verification --test-test_homog_errors:0 --show --no-full-homog-checks # -------------- # Shows (sorta) how inliers are computed python -m vtool.spatial_verification --test-test_homog_errors:1 --show python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --xy-thresh=.001 Example0: >>> # DISABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import plottool as pt >>> kpts1, kpts2, fm, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers() >>> H = estimate_refined_transform(kpts1, kpts2, fm, aff_inliers) >>> scale_thresh, ori_thresh = 2.0, 1.57 >>> full_homog_checks = not ut.get_argflag('--no-full-homog-checks') >>> homog_tup1 = test_homog_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh, ori_thresh, full_homog_checks) >>> homog_tup = (homog_tup1[0], homog_tup1[2]) >>> ut.quit_if_noshow() >>> pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, homog_tup=homog_tup) >>> ut.show_if_requested() Example1: >>> # DISABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import plottool as pt >>> kpts1, kpts2, fm_, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers() >>> H = estimate_refined_transform(kpts1, kpts2, fm_, aff_inliers) >>> scale_thresh, ori_thresh = 2.0, 1.57 >>> full_homog_checks = not ut.get_argflag('--no-full-homog-checks') >>> # ---------------- >>> # Take subset of feature matches >>> fm = fm_ >>> scale_err, xy_err, ori_err = \ ... ut.exec_func_src(test_homog_errors, globals(), locals(), ... 'scale_err, xy_err, ori_err'.split(', ')) >>> # we only care about checking out scale and orientation here. ignore bad xy points >>> xy_inliers_flag = np.less(xy_err, xy_thresh_sqrd) >>> scale_err[~xy_inliers_flag] = 0 >>> # filter >>> fm = fm_[np.array(scale_err).argsort()[::-1][:10]] >>> fm = fm_[np.array(scale_err).argsort()[::-1][:10]] >>> # Exec sourcecode >>> kpts1_m, kpts2_m, off_xy1_m, off_xy1_mt, dxy1_m, dxy1_mt, xy2_m, xy1_m, xy1_mt, scale_err, xy_err, ori_err = \ ... ut.exec_func_src(test_homog_errors, globals(), locals(), ... 'kpts1_m, kpts2_m, off_xy1_m, off_xy1_mt, dxy1_m, dxy1_mt, xy2_m, xy1_m, xy1_mt, scale_err, xy_err, ori_err'.split(', ')) >>> #--------------- >>> ut.quit_if_noshow() >>> pt.figure(fnum=1, pnum=(1, 2, 1), title='orig points and offset point') >>> segments_list1 = np.array(list(zip(xy1_m.T.tolist(), off_xy1_m.T.tolist()))) >>> pt.draw_line_segments(segments_list1, color=pt.LIGHT_BLUE) >>> pt.dark_background() >>> #--------------- >>> pt.figure(fnum=1, pnum=(1, 2, 2), title='transformed points and matching points') >>> #--------------- >>> # first have to make corresponding offset points >>> # Use reference point for scale and orientation tests >>> oris2_m = ktool.get_oris(kpts2_m) >>> scales2_m = ktool.get_scales(kpts2_m) >>> dxy2_m = np.vstack((np.sin(oris2_m), -np.cos(oris2_m))) >>> scaled_dxy2_m = dxy2_m * scales2_m[None, :] >>> off_xy2_m = xy2_m + scaled_dxy2_m >>> # Draw transformed semgents >>> segments_list2 = np.array(list(zip(xy2_m.T.tolist(), off_xy2_m.T.tolist()))) >>> pt.draw_line_segments(segments_list2, color=pt.GREEN) >>> # Draw corresponding matches semgents >>> segments_list3 = np.array(list(zip(xy1_mt.T.tolist(), off_xy1_mt.T.tolist()))) >>> pt.draw_line_segments(segments_list3, color=pt.RED) >>> # Draw matches between correspondences >>> segments_list4 = np.array(list(zip(xy1_mt.T.tolist(), xy2_m.T.tolist()))) >>> pt.draw_line_segments(segments_list4, color=pt.ORANGE) >>> pt.dark_background() >>> #--------------- >>> #vt.get _xy_axis_extents(kpts1_m) >>> #pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, homog_tup=homog_tup) >>> ut.show_if_requested() """ kpts1_m = kpts1.take(fm.T[0], axis=0) kpts2_m = kpts2.take(fm.T[1], axis=0) # Transform all xy1 matches to xy2 space xy1_m = ktool.get_xys(kpts1_m) #with ut.embed_on_exception_context: xy1_mt = ltool.transform_points_with_homography(H, xy1_m) #xy1_mt = ktool.transform_kpts_xys(H, kpts1_m) xy2_m = ktool.get_xys(kpts2_m) # --- Find (Squared) Homography Distance Error --- # You cannot test for scale or orientation easily here because # you no longer have an ellipse? (maybe, probably have a conic) when using a # projective transformation xy_err = dtool.L2_sqrd(xy1_mt.T, xy2_m.T) # Estimate final inliers #ut.embed() if full_homog_checks: # TODO: may need to use more than one reference point # Use reference point for scale and orientation tests oris1_m = ktool.get_oris(kpts1_m) scales1_m = ktool.get_scales(kpts1_m) # Get point offsets with unit length dxy1_m = np.vstack((np.sin(oris1_m), -np.cos(oris1_m))) scaled_dxy1_m = dxy1_m * scales1_m[None, :] off_xy1_m = xy1_m + scaled_dxy1_m # transform reference point off_xy1_mt = ltool.transform_points_with_homography(H, off_xy1_m) scaled_dxy1_mt = xy1_mt - off_xy1_mt scales1_mt = npl.norm(scaled_dxy1_mt, axis=0) #with warnings.catch_warnings(): # warnings.simplefilter("ignore") dxy1_mt = scaled_dxy1_mt / scales1_mt # adjust for gravity vector being 0 oris1_mt = np.arctan2(dxy1_mt[1], dxy1_mt[0]) - ktool.GRAVITY_THETA _det1_mt = scales1_mt**2 det2_m = ktool.get_sqrd_scales(kpts2_m) ori2_m = ktool.get_oris(kpts2_m) #xy_err = dtool.L2_sqrd(xy2_m.T, _xy1_mt.T) scale_err = dtool.det_distance(_det1_mt, det2_m) ori_err = dtool.ori_distance(oris1_mt, ori2_m) ### xy_inliers_flag = np.less(xy_err, xy_thresh_sqrd) scale_inliers_flag = np.less(scale_err, scale_thresh) ori_inliers_flag = np.less(ori_err, ori_thresh) hypo_inliers_flag = xy_inliers_flag # Try to re-use memory np.logical_and(hypo_inliers_flag, ori_inliers_flag, out=hypo_inliers_flag) np.logical_and(hypo_inliers_flag, scale_inliers_flag, out=hypo_inliers_flag) # Seems slower due to memory #hypo_inliers_flag = np.logical_and.reduce( # [xy_inliers_flag, ori_inliers_flag, scale_inliers_flag]) # this is also slower #hypo_inliers_flag = np.logical_and.reduce((xy_inliers_flag, #ori_inliers_flag, scale_inliers_flag), out=xy_inliers_flag) refined_inliers = np.where(hypo_inliers_flag)[0].astype(INDEX_DTYPE) refined_errors = (xy_err, ori_err, scale_err) else: refined_inliers = np.where( xy_err < xy_thresh_sqrd)[0].astype(INDEX_DTYPE) refined_errors = (xy_err, None, None) homog_tup1 = (refined_inliers, refined_errors, H) return homog_tup1
def test_homog_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh, ori_thresh, full_homog_checks=True): r""" Test to see which keypoints the homography correctly maps Args: H (ndarray[float64_t, ndim=2]): homography/perspective matrix kpts1 (ndarray[float32_t, ndim=2]): keypoints kpts2 (ndarray[float32_t, ndim=2]): keypoints fm (list): list of feature matches as tuples (qfx, dfx) xy_thresh_sqrd (float): scale_thresh (float): ori_thresh (float): angle in radians full_homog_checks (bool): Returns: tuple: homog_tup1 CommandLine: python -m vtool.spatial_verification --test-test_homog_errors:0 --show python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 --no-full-homog-checks python -m vtool.spatial_verification --test-test_homog_errors:0 --show --no-full-homog-checks # -------------- # Shows (sorta) how inliers are computed python -m vtool.spatial_verification --test-test_homog_errors:1 --show python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance --no-affine-invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:1 --show --rotation_invariance --xy-thresh=.001 python -m vtool.spatial_verification --test-test_homog_errors:0 --show --rotation_invariance --xy-thresh=.001 Example0: >>> # DISABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import plottool as pt >>> kpts1, kpts2, fm, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers() >>> H = estimate_refined_transform(kpts1, kpts2, fm, aff_inliers) >>> scale_thresh, ori_thresh = 2.0, 1.57 >>> full_homog_checks = not ut.get_argflag('--no-full-homog-checks') >>> homog_tup1 = test_homog_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh, ori_thresh, full_homog_checks) >>> homog_tup = (homog_tup1[0], homog_tup1[2]) >>> ut.quit_if_noshow() >>> pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, homog_tup=homog_tup) >>> ut.show_if_requested() Example1: >>> # DISABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import plottool as pt >>> kpts1, kpts2, fm_, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers() >>> H = estimate_refined_transform(kpts1, kpts2, fm_, aff_inliers) >>> scale_thresh, ori_thresh = 2.0, 1.57 >>> full_homog_checks = not ut.get_argflag('--no-full-homog-checks') >>> # ---------------- >>> # Take subset of feature matches >>> fm = fm_ >>> scale_err, xy_err, ori_err = \ ... ut.exec_func_src(test_homog_errors, globals(), locals(), ... 'scale_err, xy_err, ori_err'.split(', ')) >>> # we only care about checking out scale and orientation here. ignore bad xy points >>> xy_inliers_flag = np.less(xy_err, xy_thresh_sqrd) >>> scale_err[~xy_inliers_flag] = 0 >>> # filter >>> fm = fm_[np.array(scale_err).argsort()[::-1][:10]] >>> fm = fm_[np.array(scale_err).argsort()[::-1][:10]] >>> # Exec sourcecode >>> kpts1_m, kpts2_m, off_xy1_m, off_xy1_mt, dxy1_m, dxy1_mt, xy2_m, xy1_m, xy1_mt, scale_err, xy_err, ori_err = \ ... ut.exec_func_src(test_homog_errors, globals(), locals(), ... 'kpts1_m, kpts2_m, off_xy1_m, off_xy1_mt, dxy1_m, dxy1_mt, xy2_m, xy1_m, xy1_mt, scale_err, xy_err, ori_err'.split(', ')) >>> #--------------- >>> ut.quit_if_noshow() >>> pt.figure(fnum=1, pnum=(1, 2, 1), title='orig points and offset point') >>> segments_list1 = np.array(list(zip(xy1_m.T.tolist(), off_xy1_m.T.tolist()))) >>> pt.draw_line_segments(segments_list1, color=pt.LIGHT_BLUE) >>> pt.dark_background() >>> #--------------- >>> pt.figure(fnum=1, pnum=(1, 2, 2), title='transformed points and matching points') >>> #--------------- >>> # first have to make corresponding offset points >>> # Use reference point for scale and orientation tests >>> oris2_m = ktool.get_oris(kpts2_m) >>> scales2_m = ktool.get_scales(kpts2_m) >>> dxy2_m = np.vstack((np.sin(oris2_m), -np.cos(oris2_m))) >>> scaled_dxy2_m = dxy2_m * scales2_m[None, :] >>> off_xy2_m = xy2_m + scaled_dxy2_m >>> # Draw transformed semgents >>> segments_list2 = np.array(list(zip(xy2_m.T.tolist(), off_xy2_m.T.tolist()))) >>> pt.draw_line_segments(segments_list2, color=pt.GREEN) >>> # Draw corresponding matches semgents >>> segments_list3 = np.array(list(zip(xy1_mt.T.tolist(), off_xy1_mt.T.tolist()))) >>> pt.draw_line_segments(segments_list3, color=pt.RED) >>> # Draw matches between correspondences >>> segments_list4 = np.array(list(zip(xy1_mt.T.tolist(), xy2_m.T.tolist()))) >>> pt.draw_line_segments(segments_list4, color=pt.ORANGE) >>> pt.dark_background() >>> #--------------- >>> #vt.get _xy_axis_extents(kpts1_m) >>> #pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, homog_tup=homog_tup) >>> ut.show_if_requested() """ kpts1_m = kpts1.take(fm.T[0], axis=0) kpts2_m = kpts2.take(fm.T[1], axis=0) # Transform all xy1 matches to xy2 space xy1_m = ktool.get_xys(kpts1_m) #with ut.embed_on_exception_context: xy1_mt = ltool.transform_points_with_homography(H, xy1_m) #xy1_mt = ktool.transform_kpts_xys(H, kpts1_m) xy2_m = ktool.get_xys(kpts2_m) # --- Find (Squared) Homography Distance Error --- # You cannot test for scale or orientation easily here because # you no longer have an ellipse? (maybe, probably have a conic) when using a # projective transformation xy_err = dtool.L2_sqrd(xy1_mt.T, xy2_m.T) # Estimate final inliers #ut.embed() if full_homog_checks: # TODO: may need to use more than one reference point # Use reference point for scale and orientation tests oris1_m = ktool.get_oris(kpts1_m) scales1_m = ktool.get_scales(kpts1_m) # Get point offsets with unit length dxy1_m = np.vstack((np.sin(oris1_m), -np.cos(oris1_m))) scaled_dxy1_m = dxy1_m * scales1_m[None, :] off_xy1_m = xy1_m + scaled_dxy1_m # transform reference point off_xy1_mt = ltool.transform_points_with_homography(H, off_xy1_m) scaled_dxy1_mt = xy1_mt - off_xy1_mt scales1_mt = npl.norm(scaled_dxy1_mt, axis=0) #with warnings.catch_warnings(): # warnings.simplefilter("ignore") dxy1_mt = scaled_dxy1_mt / scales1_mt # adjust for gravity vector being 0 oris1_mt = np.arctan2(dxy1_mt[1], dxy1_mt[0]) - ktool.GRAVITY_THETA _det1_mt = scales1_mt ** 2 det2_m = ktool.get_sqrd_scales(kpts2_m) ori2_m = ktool.get_oris(kpts2_m) #xy_err = dtool.L2_sqrd(xy2_m.T, _xy1_mt.T) scale_err = dtool.det_distance(_det1_mt, det2_m) ori_err = dtool.ori_distance(oris1_mt, ori2_m) ### xy_inliers_flag = np.less(xy_err, xy_thresh_sqrd) scale_inliers_flag = np.less(scale_err, scale_thresh) ori_inliers_flag = np.less(ori_err, ori_thresh) hypo_inliers_flag = xy_inliers_flag # Try to re-use memory np.logical_and(hypo_inliers_flag, ori_inliers_flag, out=hypo_inliers_flag) np.logical_and(hypo_inliers_flag, scale_inliers_flag, out=hypo_inliers_flag) # Seems slower due to memory #hypo_inliers_flag = np.logical_and.reduce( # [xy_inliers_flag, ori_inliers_flag, scale_inliers_flag]) # this is also slower #hypo_inliers_flag = np.logical_and.reduce((xy_inliers_flag, #ori_inliers_flag, scale_inliers_flag), out=xy_inliers_flag) refined_inliers = np.where(hypo_inliers_flag)[0].astype(INDEX_DTYPE) refined_errors = (xy_err, ori_err, scale_err) else: refined_inliers = np.where(xy_err < xy_thresh_sqrd)[0].astype(INDEX_DTYPE) refined_errors = (xy_err, None, None) homog_tup1 = (refined_inliers, refined_errors, H) return homog_tup1
def _test_hypothesis_inliers(Aff, invVR1s_m, xy2_m, det2_m, ori2_m, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh): """ Critical section code. Inner loop of _test_hypothesis_inliers CommandLine: python -m vtool.spatial_verification --test-_test_hypothesis_inliers Example: >>> # ENABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> from vtool.spatial_verification import _test_hypothesis_inliers # NOQA >>> import vtool.tests.dummy as dummy >>> import vtool.keypoint as ktool >>> _kw1 = dict(seed=12, damping=1.2, wh_stride=(30, 30)) >>> _kw2 = dict(seed=24, damping=1.6, wh_stride=(30, 30)) >>> kpts1 = dummy.perterbed_grid_kpts(**_kw1).astype(np.float64) >>> kpts2 = dummy.perterbed_grid_kpts(**_kw2).astype(np.float64) >>> fm = dummy.make_dummy_fm(len(kpts1)).astype(np.int32) >>> kpts1_m = kpts1[fm.T[0]] >>> kpts2_m = kpts2[fm.T[1]] >>> xy_thresh_sqrd = np.float64(.009) ** 2 >>> scale_thresh_sqrd = np.float64(2) >>> ori_thresh = np.float64(TAU / 4) >>> # Get keypoints to project in matrix form >>> #invVR1s_m = ktool.get_invV_mats(kpts1_m, with_trans=True, with_ori=True) >>> #print(invVR1s_m[0]) >>> invVR1s_m = ktool.get_invVR_mats3x3(kpts1_m) >>> RV1s_m = ktool.get_RV_mats_3x3(kpts1_m) >>> invVR2s_m = ktool.get_invVR_mats3x3(kpts2_m) >>> # The transform from kp1 to kp2 is given as: >>> Aff_mats = matrix_multiply(invVR2s_m, RV1s_m) >>> Aff = Aff_mats[0] >>> # Get components to test projects against >>> xy2_m = ktool.get_invVR_mats_xys(invVR2s_m) >>> det2_m = ktool.get_sqrd_scales(kpts2_m) >>> ori2_m = ktool.get_invVR_mats_oris(invVR2s_m) >>> output = _test_hypothesis_inliers(Aff, invVR1s_m, xy2_m, det2_m, ori2_m, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh) >>> result = ut.hashstr(output) >>> print(result) +%q&%je52nlyli5& Timeit: %timeit xy_err < xy_thresh_sqrd %timeit np.less(xy_err, xy_thresh_sqrd) """ # Map keypoints from image 1 onto image 2 invVR1s_mt = matrix_multiply(Aff, invVR1s_m) # Get projection components _xy1_mt = ktool.get_invVR_mats_xys(invVR1s_mt) _det1_mt = ktool.get_invVR_mats_sqrd_scale(invVR1s_mt) _ori1_mt = ktool.get_invVR_mats_oris(invVR1s_mt) ## Check for projection errors xy_err = dtool.L2_sqrd(xy2_m.T, _xy1_mt.T, dtype=SV_DTYPE) scale_err = dtool.det_distance(_det1_mt, det2_m) ori_err = dtool.ori_distance(_ori1_mt, ori2_m) # Mark keypoints which are inliers to this hypothosis xy_inliers_flag = np.less(xy_err, xy_thresh_sqrd) scale_inliers_flag = np.less(scale_err, scale_thresh_sqrd) ori_inliers_flag = np.less(ori_err, ori_thresh) #np.logical_and(xy_inliers_flag, scale_inliers_flag) # TODO Add uniqueness of matches constraint #hypo_inliers_flag = np.empty(xy_inliers_flag.size, dtype=np.bool) hypo_inliers_flag = xy_inliers_flag # Try to re-use memory np.logical_and(hypo_inliers_flag, ori_inliers_flag, out=hypo_inliers_flag) np.logical_and(hypo_inliers_flag, scale_inliers_flag, out=hypo_inliers_flag) #hypo_inliers_flag = np.logical_and.reduce( # [xy_inliers_flag, ori_inliers_flag, scale_inliers_flag]) # this is also slower hypo_inliers = np.where(hypo_inliers_flag)[0] hypo_errors = (xy_err, ori_err, scale_err) return hypo_inliers, hypo_errors