def testdata_matching_affine_inliers_normalized(): tup = testdata_matching_affine_inliers() kpts1, kpts2, fm, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = tup kpts1_ma = kpts1.take(fm.T[0].take(aff_inliers), axis=0) kpts2_ma = kpts2.take(fm.T[1].take(aff_inliers), axis=0) #kpts1_ma, kpts2_ma, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers() # Matching affine inliers xy1_ma = ktool.get_xys(kpts1_ma) xy2_ma = ktool.get_xys(kpts2_ma) # Matching affine inliers normalized xy1_man, T1 = ltool.whiten_xy_points(xy1_ma) xy2_man, T2 = ltool.whiten_xy_points(xy2_ma) return xy1_man, xy2_man, rchip1, rchip2, T1, T2
def kpts_dlen_sqrd(tx): kpts2 = topx2_kpts[tx] aid = topx2_aid[tx] fm = aid2_fm[aid] x_m, y_m = ktool.get_xys(kpts2[fm[:, 1]]) dlensqrd = (x_m.max() - x_m.min()) ** 2 + (y_m.max() - y_m.min()) ** 2 return dlensqrd
def kpts_dlen_sqrd(tx): kpts2 = topx2_kpts[tx] aid = topx2_aid[tx] fm = aid2_fm[aid] x_m, y_m = ktool.get_xys(kpts2[fm[:, 1]]) dlensqrd = (x_m.max() - x_m.min())**2 + (y_m.max() - y_m.min())**2 return dlensqrd
def perterb_kpts(kpts, xy_std=None, invV_std=None, ori_std=None, damping=None, seed=None, **kwargs): """ Adds normally distributed pertibations to keypoints """ # TODO: Move to ktool # Get standard deviations of pertibations if xy_std is None: xy_std = ktool.get_xys(kpts).std(1) + mtool.eps if invV_std is None: invV_std = ktool.get_invVs(kpts).std(1) + mtool.eps if ori_std is None: ori_std = ktool.get_oris(kpts).std() + mtool.eps xy_std = np.array(xy_std, dtype=ktool.KPTS_DTYPE) invV_std = np.array(invV_std, dtype=ktool.KPTS_DTYPE) if damping is not None: xy_std /= damping invV_std /= damping ori_std /= damping if seed is not None: np.random.seed(seed) # Create normally distributed pertibations xy_aug = np.random.normal(0, scale=xy_std, size=(len(kpts), 2)).astype(ktool.KPTS_DTYPE) try: invV_aug = np.random.normal(0, scale=invV_std, size=(len(kpts), 3)).astype(ktool.KPTS_DTYPE) except ValueError as ex: ut.printex(ex, key_list=[(type, 'invV_std')]) raise ori_aug = np.random.normal(0, scale=ori_std, size=(len(kpts), 1)).astype(ktool.KPTS_DTYPE) # Augment keypoints aug = np.hstack((xy_aug, invV_aug, ori_aug)) kpts_ = kpts + aug # Ensure keypoint feasibility kpts_ = force_kpts_feasibility(kpts_) #print(ut.dict_str({key: type(val) if not isinstance(val, np.ndarray) else val.dtype for key, val in locals().items()})) #assert kpts_.dtype == ktool.KPTS_DTYPE, 'bad cast somewhere kpts_.dtype=%r' % (kpts_.dtype) return kpts_
def get_normalized_affine_inliers(kpts1, kpts2, fm, aff_inliers): """ returns xy-inliers that are normalized to have a mean of 0 and std of 1 as well as the transformations so the inverse can be taken """ fm_affine = fm.take(aff_inliers, axis=0) # Get corresponding points and shapes kpts1_ma = kpts1.take(fm_affine.T[0], axis=0) kpts2_ma = kpts2.take(fm_affine.T[1], axis=0) #kpts1_ma = kpts1.take(fm_affine.T[0], axis=0) #kpts2_ma = kpts2.take(fm_affine.T[1], axis=0) # Normalize affine inliers xy locations xy1_ma = ktool.get_xys(kpts1_ma) xy2_ma = ktool.get_xys(kpts2_ma) xy1_man, T1 = ltool.whiten_xy_points(xy1_ma) xy2_man, T2 = ltool.whiten_xy_points(xy2_ma) return xy1_man, xy2_man, T1, T2
def orientation_actors(kpts, H=None): """ creates orientation actors w.r.t. the gravity vector """ import vtool.keypoint as ktool try: # Get xy diretion of the keypoint orientations _xs, _ys = ktool.get_xys(kpts) _iv11s, _iv21s, _iv22s = ktool.get_invVs(kpts) _oris = ktool.get_oris(kpts) # mpl's 0 ori == (-tau / 4) w.r.t GRAVITY_THETA abs_oris = _oris + ktool.GRAVITY_THETA _sins = np.sin(abs_oris) _coss = np.cos(abs_oris) # The following is essentially # invV.dot(R) _dxs = _coss * _iv11s _dys = _coss * _iv21s + _sins * _iv22s # ut.embed() # if H is not None: # # adjust for homogrpahy # import vtool as vt # _xs, _ys = vt.transform_points_with_homography(H, np.vstack((_xs, _ys))) # _dxs, _dys = vt.transform_points_with_homography(H, np.vstack((_dxs, _dys))) # head_width_list = np.log(_iv11s * _iv22s) / 5 head_width_list = np.ones(len(_iv11s)) / 10 kwargs = { 'length_includes_head': True, 'shape': 'full', 'overhang': 0, 'head_starts_at_zero': False, } if H is not None: kwargs['transform'] = HomographyTransform(H) ori_actors = [ mpl.patches.FancyArrow(x, y, dx, dy, head_width=hw, **kwargs) for (x, y, dx, dy, hw) in zip(_xs, _ys, _dxs, _dys, head_width_list) ] except ValueError as ex: print('\n[mplkp.2] !!! ERROR %s: ' % str(ex)) print('_oris.shape = %r' % (_oris.shape, )) # print('x, y, dx, dy = %r' % ((x, y, dx, dy),)) print('_dxs = %r' % (_dxs, )) print('_dys = %r' % (_dys, )) print('_xs = %r' % (_xs, )) print('_ys = %r' % (_ys, )) raise return ori_actors
def orientation_actors(kpts, H=None): """ creates orientation actors w.r.t. the gravity vector """ try: # Get xy diretion of the keypoint orientations _xs, _ys = ktool.get_xys(kpts) _iv11s, _iv21s, _iv22s = ktool.get_invVs(kpts) _oris = ktool.get_oris(kpts) # mpl's 0 ori == (-tau / 4) w.r.t GRAVITY_THETA abs_oris = _oris + ktool.GRAVITY_THETA _sins = np.sin(abs_oris) _coss = np.cos(abs_oris) # The following is essentially # invV.dot(R) _dxs = _coss * _iv11s _dys = _coss * _iv21s + _sins * _iv22s #ut.embed() #if H is not None: # # adjust for homogrpahy # import vtool as vt # _xs, _ys = vt.transform_points_with_homography(H, np.vstack((_xs, _ys))) # _dxs, _dys = vt.transform_points_with_homography(H, np.vstack((_dxs, _dys))) #head_width_list = np.log(_iv11s * _iv22s) / 5 head_width_list = np.ones(len(_iv11s)) / 10 kwargs = { 'length_includes_head': True, 'shape': 'full', 'overhang': 0, 'head_starts_at_zero': False, } if H is not None: kwargs['transform'] = HomographyTransform(H) ori_actors = [mpl.patches.FancyArrow(x, y, dx, dy, head_width=hw, **kwargs) for (x, y, dx, dy, hw) in zip(_xs, _ys, _dxs, _dys, head_width_list)] except ValueError as ex: print('\n[mplkp.2] !!! ERROR %s: ' % str(ex)) print('_oris.shape = %r' % (_oris.shape,)) print('x, y, dx, dy = %r' % ((x, y, dx, dy),)) print('_dxs = %r' % (_dxs,)) print('_dys = %r' % (_dys,)) print('_xs = %r' % (_xs,)) print('_ys = %r' % (_ys,)) raise return ori_actors
def test_affine_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh): """ used for refinement as opposed to initial estimation """ kpts1_m = kpts1.take(fm.T[0], axis=0) kpts2_m = kpts2.take(fm.T[1], axis=0) invVR1s_m = ktool.get_invVR_mats3x3(kpts1_m) xy2_m = ktool.get_xys(kpts2_m) det2_m = ktool.get_sqrd_scales(kpts2_m) ori2_m = ktool.get_oris(kpts2_m) refined_inliers, refined_errors = _test_hypothesis_inliers( H, invVR1s_m, xy2_m, det2_m, ori2_m, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh) refined_tup1 = (refined_inliers, refined_errors, H) return refined_tup1
def draw_keypoints(ax, kpts_, scale_factor=1.0, offset=(0.0, 0.0), rotation=0.0, ell=True, pts=False, rect=False, eig=False, ori=False, sifts=None, siftkw={}, H=None, **kwargs): """ draws keypoints extracted by pyhesaff onto a matplotlib axis FIXME: There is probably a matplotlib bug here. If you specify two different alphas in a collection, whatever the last alpha was gets applied to everything Args: ax (mpl.Axes): kpts (ndarray): keypoints [[x, y, a, c, d, theta], ...] scale_factor (float): offset (tuple): rotation (float): ell (bool): pts (bool): rect (bool): eig (bool): ori (bool): sifts (None): References: http://stackoverflow.com/questions/28401788/transforms-non-affine-patch CommandLine: python -m wbia.plottool.mpl_keypoint draw_keypoints --show Example: >>> # ENABLE_DOCTEST >>> from wbia.plottool.mpl_keypoint import * # NOQA >>> from wbia.plottool.mpl_keypoint import _draw_patches, _draw_pts # NOQA >>> import wbia.plottool as pt >>> import vtool as vt >>> imgBGR = vt.get_star_patch(jitter=True) >>> H = np.array([[1, 0, 0], [.5, 2, 0], [0, 0, 1]]) >>> H = np.array([[.8, 0, 0], [0, .8, 0], [0, 0, 1]]) >>> H = None >>> TAU = 2 * np.pi >>> kpts_ = vt.make_test_image_keypoints(imgBGR, scale=.5, skew=2, theta=TAU / 8.0) >>> scale_factor=1.0 >>> #offset=(0.0, -4.0) >>> offset=(0.0, 0.0) >>> rotation=0.0 >>> ell=True >>> pts=True >>> rect=True >>> eig=True >>> ori=True >>> # make random sifts >>> sifts = mpl_sift.testdata_sifts() >>> siftkw = {} >>> kwargs = dict(ori_color=[0, 1, 0], rect_color=[0, 0, 1], >>> eig_color=[1, 1, 0], pts_size=.1) >>> w, h = imgBGR.shape[0:2][::-1] >>> imgBGR_ = imgBGR if H is None else vt.warpAffine( >>> imgBGR, H, (int(w * .8), int(h * .8))) >>> fig, ax = pt.imshow(imgBGR_ * 255) >>> draw_keypoints(ax, kpts_, scale_factor, offset, rotation, ell, pts, ... rect, eig, ori, sifts, siftkw, H=H, **kwargs) >>> pt.iup() >>> pt.show_if_requested() """ import vtool.keypoint as ktool if kpts_.shape[1] == 2: # pad out structure if only xy given kpts = np.zeros((len(kpts_), 6)) kpts[:, 0:2] = kpts_ kpts[:, 2] = 1 kpts[:, 4] = 1 kpts_ = kpts if scale_factor is None: scale_factor = 1.0 # print('[mpl_keypoint.draw_keypoints] kwargs = ' + ut.repr2(kwargs)) # ellipse and point properties pts_size = kwargs.get('pts_size', 2) pts_alpha = kwargs.get('pts_alpha', 1.0) ell_alpha = kwargs.get('ell_alpha', 1.0) ell_linewidth = kwargs.get('ell_linewidth', 2) ell_color = kwargs.get('ell_color', None) if ell_color is None: ell_color = [1, 0, 0] # colors pts_color = kwargs.get('pts_color', ell_color) rect_color = kwargs.get('rect_color', ell_color) eig_color = kwargs.get('eig_color', ell_color) ori_color = kwargs.get('ori_color', ell_color) # linewidths eig_linewidth = kwargs.get('eig_linewidth', ell_linewidth) rect_linewidth = kwargs.get('rect_linewidth', ell_linewidth) ori_linewidth = kwargs.get('ori_linewidth', ell_linewidth) # Offset keypoints assert len(kpts_) > 0, 'cannot draw no keypoints1' kpts = ktool.offset_kpts(kpts_, offset, scale_factor) assert len(kpts) > 0, 'cannot draw no keypoints2' # Build list of keypoint shape transforms from unit circles to ellipes invVR_aff2Ds = get_invVR_aff2Ds(kpts, H=H) try: if sifts is not None: # SIFT descriptors pass_props( kwargs, siftkw, 'bin_color', 'arm1_color', 'arm2_color', 'arm1_lw', 'arm2_lw', 'stroke', 'arm_alpha', 'arm_alpha', 'multicolored_arms', ) mpl_sift.draw_sifts(ax, sifts, invVR_aff2Ds, **siftkw) if rect: # Bounding Rectangles rect_patches = rectangle_actors(invVR_aff2Ds) _draw_patches(ax, rect_patches, rect_color, ell_alpha, rect_linewidth) if ell: # Keypoint shape ell_patches = ellipse_actors(invVR_aff2Ds) _draw_patches(ax, ell_patches, ell_color, ell_alpha, ell_linewidth) if eig: # Shape eigenvectors eig_patches = eigenvector_actors(invVR_aff2Ds) _draw_patches(ax, eig_patches, eig_color, ell_alpha, eig_linewidth) if ori: # Keypoint orientation ori_patches = orientation_actors(kpts, H=H) _draw_patches(ax, ori_patches, ori_color, ell_alpha, ori_linewidth, ori_color) if pts: # Keypoint locations _xs, _ys = ktool.get_xys(kpts) if H is not None: # adjust for homogrpahy import vtool as vt _xs, _ys = vt.transform_points_with_homography( H, np.vstack((_xs, _ys))) pts_patches = _draw_pts(ax, _xs, _ys, pts_size, pts_color, pts_alpha) if pts_patches is not None: _draw_patches(ax, pts_patches, 'none', pts_alpha, pts_size, pts_color) except ValueError as ex: ut.printex(ex, '\n[mplkp] !!! ERROR') # print('_oris.shape = %r' % (_oris.shape,)) # print('_xs.shape = %r' % (_xs.shape,)) # print('_iv11s.shape = %r' % (_iv11s.shape,)) raise
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 get_affine_inliers(kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh): """ Estimates inliers deterministically using elliptical shapes Compute all transforms from kpts1 to kpts2 (enumerate all hypothesis) We transform from chip1 -> chip2 The determinants are squared keypoint scales FROM PERDOCH 2009:: H = inv(Aj).dot(Rj.T).dot(Ri).dot(Ai) H = inv(Aj).dot(Ai) The input invVs = perdoch.invA's CommandLine: python -m vtool.spatial_verification --test-get_affine_inliers Example: >>> # ENABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import vtool.tests.dummy as dummy >>> import vtool.keypoint as ktool >>> kpts1, kpts2 = dummy.get_dummy_kpts_pair((100, 100)) >>> fm = dummy.make_dummy_fm(len(kpts1)).astype(np.int32) >>> fs = np.ones(len(fm), dtype=np.float64) >>> xy_thresh_sqrd = ktool.KPTS_DTYPE(.009) ** 2 >>> scale_thresh_sqrd = ktool.KPTS_DTYPE(2) >>> ori_thresh = ktool.KPTS_DTYPE(TAU / 4) >>> output = get_affine_inliers(kpts1, kpts2, fm, fs, xy_thresh_sqrd, >>> scale_thresh_sqrd, ori_thresh) >>> result = ut.hashstr(output) >>> print(result) 89kz8nh6p+66t!+u Ignore:: from vtool.spatial_verification import * # NOQA import vtool.tests.dummy as dummy import vtool.keypoint as ktool kpts1, kpts2 = dummy.get_dummy_kpts_pair((100, 100)) a = kpts1[fm.T[0]] b = kpts1.take(fm.T[0]) align = fm.dtype.itemsize * fm.shape[1] align2 = [fm.dtype.itemsize, fm.dtype.itemsize] viewtype1 = np.dtype(np.void, align) viewtype2 = np.dtype(np.int32, align2) c = np.ascontiguousarray(fm).view(viewtype1) fm_view = np.ascontiguousarray(fm).view(viewtype1) qfx = fm.view(np.dtype(np.int32 np.int32.itemsize)) dfx = fm.view(np.dtype(np.int32, np.int32.itemsize)) d = np.ascontiguousarray(c).view(viewtype2) fm.view(np.dtype(np.void, align)) np.ascontiguousarray(fm).view(np.dtype((np.void, Z.dtype.itemsize * Z.shape[1]))) """ #http://ipython-books.github.io/featured-01/ kpts1_m = kpts1.take(fm.T[0], axis=0) kpts2_m = kpts2.take(fm.T[1], axis=0) # Get keypoints to project in matrix form #invVR2s_m = ktool.get_invV_mats(kpts2_m, with_trans=True, with_ori=True) #invVR1s_m = ktool.get_invV_mats(kpts1_m, with_trans=True, with_ori=True) invVR2s_m = ktool.get_invVR_mats3x3(kpts2_m) invVR1s_m = ktool.get_invVR_mats3x3(kpts1_m) RV1s_m = ktool.invert_invV_mats(invVR1s_m) # 539 us # BUILD ALL HYPOTHESIS TRANSFORMS: The transform from kp1 to kp2 is: Aff_mats = matrix_multiply(invVR2s_m, RV1s_m) # Get components to test projects against xy2_m = ktool.get_xys(kpts2_m) det2_m = ktool.get_sqrd_scales(kpts2_m) ori2_m = ktool.get_oris(kpts2_m) # SLOWER EQUIVALENT # RV1s_m = ktool.get_V_mats(kpts1_m, with_trans=True, with_ori=True) # 5.2 ms # xy2_m = ktool.get_invVR_mats_xys(invVR2s_m) # ori2_m = ktool.get_invVR_mats_oris(invVR2s_m) # assert np.all(ktool.get_oris(kpts2_m) == ktool.get_invVR_mats_oris(invVR2s_m)) # assert np.all(ktool.get_xys(kpts2_m) == ktool.get_invVR_mats_xys(invVR2s_m)) # The previous versions of this function were all roughly comparable. # The for loop one was the slowest. I'm deciding to go with the one # where there is no internal function definition. It was moderately faster, # and it gives us access to profile that function inliers_and_errors_list = [ _test_hypothesis_inliers(Aff, invVR1s_m, xy2_m, det2_m, ori2_m, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh) for Aff in Aff_mats ] aff_inliers_list = [tup[0] for tup in inliers_and_errors_list] aff_errors_list = [tup[1] for tup in inliers_and_errors_list] return aff_inliers_list, aff_errors_list, Aff_mats
def draw_keypoints(ax, kpts_, scale_factor=1.0, offset=(0.0, 0.0), rotation=0.0, ell=True, pts=False, rect=False, eig=False, ori=False, sifts=None, siftkw={}, H=None, **kwargs): """ draws keypoints extracted by pyhesaff onto a matplotlib axis FIXME: There is probably a matplotlib bug here. If you specify two different alphas in a collection, whatever the last alpha was gets applied to everything Args: ax (mpl.Axes): kpts (ndarray): keypoints [[x, y, a, c, d, theta], ...] scale_factor (float): offset (tuple): rotation (float): ell (bool): pts (bool): rect (bool): eig (bool): ori (bool): sifts (None): References: http://stackoverflow.com/questions/28401788/using-homogeneous-transforms-non-affine-with-matplotlib-patches CommandLine: python -m plottool.mpl_keypoint --test-draw_keypoints --show Example: >>> # ENABLE_DOCTEST >>> from plottool.mpl_keypoint import * # NOQA >>> from plottool.mpl_keypoint import _draw_patches, _draw_pts # NOQA >>> import plottool as pt >>> import vtool as vt >>> imgBGR = vt.get_star_patch(jitter=True) >>> H = np.array([[1, 0, 0], [.5, 2, 0], [0, 0, 1]]) >>> H = None >>> TAU = 2 * np.pi >>> kpts_ = vt.make_test_image_keypoints(imgBGR, scale=.5, skew=2, theta=TAU / 8.0) >>> scale_factor=1.0 >>> offset=(0.0, 0.0) >>> rotation=0.0 >>> ell=True >>> pts=True >>> rect=True >>> eig=True >>> ori=True >>> # make random sifts >>> sifts = mpl_sift.testdata_sifts() >>> siftkw = {} >>> kwargs = dict(ori_color=[0, 1, 0], rect_color=[0, 0, 1], eig_color=[1, 1, 0], pts_size=.1) >>> w, h = imgBGR.shape[0:2][::-1] >>> imgBGR_ = imgBGR if H is None else vt.warpAffine(imgBGR, H, (w * 2, h * 2)) >>> fig, ax = pt.imshow(imgBGR_ * 255) >>> draw_keypoints(ax, kpts_, scale_factor, offset, rotation, ell, pts, ... rect, eig, ori, sifts, siftkw, H=H, **kwargs) >>> pt.iup() >>> pt.show_if_requested() """ if kpts_.shape[1] == 2: # pad out structure if only xy given kpts = np.zeros((len(kpts_), 6)) kpts[:, 0:2] = kpts_ kpts[:, 2] = 1 kpts[:, 4] = 1 kpts_ = kpts if scale_factor is None: scale_factor = 1.0 #print('[mpl_keypoint.draw_keypoints] kwargs = ' + ut.dict_str(kwargs)) # ellipse and point properties pts_size = kwargs.get('pts_size', 2) pts_alpha = kwargs.get('pts_alpha', 1.0) ell_alpha = kwargs.get('ell_alpha', 1.0) ell_linewidth = kwargs.get('ell_linewidth', 2) ell_color = kwargs.get('ell_color', None) if ell_color is None: ell_color = [1, 0, 0] # colors pts_color = kwargs.get('pts_color', ell_color) rect_color = kwargs.get('rect_color', ell_color) eig_color = kwargs.get('eig_color', ell_color) ori_color = kwargs.get('ori_color', ell_color) # linewidths eig_linewidth = kwargs.get('eig_linewidth', ell_linewidth) rect_linewidth = kwargs.get('rect_linewidth', ell_linewidth) ori_linewidth = kwargs.get('ori_linewidth', ell_linewidth) # Offset keypoints assert len(kpts_) > 0, 'cannot draw no keypoints1' kpts = ktool.offset_kpts(kpts_, offset, scale_factor) assert len(kpts) > 0, 'cannot draw no keypoints2' # Build list of keypoint shape transforms from unit circles to ellipes invVR_aff2Ds = get_invVR_aff2Ds(kpts, H=H) try: if sifts is not None: # SIFT descriptors pass_props(kwargs, siftkw, 'bin_color', 'arm1_color', 'arm2_color', 'arm1_lw', 'arm2_lw', 'arm_alpha', 'arm_alpha', 'multicolored_arms') mpl_sift.draw_sifts(ax, sifts, invVR_aff2Ds, **siftkw) if rect: # Bounding Rectangles rect_patches = rectangle_actors(invVR_aff2Ds) _draw_patches(ax, rect_patches, rect_color, ell_alpha, rect_linewidth) if ell: # Keypoint shape ell_patches = ellipse_actors(invVR_aff2Ds) _draw_patches(ax, ell_patches, ell_color, ell_alpha, ell_linewidth) if eig: # Shape eigenvectors eig_patches = eigenvector_actors(invVR_aff2Ds) _draw_patches(ax, eig_patches, eig_color, ell_alpha, eig_linewidth) if ori: # Keypoint orientation ori_patches = orientation_actors(kpts, H=H) _draw_patches(ax, ori_patches, ori_color, ell_alpha, ori_linewidth, ori_color) if pts: # Keypoint locations _xs, _ys = ktool.get_xys(kpts) if H is not None: # adjust for homogrpahy import vtool as vt _xs, _ys = vt.transform_points_with_homography(H, np.vstack((_xs, _ys))) pts_patches = _draw_pts(ax, _xs, _ys, pts_size, pts_color, pts_alpha) if pts_patches is not None: _draw_patches(ax, pts_patches, 'none', pts_alpha, pts_size, pts_color) except ValueError as ex: ut.printex(ex, '\n[mplkp] !!! ERROR') #print('_oris.shape = %r' % (_oris.shape,)) #print('_xs.shape = %r' % (_xs.shape,)) #print('_iv11s.shape = %r' % (_iv11s.shape,)) raise
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 get_affine_inliers(kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh): """ Estimates inliers deterministically using elliptical shapes Compute all transforms from kpts1 to kpts2 (enumerate all hypothesis) We transform from chip1 -> chip2 The determinants are squared keypoint scales FROM PERDOCH 2009:: H = inv(Aj).dot(Rj.T).dot(Ri).dot(Ai) H = inv(Aj).dot(Ai) The input invVs = perdoch.invA's CommandLine: python -m vtool.spatial_verification --test-get_affine_inliers Example: >>> # ENABLE_DOCTEST >>> from vtool.spatial_verification import * # NOQA >>> import vtool.tests.dummy as dummy >>> import vtool.keypoint as ktool >>> kpts1, kpts2 = dummy.get_dummy_kpts_pair((100, 100)) >>> fm = dummy.make_dummy_fm(len(kpts1)).astype(np.int32) >>> fs = np.ones(len(fm), dtype=np.float64) >>> xy_thresh_sqrd = ktool.KPTS_DTYPE(.009) ** 2 >>> scale_thresh_sqrd = ktool.KPTS_DTYPE(2) >>> ori_thresh = ktool.KPTS_DTYPE(TAU / 4) >>> output = get_affine_inliers(kpts1, kpts2, fm, fs, xy_thresh_sqrd, >>> scale_thresh_sqrd, ori_thresh) >>> result = ut.hashstr(output) >>> print(result) 89kz8nh6p+66t!+u Ignore:: from vtool.spatial_verification import * # NOQA import vtool.tests.dummy as dummy import vtool.keypoint as ktool kpts1, kpts2 = dummy.get_dummy_kpts_pair((100, 100)) a = kpts1[fm.T[0]] b = kpts1.take(fm.T[0]) align = fm.dtype.itemsize * fm.shape[1] align2 = [fm.dtype.itemsize, fm.dtype.itemsize] viewtype1 = np.dtype(np.void, align) viewtype2 = np.dtype(np.int32, align2) c = np.ascontiguousarray(fm).view(viewtype1) fm_view = np.ascontiguousarray(fm).view(viewtype1) qfx = fm.view(np.dtype(np.int32 np.int32.itemsize)) dfx = fm.view(np.dtype(np.int32, np.int32.itemsize)) d = np.ascontiguousarray(c).view(viewtype2) fm.view(np.dtype(np.void, align)) np.ascontiguousarray(fm).view(np.dtype((np.void, Z.dtype.itemsize * Z.shape[1]))) """ #http://ipython-books.github.io/featured-01/ kpts1_m = kpts1.take(fm.T[0], axis=0) kpts2_m = kpts2.take(fm.T[1], axis=0) # Get keypoints to project in matrix form #invVR2s_m = ktool.get_invV_mats(kpts2_m, with_trans=True, with_ori=True) #invVR1s_m = ktool.get_invV_mats(kpts1_m, with_trans=True, with_ori=True) invVR2s_m = ktool.get_invVR_mats3x3(kpts2_m) invVR1s_m = ktool.get_invVR_mats3x3(kpts1_m) RV1s_m = ktool.invert_invV_mats(invVR1s_m) # 539 us # BUILD ALL HYPOTHESIS TRANSFORMS: The transform from kp1 to kp2 is: Aff_mats = matrix_multiply(invVR2s_m, RV1s_m) # Get components to test projects against xy2_m = ktool.get_xys(kpts2_m) det2_m = ktool.get_sqrd_scales(kpts2_m) ori2_m = ktool.get_oris(kpts2_m) # SLOWER EQUIVALENT # RV1s_m = ktool.get_V_mats(kpts1_m, with_trans=True, with_ori=True) # 5.2 ms # xy2_m = ktool.get_invVR_mats_xys(invVR2s_m) # ori2_m = ktool.get_invVR_mats_oris(invVR2s_m) # assert np.all(ktool.get_oris(kpts2_m) == ktool.get_invVR_mats_oris(invVR2s_m)) # assert np.all(ktool.get_xys(kpts2_m) == ktool.get_invVR_mats_xys(invVR2s_m)) # The previous versions of this function were all roughly comparable. # The for loop one was the slowest. I'm deciding to go with the one # where there is no internal function definition. It was moderately faster, # and it gives us access to profile that function inliers_and_errors_list = [_test_hypothesis_inliers(Aff, invVR1s_m, xy2_m, det2_m, ori2_m, xy_thresh_sqrd, scale_thresh_sqrd, ori_thresh) for Aff in Aff_mats] aff_inliers_list = [tup[0] for tup in inliers_and_errors_list] aff_errors_list = [tup[1] for tup in inliers_and_errors_list] return aff_inliers_list, aff_errors_list, Aff_mats