def fundamental_error(rpc1, rpc2, x, y, w, h, n_learn, n_test):
    """
    Measure the error made when imposing a fundamental matrix fitting on
    Pleiades data.

    Args:
        rpc1, rpc2: two instances of the rpc_model.RPCModel class
        x, y, w, h: four integers definig a rectangular region of interest
            (ROI) in the first view. (x, y) is the top-left corner, and (w, h)
            are the dimensions of the rectangle. In the first view, the keypoints
            used for estimation and evaluation are located in that ROI.
        n_learn: number of points sampled in each of the 3 directions to
            generate image correspondences constraints for fundamental matrix
            estimation.
        n_test: number of points sampled in each of the 3 directions to
            generate correspondences used to test the accuracy of the fundamental
            matrix.

    Returns:
        A float value measuring the accuracy of the fundamental matrix. Smaller
        is better.
    """

    # step 1: estimate the fundamental matrix
    matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, n_learn)
    F = estimation.fundamental_matrix(matches)

    # step 2: compute the residual error
    matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, n_test)
    err = evaluation.fundamental_matrix(F, matches)
    return err
Esempio n. 2
0
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
Esempio n. 3
0
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)
Esempio n. 4
0
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
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
File: sift.py Progetto: jguinet/s2p
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
Esempio n. 8
0
def cost_function_linear(v, rpc1, rpc2, matches):
    """
    Objective function to minimize in order to correct the pointing error.

    Arguments:
        v: vector of size 4, containing the 4 parameters of the euclidean
            transformation we are looking for.
        rpc1, rpc2: two instances of the rpc_model.RPCModel class
        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 the one of the big images.
        alpha: relative weight of the error terms: e + alpha*(h-h0)^2. See
            paper for more explanations.

    Returns:
        The sum of pointing errors and altitude differences, as written in the
        paper formula (1).
    """
    print_params(v)

    # verify that parameters are in the bounding box
    if (np.abs(v[0]) > 200*np.pi or
        np.abs(v[1]) > 10000 or
        np.abs(v[2]) > 10000 or
        np.abs(v[3]) > 20000):
        print 'warning: cost_function is going too far'
        print v

    x, y, w, h = common.bounding_box2D(matches[:, 0:2])
    matches_rpc = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, 5)
    F = estimation.fundamental_matrix(matches_rpc)

    # transform the coordinates of points in the second image according to
    # matrix A, built from vector v
    A = euclidean_transform_matrix(v)
    p2 = common.points_apply_homography(A, matches[:, 2:4])

    return evaluation.fundamental_matrix_L1(F, np.hstack([matches[:, 0:2], p2]))
Esempio n. 9
0
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
Esempio n. 10
0
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
Esempio n. 11
0
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, sift_matches=None, method="rpc"):
    """
    Rectify a ROI in a pair of images.

    Args:
        im1, im2: paths to two image files
        rpc1, rpc2: paths to the two xml files containing RPC data
        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.
        out1, out2: paths to the output rectified crops
        A (optional): 3x3 numpy array containing the pointing error correction
            for im2. This matrix is usually estimated with the pointing_accuracy
            module.
        sift_matches (optional): Nx4 numpy array containing a list of sift
            matches, in the full image coordinates frame
        method (default: 'rpc'): option to decide wether to use rpc of sift
            matches for the fundamental matrix estimation.

        This function uses the parameter subsampling_factor from the
        config module. If the factor z > 1 then the output images will
        be subsampled by a factor z. The output matrices H1, H2, and the
        ranges are also updated accordingly:
        Hi = Z * Hi with Z = diag(1/z, 1/z, 1) and
        disp_min = disp_min / z  (resp _max)

    Returns:
        H1, H2: Two 3x3 matrices representing the rectifying homographies that
        have been applied to the two original (large) images.
        disp_min, disp_max: horizontal disparity range
    """
    # read RPC data
    rpc1 = rpc_model.RPCModel(rpc1)
    rpc2 = rpc_model.RPCModel(rpc2)

    # compute real or virtual matches
    if method == "rpc":
        # find virtual matches from RPC camera models
        matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, cfg["n_gcp_per_axis"])

        # correct second image coordinates with the pointing correction matrix
        if A is not None:
            matches[:, 2:] = common.points_apply_homography(np.linalg.inv(A), matches[:, 2:])
    else:
        matches = sift_matches

    # compute rectifying homographies
    H1, H2, F = rectification_homographies(matches, x, y, w, h)

    if cfg["register_with_shear"]:
        # compose H2 with a horizontal shear to reduce the disparity range
        a = np.mean(rpc_utils.altitude_range(rpc1, x, y, w, h))
        lon, lat, alt = rpc_utils.ground_control_points(rpc1, x, y, w, h, a, a, 4)
        x1, y1 = rpc1.inverse_estimate(lon, lat, alt)[:2]
        x2, y2 = rpc2.inverse_estimate(lon, lat, alt)[:2]
        m = np.vstack([x1, y1, x2, y2]).T
        m = np.vstack({tuple(row) for row in m})  # remove duplicates due to no alt range
        H2 = register_horizontally_shear(m, H1, H2)

    # compose H2 with a horizontal translation to center disp range around 0
    if sift_matches is not None:
        sift_matches = filter_matches_epipolar_constraint(F, sift_matches, cfg["epipolar_thresh"])
        if len(sift_matches) < 10:
            print "WARNING: no registration with less than 10 matches"
        else:
            H2 = register_horizontally_translation(sift_matches, H1, H2)

    # compute disparity range
    disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2, sift_matches, A)

    # compute output images size
    roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]
    pts1 = common.points_apply_homography(H1, roi)
    x0, y0, w0, h0 = common.bounding_box2D(pts1)
    # check that the first homography maps the ROI in the positive quadrant
    np.testing.assert_allclose(np.round([x0, y0]), 0, atol=0.01)

    # apply homographies and do the crops TODO XXX FIXME cleanup here
    # homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True)
    # homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True)
    common.image_apply_homography(out1, im1, H1, w0, h0)
    common.image_apply_homography(out2, im2, H2, w0, h0)

    #  if subsampling_factor'] the homographies are altered to reflect the zoom
    if cfg["subsampling_factor"] != 1:
        Z = np.eye(3)
        Z[0, 0] = Z[1, 1] = 1.0 / cfg["subsampling_factor"]

        H1 = np.dot(Z, H1)
        H2 = np.dot(Z, H2)
        disp_m = np.floor(disp_m / cfg["subsampling_factor"])
        disp_M = np.ceil(disp_M / cfg["subsampling_factor"])

    return H1, H2, disp_m, disp_M
