# -*- coding: utf-8 -*-
"""
Spatial verification of keypoint matches
Notation::
1_m = img1_matches; 2_m = img2_matches
x and y are locations, invV is the elliptical shapes.
fx are the original feature indexes (used for making sure 1 keypoint isn't assigned to 2)
Look Into::
Standard
skimage.transform
http://stackoverflow.com/questions/11462781/fast-2d-rigid-body-transformations-in-numpy-scipy
skimage.transform.fast_homography(im, H)
FIXME:
is it scaled_thresh or scaled_thresh_sqrd
References:
http://ags.cs.uni-kl.de/fileadmin/inf_ags/3dcv-ws11-12/3DCV_WS11-12_lec04.pdf
http://www.imgfsr.com/CVPR2011/Tutorial6/RANSAC_CVPR2011.pdf
http://szeliski.org/Book/drafts/SzeliskiBook_20100903_draft.pdf Page 317
Notes:
Invariants of affine transforms - parallel lines, ratios of parallel lengths, ratios of areas
Invariants of homographies - crossâratio of four points on a line (ratio of ratio)
"""
from __future__ import absolute_import, division, print_function
from six.moves import range
import warnings # NOQA
import six # NOQA
import utool as ut
import numpy as np
import numpy.linalg as npl
import scipy.sparse as sps
import scipy.sparse.linalg as spsl
from numpy.core.umath_tests import matrix_multiply
import vtool.keypoint as ktool
import vtool.linalg as ltool
import vtool.distance
import cv2
from .util_math import TAU
try:
from vtool import sver_c_wrapper
HAVE_SVER_C_WRAPPER = not ut.get_argflag('--no-c')
except Exception as ex:
HAVE_SVER_C_WRAPPER = False
if ut.VERBOSE:
ut.printex(ex, 'please build the sver c wrapper (run with --rebuild-sver')
if False:
raise
VERBOSE_SVER = ut.get_argflag('--verb-sver')
SV_DTYPE = np.float64
INDEX_DTYPE = np.int32
[docs]def build_lstsqrs_Mx9(xy1_mn, xy2_mn):
""" Builds the M x 9 least squares matrix
CommandLine:
python -m vtool.spatial_verification --test-build_lstsqrs_Mx9
Example:
>>> # DISABLE_DOCTEST
>>> # xdoctest: +SKIP
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair()
>>> xy1_mn = ktool.get_xys(kpts1).astype(np.float64)
>>> xy2_mn = ktool.get_xys(kpts2).astype(np.float64)
>>> Mx9 = build_lstsqrs_Mx9(xy1_mn, xy2_mn)
>>> import ubelt as ub
>>> result = (ub.repr2(Mx9[0:2], suppress_small=True, precision=2, with_dtype=True))
>>> print(result)
np.array([[ 0.00e+00, 0.00e+00, 0.00e+00, -3.20e+01, -2.72e+01,
-1.00e+00, 8.82e+02, 7.49e+02, 2.76e+01],
[ 3.20e+01, 2.72e+01, 1.00e+00, 0.00e+00, 0.00e+00,
0.00e+00, -1.09e+03, -9.28e+02, -3.42e+01]], dtype=np.float64)
References:
http://dip.sun.ac.za/~stefan/TW793/attach/notes/homography_estimation.pdf
http://szeliski.org/Book/drafts/SzeliskiBook_20100903_draft.pdf Page 317
http://vision.ece.ucsb.edu/~zuliani/Research/RANSAC/docs/RANSAC4Dummies.pdf page 53
"""
x1_mn = xy1_mn[0]
y1_mn = xy1_mn[1]
x2_mn = xy2_mn[0]
y2_mn = xy2_mn[1]
num_pts = x1_mn.shape[0]
# Mx9 = np.zeros((2 * num_pts, 9), dtype=SV_DTYPE)
Mx9 = np.empty((2 * num_pts, 9), dtype=SV_DTYPE)
for ix in range(num_pts): # Loop over inliers
# Concatenate all 2x9 matrices into an Mx9 matrix
u2 = x2_mn[ix]
v2 = y2_mn[ix]
x1 = x1_mn[ix]
y1 = y1_mn[ix]
(d, e, f) = (-x1, -y1, -1)
(g, h, i) = (v2 * x1, v2 * y1, v2)
(j, k, l) = (x1, y1, 1)
(p, q, r) = (-u2 * x1, -u2 * y1, -u2)
Mx9[ix * 2] = (0, 0, 0, d, e, f, g, h, i)
Mx9[ix * 2 + 1] = (j, k, l, 0, 0, 0, p, q, r)
return Mx9
[docs]def try_svd(M):
"""
CommandLine:
python -m vtool.spatial_verification try_svd
Example:
>>> # SLOW_DOCTEST
>>> # xdoctest: +SKIP
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> rng = np.random.RandomState(42)
>>> num = 1000
>>> xy1_mn = rng.randn(2, num)
>>> xy2_mn = rng.randn(2, num)
>>> M = build_lstsqrs_Mx9(xy1_mn, xy2_mn)
>>> print('M.shape = %r' % (M.shape,))
>>> USV = npl.svd(M, full_matrices=True, compute_uv=True)
>>> USV = try_svd(M)
Example:
>>> # SLOW_DOCTEST
>>> # xdoctest: +SKIP
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> num = np.ceil(np.sqrt(2000))
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair(wh_num=(num, num))
>>> xy1_mn = ktool.get_xys(kpts1).astype(np.float64)
>>> xy2_mn = ktool.get_xys(kpts2).astype(np.float64)
>>> M = build_lstsqrs_Mx9(xy1_mn, xy2_mn)
>>> print('M.shape = %r' % (M.shape,))
>>> USV = npl.svd(M, full_matrices=True, compute_uv=True)
>>> USV = try_svd(M)
"""
# if M.shape[0] > 2500:
# # hack to prevent bug in lapack
# M = M[:2500]
try:
USV = npl.svd(M, full_matrices=True, compute_uv=True)
except MemoryError as ex:
ut.printex(ex, '[sver] Caught MemErr during full SVD. Trying sparse SVD.')
M_sparse = sps.lil_matrix(M)
USV = spsl.svds(M_sparse)
except npl.LinAlgError as ex:
ut.printex(ex, '[sver] svd did not converge')
raise
except Exception as ex:
ut.printex(ex, '[sver] svd error')
raise
return USV
[docs]def build_affine_lstsqrs_Mx6(xy1_man, xy2_man):
"""
CURRENTLY NOT WORKING
CommandLine:
python -m vtool.spatial_verification --test-build_affine_lstsqrs_Mx6
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair()
>>> xy1_man = ktool.get_xys(kpts1).astype(np.float64)
>>> xy2_man = ktool.get_xys(kpts2).astype(np.float64)
>>> Mx6 = build_affine_lstsqrs_Mx6(xy1_man, xy2_man)
>>> import ubelt as ub
>>> print(ub.repr2(Mx6))
>>> result = ut.hashstr(Mx6)
>>> print(result)
Sympy:
import sympy as sym
x1, y1, x2, y2 = sym.symbols('x1, y1, x2, y2')
A = sym.Matrix([
[x1, y1, 0, 0, 1, 0],
[ 0, 0, x1, y1, 0, 1],
])
b = sym.Matrix([[x2], [y2]])
x = (A.T.multiply(A)).inv().multiply(A.T.multiply(b))
x = (A.T.multiply(A)).pinv().multiply(A.T.multiply(b))
References:
https://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf page 22
"""
x1_mn = xy1_man[0]
y1_mn = xy1_man[1]
x2_mn = xy2_man[0]
y2_mn = xy2_man[1]
num_pts = x1_mn.shape[0]
Mx6 = np.empty((2 * num_pts, 6), dtype=SV_DTYPE)
b = np.empty((2 * num_pts, 1), dtype=SV_DTYPE)
for ix in range(num_pts): # Loop over inliers
# Concatenate all 2x9 matrices into an Mx6 matrix
x1 = x1_mn[ix]
x2 = x2_mn[ix]
y1 = y1_mn[ix]
y2 = y2_mn[ix]
Mx6[ix * 2] = (x1, y1, 0, 0, 1, 0)
Mx6[ix * 2 + 1] = (0, 0, x1, y1, 0, 1)
b[ix * 2] = x2
b[ix * 2 + 1] = y2
# npl.solve(b, Mx6)
U, s, Vt = try_svd(Mx6)
if False:
S_ = np.zeros((len(U), len(Vt)))
S_[np.diag_indices(len(s))] = s
U.dot(S_).dot(Vt)
assert np.allclose(U.dot(S_).dot(Vt), Mx6)
assert not np.allclose(U.dot(S_).dot(Vt.T), Mx6)
# Inefficient, but I think the math works
# We want to solve Ax=b (where A is the Mx6 in this case)
# Ax = b
# (U S V.T) x = b
# x = (U.T inv(S) V) b
Sinv = np.zeros((len(Vt), len(U)))
Sinv[np.diag_indices(len(s))] = 1 / s
a = Vt.T.dot(Sinv).dot(U.T).dot(b).T[0]
A = np.array([[a[0], a[1], a[4]], [a[2], a[3], a[5]], [0, 0, 1],])
return A
# TODO FIXME
# return Mx6
[docs]def compute_affine(xy1_man, xy2_man):
"""
Args:
xy1_mn (ndarray[ndim=2]): xy points in image1
xy2_mn (ndarray[ndim=2]): corresponding xy points in image 2
Returns:
ndarray[shape=(3,3)]: A - affine matrix
CommandLine:
python -m vtool.spatial_verification --test-compute_affine:1 --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> import vtool.keypoint as ktool
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair()
>>> xy1_mn = ktool.get_xys(kpts1)
>>> xy2_mn = ktool.get_xys(kpts2)
>>> A = compute_affine(xy1_mn, xy1_mn)
>>> result =str(A)
>>> result = np.array_str(A, precision=2)
>>> print(result)
Example1:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> import vtool.keypoint as ktool
>>> import wbia.plottool as pt
>>> xy1_man, xy2_man, rchip1, rchip2, T1, T2 = testdata_matching_affine_inliers_normalized()
>>> A_prime = compute_affine(xy1_man, xy2_man)
>>> A = npl.solve(T2, A_prime).dot(T1)
>>> A /= A[2, 2]
>>> result = np.array_str(A, precision=2)
>>> print(result)
>>> # xdoctest: +REQUIRES(--show)
>>> rchip2_blendA = pt.draw_sv.get_blended_chip(rchip1, rchip2, A)
>>> pt.imshow(rchip2_blendA)
>>> ut.show_if_requested()
[[ 1.19e+00 -1.06e-02 -4.49e+01]
[ -2.22e-01 1.12e+00 -2.78e+01]
[ 0.00e+00 0.00e+00 1.00e+00]]
"""
# Solve for the nullspace of the Mx6 matrix (solves least squares)
# Mx6 = build_affine_lstsqrs_Mx6(xy1_man, xy2_man)
A = build_affine_lstsqrs_Mx6(xy1_man, xy2_man)
# U, S, V = try_svd(Mx6)
# a = V[5] # Hack for Cython.wraparound(False)
# a = a / a[-1]
# A = np.array([
# [a[0], a[1], a[4]],
# [a[2], a[3], a[5]],
# [ 0, 0, 1],
# ])
return A
[docs]def compute_homog(xy1_mn, xy2_mn):
"""
Generate 6 degrees of freedom homography transformation
Computes homography from normalized (0 to 1) point correspondences
from 2 --> 1
(database->query)
Args:
xy1_mn (ndarray[ndim=2]): xy points in image1
xy2_mn (ndarray[ndim=2]): corresponding xy points in image 2
Returns:
ndarray[shape=(3,3)]: H - homography matrix
CommandLine:
python -m vtool.spatial_verification --test-compute_homog:1 --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.keypoint as ktool
>>> import vtool.demodata as demodata
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair()
>>> xy1_mn = ktool.get_xys(kpts1)
>>> xy2_mn = ktool.get_xys(kpts2)
>>> H = compute_homog(xy1_mn, xy2_mn)
>>> #result = ut.hashstr(H)
>>> result = np.array_str(H, precision=2)
>>> print(result)
[[ 1.83e-03 2.85e-03 -7.11e-01]
[ 2.82e-03 1.80e-03 -7.03e-01]
[ 1.67e-05 1.68e-05 -5.53e-03]]
Example1:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.keypoint as ktool
>>> import wbia.plottool as pt
>>> xy1_man, xy2_man, rchip1, rchip2, T1, T2 = testdata_matching_affine_inliers_normalized()
>>> H_prime = compute_homog(xy1_man, xy2_man)
>>> H = npl.solve(T2, H_prime).dot(T1)
>>> H /= H[2, 2]
>>> result = np.array_str(H, precision=2)
>>> print(result)
>>> # xdoctest: +REQUIRES(--show)
>>> rchip2_blendH = pt.draw_sv.get_blended_chip(rchip1, rchip2, H)
>>> pt.imshow(rchip2_blendH)
>>> ut.show_if_requested()
[[ 9.22e-01 -2.50e-01 2.75e+01]
[ -2.04e-01 8.79e-01 -7.94e+00]
[ -1.82e-04 -5.99e-04 1.00e+00]]
"""
# Solve for the nullspace of the Mx9 matrix (solves least squares)
Mx9 = build_lstsqrs_Mx9(xy1_mn, xy2_mn)
U, S, V = try_svd(Mx9)
# Rearange the nullspace into a homography
# h = V[-1] # v = V.H
h = V[8] # Hack for Cython.wraparound(False)
# FIXME: THERE IS A BUG HERE, sometimes len(V) = 6. why???
H = np.vstack((h[0:3], h[3:6], h[6:9]))
return H
[docs]def testdata_matching_affine_inliers():
import vtool.demodata as demodata
import vtool as vt
scale_thresh = 2.0
xy_thresh = ut.get_argval('--xy-thresh', type_=float, default=0.01)
dlen_sqrd2 = 447271.015
ori_thresh = 1.57
xy_thresh_sqrd = dlen_sqrd2 * xy_thresh
featkw = ut.argparse_dict(vt.get_extract_features_default_params())
fname1 = ut.get_argval('--fname1', type_=str, default='easy1.png')
fname2 = ut.get_argval('--fname2', type_=str, default='easy2.png')
(kpts1, kpts2, fm, fs, rchip1, rchip2) = demodata.testdata_ratio_matches(
fname1, fname2, **featkw
)
aff_inliers, aff_errors, Aff = get_best_affine_inliers_(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
return kpts1, kpts2, fm, aff_inliers, rchip1, rchip2, xy_thresh_sqrd
[docs]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 _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
Returns:
tuple: hypo_inliers, hypo_errors
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.demodata as demodata
>>> 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 = demodata.perterbed_grid_kpts(**_kw1).astype(np.float64)
>>> kpts2 = demodata.perterbed_grid_kpts(**_kw2).astype(np.float64)
>>> fm = demodata.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)
>>> import ubelt as ub
>>> output_str = ub.repr2(output, precision=2, suppress_small=True)
>>> print('output_str = %s' % (output_str,))
>>> hypo_inliers, hypo_errors = output
>>> # Inverting matrices is different in python2
>>> result = 'nInliers=%r hash=%s' % (len(hypo_inliers), ut.hash_data(output))
>>> print(result)
"""
# 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 = vtool.distance.L2_sqrd(xy2_m.T, _xy1_mt.T, dtype=SV_DTYPE)
scale_err = vtool.distance.det_distance(_det1_mt, det2_m)
ori_err = vtool.distance.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
[docs]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
Returns:
tuple: aff_inliers_list, aff_errors_list, Aff_mats
Notes:
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:
python2 -m vtool.spatial_verification --test-get_affine_inliers
python3 -m vtool.spatial_verification --test-get_affine_inliers
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> import vtool.keypoint as ktool
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair((100, 100))
>>> fm = demodata.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)
>>> output_str = ut.repr3(output, precision=2, suppress_small=True)
>>> print('output_str = %s' % (output_str,))
>>> aff_inliers_list, aff_errors_list, Aff_mats = output
>>> result = 'nInliers=%r hash=%s' % (len(aff_inliers_list), ut.hash_data(output_str))
>>> print(result)
"""
# 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
[docs]def get_best_affine_inliers(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh, forcepy=False
):
""" Tests each hypothesis and returns only the best transformation and inliers
"""
# Test each affine hypothesis
# get list if inliers, errors, the affine matrix for each hypothesis
if HAVE_SVER_C_WRAPPER and not forcepy:
(
aff_inliers_list,
aff_errors_list,
Aff_mats,
) = sver_c_wrapper.get_affine_inliers_cpp(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
else:
aff_inliers_list, aff_errors_list, Aff_mats = get_affine_inliers(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
# ut.embed()
# Determine the best hypothesis using the number of inliers
# TODO: other measures in the error lists could be used as well
# nInliers_list = np.array([len(inliers) for inliers in aff_inliers_list])
weight_list = np.array([fs.take(inliers).sum() for inliers in aff_inliers_list])
# ut.embed()
# sortx = weight_list.argsort()[::-1] # sort by non-inliers
# best_index = sortx[0] # chose best
best_index = weight_list.argmax()
aff_inliers = aff_inliers_list[best_index]
aff_errors = aff_errors_list[best_index]
Aff = Aff_mats[best_index]
return aff_inliers, aff_errors, Aff
[docs]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
[docs]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 wbia.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])
>>> # xdoctest: +REQUIRES(--show)
>>> 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 wbia.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(', '))
>>> #---------------
>>> # xdoctest: +REQUIRES(--show)
>>> 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 = vtool.distance.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 = vtool.distance.L2_sqrd(xy2_m.T, _xy1_mt.T)
scale_err = vtool.distance.det_distance(_det1_mt, det2_m)
ori_err = vtool.distance.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
[docs]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
[docs]def refine_inliers(
kpts1,
kpts2,
fm,
aff_inliers,
xy_thresh_sqrd,
scale_thresh=2.0,
ori_thresh=1.57,
full_homog_checks=True,
refine_method='homog',
):
"""
Given a set of hypothesis inliers, computes a homography and refines inliers
returned homography maps image1 space into image2 space
CommandLine:
python -m vtool.spatial_verification --test-refine_inliers
python -m vtool.spatial_verification --test-refine_inliers:0
python -m vtool.spatial_verification --test-refine_inliers:1 --show
Example0:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.demodata as demodata
>>> import vtool.keypoint as ktool
>>> kpts1, kpts2 = demodata.get_dummy_kpts_pair((100, 100))
>>> fm = demodata.make_dummy_fm(len(kpts1)).astype(np.int32)
>>> aff_inliers = np.arange(len(fm))
>>> xy_thresh_sqrd = .01 * ktool.get_kpts_dlen_sqrd(kpts2)
>>> homogtup = refine_inliers(kpts1, kpts2, fm, aff_inliers, xy_thresh_sqrd)
>>> refined_inliers, refined_errors, H = homogtup
>>> import ubelt as ub
>>> result = ub.repr2(homogtup, precision=2, nl=True, suppress_small=True, nobr=True)
>>> print(result)
Example1:
>>> # DISABLE_DOCTEST
>>> from vtool.spatial_verification import * # NOQA
>>> import vtool.keypoint as ktool
>>> import wbia.plottool as pt
>>> kpts1, kpts2, fm, aff_inliers, rchip1, rchip2, xy_thresh_sqrd = testdata_matching_affine_inliers()
>>> homog_tup1 = refine_inliers(kpts1, kpts2, fm, aff_inliers, xy_thresh_sqrd)
>>> homog_tup = (homog_tup1[0], homog_tup1[2])
>>> # xdoctest: +REQUIRES(--show)
>>> pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, homog_tup=homog_tup)
>>> ut.show_if_requested()
"""
H = estimate_refined_transform(
kpts1, kpts2, fm, aff_inliers, refine_method=refine_method
)
if refine_method.endswith('homog'):
homog_tup1 = test_homog_errors(
H,
kpts1,
kpts2,
fm,
xy_thresh_sqrd,
scale_thresh,
ori_thresh,
full_homog_checks,
)
# elif refine_method == 'cv2-homog':
# homog_tup1 = test_homog_errors(H, kpts1, kpts2, fm, xy_thresh_sqrd,
# scale_thresh, ori_thresh, full_homog_checks)
elif refine_method == 'affine':
homog_tup1 = test_affine_errors(
H, kpts1, kpts2, fm, xy_thresh_sqrd, scale_thresh, ori_thresh
)
return homog_tup1
[docs]def get_best_affine_inliers_(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
):
if HAVE_SVER_C_WRAPPER:
aff_inliers, aff_errors, Aff = sver_c_wrapper.get_best_affine_inliers_cpp(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
else:
if ut.NOT_QUIET:
print('WARNING: sver has not been compiled')
aff_inliers, aff_errors, Aff = get_best_affine_inliers(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
return aff_inliers, aff_errors, Aff
[docs]def spatially_verify_kpts(
kpts1,
kpts2,
fm,
xy_thresh=0.01,
scale_thresh=2.0,
ori_thresh=TAU / 4.0,
dlen_sqrd2=None,
min_nInliers=4,
match_weights=None,
returnAff=False,
full_homog_checks=True,
refine_method='homog',
max_nInliers=5000,
):
"""
Driver function
Spatially validates feature matches
FIXME: there is a non-determenism here
Returned homography maps image1 space into image2 space.
Args:
kpts1 (ndarray[ndim=2]): all keypoints in image 1
kpts2 (ndarray[ndim=2]): all keypoints in image 2
fm (ndarray[ndim=2]): matching keypoint indexes [..., (kp1x, kp2x), ...]
xy_thresh (float): spatial distance threshold under affine transform to
be considered a match
scale_thresh (float):
ori_thresh (float):
dlen_sqrd2 (float): diagonal length squared of image/chip 2
min_nInliers (int): default=4
returnAff (bool): returns best affine hypothesis as well
max_nInliers (int): homog is not considered after this threshold
Returns:
tuple : (refined_inliers, refined_errors, H, aff_inliers, aff_errors, Aff) if success else None
CommandLine:
python -m xdoctest vtool.spatial_verification spatially_verify_kpts:0 --show
python -m xdoctest vtool.spatial_verification spatially_verify_kpts:0 --show --refine-method='affine'
python -m xdoctest vtool.spatial_verification spatially_verify_kpts:0 --dpath figures --show --save ~/latex/crall-candidacy-2015/figures/sver_kpts.jpg # NOQA
python -m xdoctest vtool.spatial_verification spatially_verify_kpts:0
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.spatial_verification import *
>>> import vtool.demodata as demodata
>>> import vtool as vt
>>> fname1 = ut.get_argval('--fname1', type_=str, default='easy1.png')
>>> fname2 = ut.get_argval('--fname2', type_=str, default='easy2.png')
>>> default_dict = vt.get_extract_features_default_params()
>>> default_dict['ratio_thresh'] = .625
>>> kwargs = ut.argparse_dict(default_dict)
>>> (kpts1, kpts2, fm, fs, rchip1, rchip2) = demodata.testdata_ratio_matches(fname1, fname2, **kwargs)
>>> xy_thresh = .01
>>> dlen_sqrd2 = 447271.015
>>> ori_thresh = 1.57
>>> min_nInliers = 4
>>> returnAff = True
>>> scale_thresh = 2.0
>>> match_weights = np.ones(len(fm), dtype=np.float64)
>>> refine_method = ut.get_argval('--refine-method', default='homog')
>>> svtup = spatially_verify_kpts(kpts1, kpts2, fm, xy_thresh,
>>> scale_thresh, ori_thresh, dlen_sqrd2,
>>> min_nInliers, match_weights, returnAff,
>>> refine_method=refine_method)
>>> assert svtup is not None and len(svtup) == 6, 'sver failed'
>>> refined_inliers, refined_errors, H = svtup[0:3]
>>> aff_inliers, aff_errors, Aff = svtup[3:6]
>>> #print('aff_errors = %r' % (aff_errors,))
>>> print('aff_inliers = %r' % (aff_inliers,))
>>> print('refined_inliers = %r' % (refined_inliers,))
>>> #print('refined_errors = %r' % (refined_errors,))
>>> import ubelt as ub
>>> result = ut.list_type_profile(svtup, with_dtype=False)
>>> #result = ub.repr2(svtup, precision=3)
>>> print(result)
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> homog_tup = (refined_inliers, H)
>>> aff_tup = (aff_inliers, Aff)
>>> pt.draw_sv.show_sv(rchip1, rchip2, kpts1, kpts2, fm, aff_tup=aff_tup, homog_tup=homog_tup, refine_method=refine_method)
>>> pt.show_if_requested()
tuple(numpy.ndarray, tuple(numpy.ndarray*3), numpy.ndarray, numpy.ndarray, tuple(numpy.ndarray*3), numpy.ndarray)
"""
if len(fm) == 0:
if VERBOSE_SVER:
print('[sver] Cannot verify with no matches')
svtup = None
return svtup
# Cast keypoints to float64 to avoid numerical issues
kpts1 = kpts1.astype(np.float64, casting='same_kind', copy=False)
kpts2 = kpts2.astype(np.float64, casting='same_kind', copy=False)
# kpts1 = kpts1.astype(np.float64)
# kpts2 = kpts2.astype(np.float64)
assert match_weights is not None, 'provide at least ones please for match_weights'
fs = match_weights
# Get diagonal length if not provided
if dlen_sqrd2 is None:
kpts2_m = kpts2.take(fm.T[1], axis=0)
dlen_sqrd2 = ktool.get_kpts_dlen_sqrd(kpts2_m)
# Determine the best hypothesis transformation and get its inliers
xy_thresh_sqrd = dlen_sqrd2 * xy_thresh
aff_inliers, aff_errors, Aff = get_best_affine_inliers_(
kpts1, kpts2, fm, fs, xy_thresh_sqrd, scale_thresh, ori_thresh
)
# print(aff_inliers)
# Return if there are not enough inliers to compute homography
if len(aff_inliers) < min_nInliers:
# Test user defined param
if VERBOSE_SVER:
print(
'[sver] Failed spatial verification len(aff_inliers) = %r'
% (len(aff_inliers),)
)
svtup = None
return svtup
if (refine_method.endswith('homog') and len(aff_inliers) < 7) or len(aff_inliers) < 4:
# Test fundamental param
# need to have 4 or more inliers to comopute an affine
# and need at least 7 to compute a homography
if VERBOSE_SVER:
print(
'[sver] Failed spatial verification len(aff_inliers) = %r'
% (len(aff_inliers),)
)
svtup = None
return svtup
if len(aff_inliers) >= max_nInliers:
# If there are a very large number of affine inliers, then the affine
# matrix is probably good enough.
svtup = (aff_inliers, aff_errors, Aff, aff_inliers, aff_errors, Aff)
return svtup
# Refine inliers using a projective transformation (homography)
try:
refined_inliers, refined_errors, H = refine_inliers(
kpts1,
kpts2,
fm,
aff_inliers,
xy_thresh_sqrd,
scale_thresh,
ori_thresh,
full_homog_checks,
refine_method=refine_method,
)
# print(refined_inliers)
except npl.LinAlgError as ex:
if ut.VERYVERBOSE and ut.SUPER_STRICT:
ut.printex(ex, 'numeric error in homog estimation.', iswarning=True)
return None
except ValueError as ex:
if ut.VERYVERBOSE and ut.SUPER_STRICT:
ut.printex(ex, 'error cv2 in homog estimation.', iswarning=True)
return None
except IndexError:
raise
except Exception as ex:
# There is a weird error that starts with MemoryError and ends up
# makeing len(h) = 6.
ut.printex(
ex,
'Unknown error in homog estimation.',
keys=[
'kpts1',
'kpts2',
'fm',
'fm.shape',
'kpts1.shape',
(len, 'aff_inliers'),
'kpts2.shape',
'xy_thresh',
'scale_thresh',
'dlen_sqrd2',
'min_nInliers',
],
)
if ut.SUPER_STRICT:
print('SUPER_STRICT is on. Reraising')
raise
return None
if VERBOSE_SVER:
print('[sver] Succesfully finished spatial verification.')
if returnAff:
svtup = (refined_inliers, refined_errors, H, aff_inliers, aff_errors, Aff)
return svtup
else:
svtup = (refined_inliers, refined_errors, H, None, None, None)
return svtup
if __name__ == '__main__':
"""
CommandLine:
xdoctest -m vtool.spatial_verification
"""
import xdoctest
xdoctest.doctest_module(__file__)