def local_translation(r1, r2, x, y, w, h, m): """ Estimates the optimal translation to minimise the relative pointing error on a given tile. Args: r1, r2: two instances of the rpc_model.RPCModel class x, y, w, h: region of interest in the reference image (r1) m: Nx4 numpy array containing a list of matches, one per line. Each match is given by (p1, p2, q1, q2) where (p1, p2) is a point of the reference view and (q1, q2) is the corresponding point in the secondary view. Returns: 3x3 numpy array containing the homogeneous representation of the optimal planar translation, to be applied to the secondary image in order to correct the pointing error. """ # estimate the affine fundamental matrix between the two views n = cfg['n_gcp_per_axis'] rpc_matches = rpc_utils.matches_from_rpc(r1, r2, x, y, w, h, n) F = estimation.affine_fundamental_matrix(rpc_matches) # compute the error vectors e = error_vectors(m, F, 'sec') # compute the median: as the vectors are collinear (because F is affine) # computing the median of each component independently is correct N = len(e) out_x = np.sort(e[:, 0])[int(N / 2)] out_y = np.sort(e[:, 1])[int(N / 2)] # the correction to be applied to the second view is the opposite A = np.array([[1, 0, -out_x], [0, 1, -out_y], [0, 0, 1]]) return A
def evaluation_from_estimated_F(im1, im2, rpc1, rpc2, x, y, w, h, A=None, matches=None): """ Measures the pointing error on a Pleiades' pair of images, affine approx. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: two instances of the rpc_model.RPCModel class x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. A (optional): 3x3 numpy array containing the pointing error correction for im2. matches (optional): Nx4 numpy array containing a list of matches to use to compute the pointing error Returns: the mean pointing error, in the direction orthogonal to the epipolar lines. This error is measured in pixels, and computed from an approximated fundamental matrix. """ if not matches: matches = sift.matches_on_rpc_roi(im1, im2, rpc1, rpc2, x, y, w, h) p1 = matches[:, 0:2] p2 = matches[:, 2:4] print '%d sift matches' % len(matches) # apply pointing correction matrix, if available if A is not None: p2 = common.points_apply_homography(A, p2) # estimate the fundamental matrix between the two views rpc_matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, 5) F = estimation.affine_fundamental_matrix(rpc_matches) # compute the mean displacement from epipolar lines d_sum = 0 for i in range(len(p1)): x = np.array([p1[i, 0], p1[i, 1], 1]) xx = np.array([p2[i, 0], p2[i, 1], 1]) ll = F.dot(x) #d = np.sign(xx.dot(ll)) * evaluation.distance_point_to_line(xx, ll) d = evaluation.distance_point_to_line(xx, ll) d_sum += d return d_sum / len(p1)
def matches_on_rpc_roi(im1, im2, rpc1, rpc2, x, y, w, h): """ Compute a list of SIFT matches between two images on a given roi. The corresponding roi in the second image is determined using the rpc functions. Args: im1, im2: paths to two large tif images rpc1, rpc2: two instances of the rpc_model.RPCModel class x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. Returns: matches: 2D numpy array containing a list of matches. Each line contains one pair of points, ordered as x1 y1 x2 y2. The coordinate system is that of the full images. """ x2, y2, w2, h2 = rpc_utils.corresponding_roi(rpc1, rpc2, x, y, w, h) # estimate an approximate affine fundamental matrix from the rpcs rpc_matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, 5) # TODO FIXME: do we need to center the points as we do in the rectification module? F = estimation.affine_fundamental_matrix(rpc_matches) # if less than 10 matches, lower thresh_dog. An alternative would be ASIFT thresh_dog = 0.0133 for i in range(6): p1 = image_keypoints(im1, x, y, w, h, extra_params='--thresh-dog %f' % thresh_dog) p2 = image_keypoints(im2, x2, y2, w2, h2, extra_params='--thresh-dog %f' % thresh_dog) matches = keypoints_match(p1, p2, 'relative', cfg['sift_match_thresh'], F, model='fundamental') if matches.shape[0] > 10: break else: thresh_dog /= 2.0 return matches
def plot_pointing_error_tile(im1, im2, rpc1, rpc2, x, y, w, h, matches_sift=None, f=100, out_files_pattern=None): """ Args: im1, im2: path to full pleiades images rpc1, rcp2: path to associated rpc xml files x, y, w, h: four integers defining the rectangular tile in the reference image. (x, y) is the top-left corner, and (w, h) are the dimensions of the tile. f (optional, default is 100): exageration factor for the error vectors out_files_pattern (optional, default is None): pattern used to name the two output files (plots of the pointing error) Returns: nothing, but opens a display """ # read rpcs r1 = rpc_model.RPCModel(rpc1) r2 = rpc_model.RPCModel(rpc2) # compute sift matches if matches_sift is None: matches_sift = pointing_accuracy.filtered_sift_matches_roi(im1, im2, r1, r2, x, y, w, h) # compute rpc matches matches_rpc = rpc_utils.matches_from_rpc(r1, r2, x, y, w, h, 5) # estimate affine fundamental matrix F = estimation.affine_fundamental_matrix(matches_rpc) # compute error vectors e = pointing_accuracy.error_vectors(matches_sift, F, 'ref') A = pointing_accuracy.local_translation(r1, r2, x, y, w, h, matches_sift) p = matches_sift[:, 0:2] q = matches_sift[:, 2:4] qq = common.points_apply_homography(A, q) ee = pointing_accuracy.error_vectors(np.hstack((p, qq)), F, 'ref') print pointing_accuracy.evaluation_from_estimated_F(im1, im2, r1, r2, x, y, w, h, None, matches_sift) print pointing_accuracy.evaluation_from_estimated_F(im1, im2, r1, r2, x, y, w, h, A, matches_sift) # plot the vectors: they go from the point x to the line (F.T)x' plot_vectors(p, -e, x, y, w, h, f, out_file='%s_before.png' % out_files_pattern) plot_vectors(p, -ee, x, y, w, h, f, out_file='%s_after.png' % out_files_pattern)
def evaluation_from_estimated_F(im1, im2, rpc1, rpc2, x, y, w, h, A=None, matches=None): """ Measures the pointing error on a Pleiades' pair of images, affine approx. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: two instances of the rpc_model.RPCModel class x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. A (optional): 3x3 numpy array containing the pointing error correction for im2. matches (optional): Nx4 numpy array containing a list of matches to use to compute the pointing error Returns: the mean pointing error, in the direction orthogonal to the epipolar lines. This error is measured in pixels, and computed from an approximated fundamental matrix. """ if matches is None: matches = filtered_sift_matches_roi(im1, im2, rpc1, rpc2, x, y, w, h) p1 = matches[:, 0:2] p2 = matches[:, 2:4] print '%d sift matches' % len(matches) # apply pointing correction matrix, if available if A is not None: p2 = common.points_apply_homography(A, p2) # estimate the fundamental matrix between the two views rpc_matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, 5) F = estimation.affine_fundamental_matrix(rpc_matches) # compute the mean displacement from epipolar lines d_sum = 0 for i in range(len(p1)): x = np.array([p1[i, 0], p1[i, 1], 1]) xx = np.array([p2[i, 0], p2[i, 1], 1]) ll = F.dot(x) #d = np.sign(xx.dot(ll)) * evaluation.distance_point_to_line(xx, ll) d = evaluation.distance_point_to_line(xx, ll) d_sum += d return d_sum/len(p1)
def rectification_homographies(matches, x, y, w, h): """ Computes rectifying homographies from point matches for a given ROI. The affine fundamental matrix F is estimated with the gold-standard algorithm, then two rectifying similarities (rotation, zoom, translation) are computed directly from F. Args: matches: numpy array of shape (n, 4) containing a list of 2D point correspondences between the two images. x, y, w, h: four integers definig the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. Returns: S1, S2, F: three numpy arrays of shape (3, 3) representing the two rectifying similarities to be applied to the two images and the corresponding affine fundamental matrix. """ # estimate the affine fundamental matrix with the Gold standard algorithm F = estimation.affine_fundamental_matrix(matches) # compute rectifying similarities S1, S2 = estimation.rectifying_similarities_from_affine_fundamental_matrix(F, True) if cfg["debug"]: y1 = common.points_apply_homography(S1, matches[:, :2])[:, 1] y2 = common.points_apply_homography(S2, matches[:, 2:])[:, 1] err = np.abs(y1 - y2) print "max, min, mean rectification error on point matches: ", print np.max(err), np.min(err), np.mean(err) # pull back top-left corner of the ROI to the origin pts = common.points_apply_homography(S1, [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]) x0, y0 = common.bounding_box2D(pts)[:2] T = common.matrix_translation(-x0, -y0) return np.dot(T, S1), np.dot(T, S2), F
def rectification_homographies(matches, x, y, w, h): """ Computes rectifying homographies from point matches for a given ROI. The affine fundamental matrix F is estimated with the gold-standard algorithm, then two rectifying similarities (rotation, zoom, translation) are computed directly from F. Args: matches: numpy array of shape (n, 4) containing a list of 2D point correspondences between the two images. x, y, w, h: four integers definig the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. Returns: S1, S2, F: three numpy arrays of shape (3, 3) representing the two rectifying similarities to be applied to the two images and the corresponding affine fundamental matrix. """ # estimate the affine fundamental matrix with the Gold standard algorithm F = estimation.affine_fundamental_matrix(matches) # compute rectifying similarities S1, S2 = estimation.rectifying_similarities_from_affine_fundamental_matrix(F, True) if cfg['debug']: y1 = common.points_apply_homography(S1, matches[:, :2])[:, 1] y2 = common.points_apply_homography(S2, matches[:, 2:])[:, 1] err = np.abs(y1 - y2) print "max, min, mean rectification error on point matches: ", print np.max(err), np.min(err), np.mean(err) # pull back top-left corner of the ROI to the origin pts = common.points_apply_homography(S1, [[x, y], [x+w, y], [x+w, y+h], [x, y+h]]) x0, y0 = common.bounding_box2D(pts)[:2] T = common.matrix_translation(-x0, -y0) return np.dot(T, S1), np.dot(T, S2), F
def local_translation(r1, r2, x, y, w, h, m): """ Estimates the optimal translation to minimise the relative pointing error on a given tile. Args: r1, r2: two instances of the rpc_model.RPCModel class x, y, w, h: region of interest in the reference image (r1) m: Nx4 numpy array containing a list of matches, one per line. Each match is given by (p1, p2, q1, q2) where (p1, p2) is a point of the reference view and (q1, q2) is the corresponding point in the secondary view. Returns: 3x3 numpy array containing the homogeneous representation of the optimal planar translation, to be applied to the secondary image in order to correct the pointing error. """ # estimate the affine fundamental matrix between the two views n = cfg['n_gcp_per_axis'] rpc_matches = rpc_utils.matches_from_rpc(r1, r2, x, y, w, h, n) F = estimation.affine_fundamental_matrix(rpc_matches) # compute the error vectors e = error_vectors(m, F, 'sec') # compute the median: as the vectors are collinear (because F is affine) # computing the median of each component independently is correct N = len(e) out_x = np.sort(e[:, 0])[int(N/2)] out_y = np.sort(e[:, 1])[int(N/2)] # the correction to be applied to the second view is the opposite A = np.array([[1, 0, -out_x], [0, 1, -out_y], [0, 0, 1]]) return A
def compute_rectification_homographies(im1, im2, rpc1, rpc2, x, y, w, h, A=None, m=None): """ Computes rectifying homographies for a ROI in a pair of Pleiades images. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: two instances of the rpc_model.RPCModel class x, y, w, h: four integers definig the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. m (optional): Nx4 numpy array containing a list of matches. Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies to be applied to the two images. disp_min, disp_max: horizontal disparity range, computed on a set of sift matches """ # in brief: use 8-pts normalized algo to estimate F, then use loop-zhang to # estimate rectifying homographies. print "step 1: find virtual matches, and center them ----------------------" n = cfg['n_gcp_per_axis'] rpc_matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, n) p1 = rpc_matches[:, 0:2] p2 = rpc_matches[:, 2:4] if A is not None: print "applying pointing error correction" # correct coordinates of points in im2, according to A p2 = common.points_apply_homography(np.linalg.inv(A), p2) # the matching points are translated to be centered in 0, in order to deal # with coordinates ranging from -1000 to 1000, and decrease imprecision # effects of the loop-zhang rectification. These effects may become very # important (~ 10 pixels error) when using coordinates around 20000. pp1, T1 = center_2d_points(p1) pp2, T2 = center_2d_points(p2) print "step 2: estimate F (Gold standard algorithm) -----------------------" F = estimation.affine_fundamental_matrix(np.hstack([pp1, pp2])) print "step 3: compute rectifying homographies (loop-zhang algorithm) -----" H1, H2 = estimation.loop_zhang(F, w, h) S1, S2 = estimation.rectifying_similarities_from_affine_fundamental_matrix( F, True) print "F\n", F, "\n" print "H1\n", H1, "\n" print "S1\n", S1, "\n" print "H2\n", H2, "\n" print "S2\n", S2, "\n" # compose with previous translations to get H1, H2 in the big images frame H1 = np.dot(H1, T1) H2 = np.dot(H2, T2) # for debug print "max, min, mean rectification error on rpc matches ------------------" tmp = common.points_apply_homography(H1, p1) y1 = tmp[:, 1] tmp = common.points_apply_homography(H2, p2) y2 = tmp[:, 1] err = np.abs(y1 - y2) print np.max(err), np.min(err), np.mean(err) print "step 4: pull back top-left corner of the ROI in the origin ---------" roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] pts = common.points_apply_homography(H1, roi) x0, y0 = common.bounding_box2D(pts)[0:2] T = common.matrix_translation(-x0, -y0) H1 = np.dot(T, H1) H2 = np.dot(T, H2) # add an horizontal translation to H2 to center the disparity range around # the origin, if sift matches are available if m is not None: print "step 5: horizontal registration --------------------------------" # filter sift matches with the known fundamental matrix # but first convert F for big images coordinate frame F = np.dot(T2.T, np.dot(F, T1)) print '%d sift matches before epipolar constraint filering' % len(m) m = filter_matches_epipolar_constraint(F, m, cfg['epipolar_thresh']) print '%d sift matches after epipolar constraint filering' % len(m) if len(m) < 2: # 0 or 1 sift match print 'rectification.compute_rectification_homographies: less than' print '2 sift matches after filtering by the epipolar constraint.' print 'This may be due to the pointing error, or to strong' print 'illumination changes between the input images.' print 'No registration will be performed.' else: H2 = register_horizontally(m, H1, H2) disp_m, disp_M = update_disp_range(m, H1, H2, w, h) print "SIFT disparity range: [%f,%f]" % (disp_m, disp_M) # expand disparity range with srtm according to cfg params print cfg['disp_range_method'] if (cfg['disp_range_method'] == "srtm") or (m is None) or (len(m) < 2): disp_m, disp_M = rpc_utils.srtm_disp_range_estimation( rpc1, rpc2, x, y, w, h, H1, H2, A, cfg['disp_range_srtm_high_margin'], cfg['disp_range_srtm_low_margin']) print "SRTM disparity range: [%f,%f]" % (disp_m, disp_M) if ((cfg['disp_range_method'] == "wider_sift_srtm") and (m is not None) and (len(m) >= 2)): d_m, d_M = rpc_utils.srtm_disp_range_estimation( rpc1, rpc2, x, y, w, h, H1, H2, A, cfg['disp_range_srtm_high_margin'], cfg['disp_range_srtm_low_margin']) print "SRTM disparity range: [%f,%f]" % (d_m, d_M) disp_m = min(disp_m, d_m) disp_M = max(disp_M, d_M) print "Final disparity range: [%s, %s]" % (disp_m, disp_M) return H1, H2, disp_m, disp_M
def compute_rectification_homographies(im1, im2, rpc1, rpc2, x, y, w, h, A=None, m=None): """ Computes rectifying homographies for a ROI in a pair of Pleiades images. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: two instances of the rpc_model.RPCModel class x, y, w, h: four integers definig the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. m (optional): Nx4 numpy array containing a list of matches. Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies to be applied to the two images. disp_min, disp_max: horizontal disparity range, computed on a set of sift matches """ # in brief: use 8-pts normalized algo to estimate F, then use loop-zhang to # estimate rectifying homographies. print "step 1: find virtual matches, and center them ----------------------" n = cfg['n_gcp_per_axis'] rpc_matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, n) p1 = rpc_matches[:, 0:2] p2 = rpc_matches[:, 2:4] if A is not None: print "applying pointing error correction" # correct coordinates of points in im2, according to A p2 = common.points_apply_homography(np.linalg.inv(A), p2) # the matching points are translated to be centered in 0, in order to deal # with coordinates ranging from -1000 to 1000, and decrease imprecision # effects of the loop-zhang rectification. These effects may become very # important (~ 10 pixels error) when using coordinates around 20000. pp1, T1 = center_2d_points(p1) pp2, T2 = center_2d_points(p2) print "step 2: estimate F (Gold standard algorithm) -----------------------" F = estimation.affine_fundamental_matrix(np.hstack([pp1, pp2])) print "step 3: compute rectifying homographies (loop-zhang algorithm) -----" H1, H2 = estimation.loop_zhang(F, w, h) S1, S2 = estimation.rectifying_similarities_from_affine_fundamental_matrix( F, True) print "F\n", F, "\n" print "H1\n", H1, "\n" print "S1\n", S1, "\n" print "H2\n", H2, "\n" print "S2\n", S2, "\n" # compose with previous translations to get H1, H2 in the big images frame H1 = np.dot(H1, T1) H2 = np.dot(H2, T2) # for debug print "max, min, mean rectification error on rpc matches ------------------" tmp = common.points_apply_homography(H1, p1) y1 = tmp[:, 1] tmp = common.points_apply_homography(H2, p2) y2 = tmp[:, 1] err = np.abs(y1 - y2) print np.max(err), np.min(err), np.mean(err) print "step 4: pull back top-left corner of the ROI in the origin ---------" roi = [[x, y], [x+w, y], [x+w, y+h], [x, y+h]] pts = common.points_apply_homography(H1, roi) x0, y0 = common.bounding_box2D(pts)[0:2] T = common.matrix_translation(-x0, -y0) H1 = np.dot(T, H1) H2 = np.dot(T, H2) # add an horizontal translation to H2 to center the disparity range around # the origin, if sift matches are available if m is not None: print "step 5: horizontal registration --------------------------------" # filter sift matches with the known fundamental matrix # but first convert F for big images coordinate frame F = np.dot(T2.T, np.dot(F, T1)) print '%d sift matches before epipolar constraint filering' % len(m) m = filter_matches_epipolar_constraint(F, m, cfg['epipolar_thresh']) print '%d sift matches after epipolar constraint filering' % len(m) if len(m) < 2: # 0 or 1 sift match print 'rectification.compute_rectification_homographies: less than' print '2 sift matches after filtering by the epipolar constraint.' print 'This may be due to the pointing error, or to strong' print 'illumination changes between the input images.' print 'No registration will be performed.' else: H2 = register_horizontally(m, H1, H2) disp_m, disp_M = update_disp_range(m, H1, H2, w, h) print "SIFT disparity range: [%f,%f]"%(disp_m,disp_M) # expand disparity range with srtm according to cfg params print cfg['disp_range_method'] if (cfg['disp_range_method'] == "srtm") or (m is None) or (len(m) < 2): disp_m, disp_M = rpc_utils.srtm_disp_range_estimation( rpc1, rpc2, x, y, w, h, H1, H2, A, cfg['disp_range_srtm_high_margin'], cfg['disp_range_srtm_low_margin']) print "SRTM disparity range: [%f,%f]"%(disp_m,disp_M) if ((cfg['disp_range_method'] == "wider_sift_srtm") and (m is not None) and (len(m) >= 2)): d_m, d_M = rpc_utils.srtm_disp_range_estimation( rpc1, rpc2, x, y, w, h, H1, H2, A, cfg['disp_range_srtm_high_margin'], cfg['disp_range_srtm_low_margin']) print "SRTM disparity range: [%f,%f]"%(d_m,d_M) disp_m = min(disp_m, d_m) disp_M = max(disp_M, d_M) print "Final disparity range: [%s, %s]" % (disp_m, disp_M) return H1, H2, disp_m, disp_M