Esempio n. 12
0
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
Esempio n. 13
0
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None,
                 sift_matches=None, method='rpc'):
    """
    Rectify a ROI in a pair of images.

    Args:
        im1, im2: paths to two image files
        rpc1, rpc2: paths to the two xml files containing RPC data
        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.
        out1, out2: paths to the output rectified crops
        A (optional): 3x3 numpy array containing the pointing error correction
            for im2. This matrix is usually estimated with the pointing_accuracy
            module.
        sift_matches (optional): Nx4 numpy array containing a list of sift
            matches, in the full image coordinates frame
        method (default: 'rpc'): option to decide wether to use rpc of sift
            matches for the fundamental matrix estimation.

        This function uses the parameter subsampling_factor from the
        config module. If the factor z > 1 then the output images will
        be subsampled by a factor z. The output matrices H1, H2, and the
        ranges are also updated accordingly:
        Hi = Z * Hi with Z = diag(1/z, 1/z, 1) and
        disp_min = disp_min / z  (resp _max)

    Returns:
        H1, H2: Two 3x3 matrices representing the rectifying homographies that
        have been applied to the two original (large) images.
        disp_min, disp_max: horizontal disparity range
    """
    # read RPC data
    rpc1 = rpc_model.RPCModel(rpc1)
    rpc2 = rpc_model.RPCModel(rpc2)

    # compute real or virtual matches
    if method == 'rpc':
        # find virtual matches from RPC camera models
        matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h,
                                             cfg['n_gcp_per_axis'])

        # correct second image coordinates with the pointing correction matrix
        if A is not None:
            matches[:, 2:] = common.points_apply_homography(np.linalg.inv(A),
                                                            matches[:, 2:])
    else:
        matches = sift_matches

    # compute rectifying homographies
    H1, H2, F = rectification_homographies(matches, x, y, w, h)

    # compose H2 with a horizontal translation to center disp range around 0
    if sift_matches is not None:
        sift_matches = filter_matches_epipolar_constraint(F, sift_matches,
                                                          cfg['epipolar_thresh'])
        if len(sift_matches) < 10:
            print 'WARNING: no registration with less than 10 matches'
        else:
            H2 = register_horizontally(sift_matches, H1, H2)

    # compute disparity range
    disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2,
                                     sift_matches, A)

    # compute output images size
    roi = [[x, y], [x+w, y], [x+w, y+h], [x, y+h]]
    pts1 = common.points_apply_homography(H1, roi)
    x0, y0, w0, h0 = common.bounding_box2D(pts1)
    # check that the first homography maps the ROI in the positive quadrant
    np.testing.assert_allclose(np.round([x0, y0]), 0, atol=.01)

    # apply homographies and do the crops TODO XXX FIXME cleanup here
    #homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True)
    #homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True)
    common.image_apply_homography(out1, im1, H1, w0, h0)
    common.image_apply_homography(out2, im2, H2, w0, h0)

    #  if subsampling_factor'] the homographies are altered to reflect the zoom
    if cfg['subsampling_factor'] != 1:
        Z = np.eye(3)
        Z[0, 0] = Z[1, 1] = 1.0 / cfg['subsampling_factor']

        H1 = np.dot(Z, H1)
        H2 = np.dot(Z, H2)
        disp_m = np.floor(disp_m / cfg['subsampling_factor'])
        disp_M = np.ceil(disp_M / cfg['subsampling_factor'])

    return H1, H2, disp_m, disp_M