예제 #1
0
def optimize_pair_all_datasets(data):
    """
    Run the optimize_pair function on all the datasets found in data.

    Args:
        data: path to the folder containing all the Pleiades datasets

    Returns:
        nothing. For each dataset, it saves the computed correction matrix in a
        txt file named dataset/pointing_correction_matrix.txt.

    It uses precomputed matches, stored in dataset/sift_matches.txt. These
    matches are usually computed with the save_sift_matches_all_datasets
    function.
    """
    for f in os.listdir(data):
        dataset = os.path.join(data, f)
        im1  = os.path.join(dataset, 'im01.tif')
        im2  = os.path.join(dataset, 'im02.tif')
        rpc1 = '%s/../rpc/%s/rpc01.xml' % (data, f)
        rpc2 = '%s/../rpc/%s/rpc02.xml' % (data, f)
        rpc1 = rpc_model.RPCModel(rpc1)
        rpc2 = rpc_model.RPCModel(rpc2)
        matches_file = os.path.join(dataset, 'sift_matches.txt')
        try:
            with open(matches_file, 'r'):
                matches = np.loadtxt(matches_file)
                out = os.path.join(dataset, 'pointing_correction_matrix.txt')
                print f
                A = optimize_pair(im1, im2, rpc1, rpc2, None, matches)
                np.savetxt(out, A)
        except Exception as e:
                print e
예제 #2
0
def corresponding_roi(rpc1, rpc2, x, y, w, h):
    """
    Uses RPC functions to determine the region of im2 associated to the
    specified ROI of im1.

    Args:
        rpc1, rpc2: two instances of the rpc_model.RPCModel class, or paths to
            the xml files
        x, y, w, h: four integers defining 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.

    Returns:
        four integers defining a ROI in the second view. This ROI is supposed
        to contain the projections of the 3D points that are visible in the
        input ROI.
    """
    # read rpc files 
    if not isinstance(rpc1, rpc_model.RPCModel):
        rpc1 = rpc_model.RPCModel(rpc1)
    if not isinstance(rpc2, rpc_model.RPCModel):
        rpc2 = rpc_model.RPCModel(rpc2)
    m, M = altitude_range(rpc1, x, y, w, h, 0, 0)

    # build an array with vertices of the 3D ROI, obtained as {2D ROI} x [m, M]
    a = np.array([x, x,   x,   x, x+w, x+w, x+w, x+w])
    b = np.array([y, y, y+h, y+h,   y,   y, y+h, y+h])
    c = np.array([m, M,   m,   M,   m,   M,   m,   M])

    # corresponding points in im2
    xx, yy = find_corresponding_point(rpc1, rpc2, a, b, c)[0:2]

    # return coordinates of the bounding box in im2
    out = common.bounding_box2D(np.vstack([xx, yy]).T)
    return np.round(out)
예제 #3
0
def compute_correction(img1, rpc1, img2, rpc2, x, y, w, h,
                       filter_matches='fundamental'):
    """
    Computes pointing correction matrix for specific ROI

    Args:
        img1: path to the reference image.
        rpc1: paths to the xml file containing the rpc coefficients of the
            reference image
        img2: path to the secondary image.
        rpc2: paths to the xml file containing the rpc coefficients of the
            secondary image
        x, y, w, h: four integers defining the rectangular ROI in the reference
            image. (x, y) is the top-left corner, and (w, h) are the dimensions
            of the rectangle. The ROI may be as big as you want. If bigger than
            1 Mpix, only five crops will be used to compute sift matches.
        filter_matches (optional, default is 'fundamental'): model imposed by
            RANSAC when searching the set of inliers

    Returns:
        a 3x3 matrix representing the planar transformation to apply to img2 in
        order to correct the pointing error, and the list of sift matches used
        to compute this correction.
    """
    # read rpcs
    r1 = rpc_model.RPCModel(rpc1)
    r2 = rpc_model.RPCModel(rpc2)

    try:
        if w*h < 2e6:
            m = filtered_sift_matches_roi(img1, img2, r1, r2, x, y, w, h,
                                          filter_matches)
        else:
            m = filtered_sift_matches_full_img(img1, img2, r1, r2,
                                               cfg['pointing_correction_rois_mode'],
                                               None, 1000, x, y, w, h,
                                               model=filter_matches)
    except Exception as e:
        print e
        print "WARNING: pointing_accuracy.compute_correction: no sift matches."
        m = None

    # A = optimize_pair(img1, img2, r1, r2, None, m)
    if m is not None:
        A = local_translation(r1, r2, x, y, w, h, m)
    else:
        A = None

    return A, m
예제 #4
0
def crop_rpc_and_image(out_dir, img, rpc, rpc_ref, x, y, w, h):
    """
    Crops an image and its rpc. The ROI may be defined on another image.

    Args:
        out_dir: path to the output directory. The cropped image and rpc files
            will be written there.
        img: path to the input image
        rpc: path to the input rpc
        rpc_ref: path to the rpc file of the reference image
        x, y, w, h: 4 integers defining a rectangular ROI in the reference
            image
    """
    r = rpc_model.RPCModel(rpc)

    # recompute the roi if the input image is not the reference image
    if rpc_ref is not rpc:
        r_ref = rpc_model.RPCModel(rpc_ref)
        x, y, w, h = rpc_utils.corresponding_roi(r_ref, r, x, y, w, h)

    # output filenames
    crop_rpc_and_image.counter += 1
    s = "_%02d" % crop_rpc_and_image.counter
    out_img_file = os.path.join(out_dir, "img%s.tif" % s)
    out_rpc_file = os.path.join(out_dir, "rpc%s.xml" % s)
    out_prv_file = os.path.join(out_dir, "prv%s.png" % s)

    # do the crop
    out_r = rpc_apply_crop_to_rpc_model(r, x, y, w, h)
    out_r.write(out_rpc_file)
    common.run('gdal_translate -srcwin %d %d %d %d "%s" "%s"' %
               (x, y, w, h, img, out_img_file))

    # do the preview: it has to fit a 1366x768 rectangle
    w = float(w)
    h = float(h)
    if w > 1366 or h > 768:
        if w / h > float(1366) / 768:
            f = w / 1366
        else:
            f = h / 768
        tmp = common.tmpfile('.tif')
        common.image_zoom_gdal(out_img_file, f, tmp, w, h)
        common.run('gdal_translate -of png -ot Byte -scale %s %s' %
                   (tmp, out_prv_file))
    else:
        common.run('gdal_translate -of png -ot Byte -scale %s %s' %
                   (out_img_file, out_prv_file))
    common.run('rm %s.aux.xml' % out_prv_file)
예제 #5
0
파일: srtm.py 프로젝트: ogsdave/rpc_cropper
def list_srtm_tiles(rpcfile, x, y, w, h):
    """
    Tells which srtm tiles are needed to cover a given region of interest.

    Args:
        rpcfile: path to the xml file describing the rpc model.
        x, y, w, h: four integers defining a rectangular region of interest
            (ROI) in the image. (x, y) is the top-left corner, and (w, h) are
            the dimensions of the rectangle.

    Returns:
        the set of srtm tiles corresponding to the input ROI.
    """
    rpc = rpc_model.RPCModel(rpcfile)
    lon_min, lon_max, lat_min, lat_max = rpc_utils.geodesic_bounding_box(
        rpc, x, y, w, h)
    out = []
    for lon in [lon_min, lon_max]:
        for lat in [lat_min, lat_max]:
            p = subprocess.Popen(
                ['srtm4_which_tile', str(lon),
                 str(lat)],
                stdout=subprocess.PIPE)
            out.append(p.stdout.readline().split()[0])
    out = set(out)
    print("Needed srtm tiles: ", out)
    return out
예제 #6
0
def rpc_from_geotiff(geotiff_path, outrpcfile='.rpc'):
    """
    Reads the RPC from a geotiff file
    returns the RPC in a rpc_model object
    """
    env = os.environ.copy()
    if geotiff_path.startswith(('http://', 'https://')):
        env['CPL_VSIL_CURL_ALLOWED_EXTENSIONS'] = geotiff_path[-3:]
        path = '/vsicurl/{}'.format(geotiff_path)
    else:
        path = geotiff_path

    f = open(outrpcfile, 'wb')
    x = subprocess.Popen(["gdalinfo", path], stdout=subprocess.PIPE).communicate()[0]
    x = x.splitlines()
    for l in x:

        if(1):
            if (b'SAMP_' not in l) and (b'LINE_' not in l) and (b'HEIGHT_' not in l) and (b'LAT_' not in l) and (b'LONG_' not in l) and (b'MAX_' not in l) and (b'MIN_' not in l):
                  continue
            y = l.strip().replace(b'=',b': ')
            if b'COEFF' in y:
                  z = y.split(b' ')
                  t=1
                  for j in z[1:]:
                          f.write(b'%s_%d: %s\n'%(z[0][:-1],t,j))
                          t+=1
            else:
                  f.write((y+b'\n'))
    f.close()
    return rpc_model.RPCModel(outrpcfile)
예제 #7
0
def utm_zone(rpc, x, y, w, h):
    """
    Compute the UTM zone where the ROI probably falls (or close to its border).

    Args:
        rpc: instance of the rpc_model.RPCModel class, or path to the xml file
        x, y, w, h: four integers defining a rectangular region of interest
            (ROI) in the image. (x, y) is the top-left corner, and (w, h)
            are the dimensions of the rectangle.

    Returns:
        a string of the form '18N' or '18S' where 18 is the utm zone
        identificator.
    """
    # read rpc file
    if not isinstance(rpc, rpc_model.RPCModel):
        rpc = rpc_model.RPCModel(rpc)

    # determine lat lon of the center of the roi, assuming median altitude
    lon, lat = rpc.direct_estimate(x + .5*w, y + .5*h, rpc.altOff)[:2]

    # compute the utm zone number and add the hemisphere letter
    zone = utm.conversion.latlon_to_zone_number(lat, lon)
    if lat < 0:
        return '%dS' % zone
    else:
        return '%dN' % zone
예제 #8
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)
예제 #9
0
def rpc_from_geotiff(geotiff_path):
    """
    Read the RPC coefficients from a GeoTIFF file and return a rpc_model object.

    Args:
        geotiff_path (str): path or url to a GeoTIFF file

    Returns:
        instance of the rpc_model.RPCModel class
    """
    with rasterio.open(geotiff_path, 'r') as src:
        rpc_dict = src.tags(ns='RPC')
    return rpc_model.RPCModel(rpc_dict)
예제 #10
0
def save_sift_matches_all_datasets(data):
    """
    Run the filtered_sift_matches_full_img function on all the datasets.

    Args:
        data: path to the folder containing all the Pleiades datasets

    Returns:
        nothing. For each dataset, it saves the list of sift matches computed
        between images 1 and 2, according to the ROIs defined in the
        corresponding file.
    """
    for f in os.listdir(data):
        dataset = os.path.join(data, f)
        im1  = os.path.join(dataset, 'im01.tif')
        im2  = os.path.join(dataset, 'im02.tif')
        rpc1 = '%s/../rpc/%s/rpc01.xml' % (data, f)
        rpc2 = '%s/../rpc/%s/rpc02.xml' % (data, f)
        rpc1 = rpc_model.RPCModel(rpc1)
        rpc2 = rpc_model.RPCModel(rpc2)
        fname = os.path.join(dataset, 'sift_matches.txt')
        print fname
        m = filtered_sift_matches_full_img(im1, im2, rpc1, rpc2, 'load', None,
            1000, None, None, None, None, fname)
예제 #11
0
def test_me(rpcfile):
    import numpy as np
    import rpc_model, rpc_crop
    reload(rpc_crop)
    r1 = rpc_model.RPCModel(
        'RPC_PHR1A_P_201309231105136_SEN_756965101-001.XML')
    r2 = rpc_crop.rpc_apply_crop_to_rpc_model(r1, 10000, 20000, 2000, 2000)

    #print "direct estimate error:"
    geo1 = np.array(
        r1.direct_estimate(11000, 20100, 10, return_normalized=False))
    geo2 = np.array(r2.direct_estimate(1000, 100, 10, return_normalized=False))
    print geo1 - geo2

    #print "inverse estimate error:"
    pix1 = np.array(r1.inverse_estimate(geo1[0], geo1[1], geo1[2]))
    pix2 = np.array(r2.inverse_estimate(geo1[0], geo1[1], geo1[2]))
    print pix1 - pix2

    r2.write('cropped.xml')
def main():
    """
    launch the compute_projective_error method with default params
    """
    # default parameters
    rpc = rpc_model.RPCModel('../rpc_data/haiti/rpc01.xml')
    col, row, w, h = 15000, 20000, 2000, 1000

    # check parameters
    if len(sys.argv) > 4:
        col = int(sys.argv[1])
        row = int(sys.argv[2])
        w = int(sys.argv[3])
        h = int(sys.argv[4])
    elif len(sys.argv) > 1:
        print "Incorrect syntax, use:"
        print '  > ' + sys.argv[0] + " col row w h"
        sys.exit(1)

    err = camera_error(rpc, col, row, w, h, 5, 7)
    print 'projective error: ', err
예제 #13
0
def plot_matches_pleiades(im1, im2, rpc1, rpc2, matches, x=None, y=None,
        w=None, h=None, outfile=None):
    """
    Plot matches on Pleiades images

    Args:
        im1, im2: paths to full Pleiades images
        rpc1, rpc2: paths to xml files containing the rpc coefficients
        matches: 2D numpy array of size 4xN containing a list of matches (a
            list of pairs of points, each pair being represented by x1, y1, x2,
            y2). The coordinates are given in the frame of the full images.
        x, y, w, h (optional, default is None): ROI in the reference image
        outfile (optional, default is None): path to the output file. If None,
            the file image is displayed using the pvflip viewer

    Returns:
        path to the displayed output
    """
    # if no matches, no plot
    if not matches.size:
        print "visualisation.plot_matches_pleiades: nothing to plot"
        return

    # read rpcs
    r1 = rpc_model.RPCModel(rpc1)
    r2 = rpc_model.RPCModel(rpc2)

    # determine regions to crop in im1 and im2
    if x is not None:
        x1 = x
    else:
        x1 = np.min(matches[:, 0])

    if y is not None:
        y1 = y
    else:
        y1 = np.min(matches[:, 1])

    if w is not None:
        w1 = w
    else:
        w1 = np.max(matches[:, 0]) - x1

    if h is not None:
        h1 = h
    else:
        h1 = np.max(matches[:, 1]) - y1

    x2, y2, w2, h2 = rpc_utils.corresponding_roi(r1, r2, x1, y1, w1, h1)
    # x2 = np.min(matches[:, 2])
    # w2 = np.max(matches[:, 2]) - x2
    # y2 = np.min(matches[:, 3])
    # h2 = np.max(matches[:, 3]) - y2

    # # add 20 pixels offset and round. The image_crop_TIFF function will round
    # # off the coordinates before it does the crops.
    # x1 -= 20; x1 = np.round(x1)
    # y1 -= 20; y1 = np.round(y1)
    # x2 -= 20; x2 = np.round(x2)
    # y2 -= 20; y2 = np.round(y2)
    # w1 += 40; w1 = np.round(w1)
    # h1 += 40; h1 = np.round(h1)
    # w2 += 40; w2 = np.round(w2)
    # h2 += 40; h2 = np.round(h2)

    # do the crops
    crop1 = common.image_qauto(common.image_crop_TIFF(im1, x1, y1, w1, h1))
    crop2 = common.image_qauto(common.image_crop_TIFF(im2, x2, y2, w2, h2))

    # compute matches coordinates in the cropped images
    pts1 = matches[:, 0:2] - [x1, y1]
    pts2 = matches[:, 2:4] - [x2, y2]

    # plot the matches on the two crops
    to_display = plot_matches(crop1, crop2, np.hstack((pts1, pts2)))
    if outfile is None:
        os.system('v %s &' % (to_display))
    else:
        common.run('cp %s %s' % (to_display, outfile))

    return
예제 #14
0
def rectify_pair(im1,
                 im2,
                 rpc1,
                 rpc2,
                 x,
                 y,
                 w,
                 h,
                 out1,
                 out2,
                 A=None,
                 m=None,
                 flag='rpc'):
    """
    Rectify a ROI in a pair of Pleiades images.

    Args:
        im1, im2: paths to the two Pleiades images (usually jp2 or tif)
        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 crops
        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 sift matches, in the
            full image coordinates frame
        flag (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 (big) images.
        disp_min, disp_max: horizontal disparity range
    """
    # read RPC data
    rpc1 = rpc_model.RPCModel(rpc1)
    rpc2 = rpc_model.RPCModel(rpc2)

    # compute rectifying homographies
    if flag == 'rpc':
        H1, H2, disp_min, disp_max = compute_rectification_homographies(
            im1, im2, rpc1, rpc2, x, y, w, h, A, m)
    else:
        H1, H2, disp_min, disp_max = compute_rectification_homographies_sift(
            im1, im2, rpc1, rpc2, x, y, w, h)

    # 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:
        from math import floor, ceil
        # update the H1 and H2 to reflect the zoom
        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_min = floor(disp_min / cfg['subsampling_factor'])
        disp_max = ceil(disp_max / cfg['subsampling_factor'])

    return H1, H2, disp_min, disp_max
예제 #15
0
파일: utils.py 프로젝트: jeanbastin/tsd
def rpc_from_geotiff(geotiff_path):
    """
    """
    with rasterio.open(geotiff_path, 'r') as src:
        rpc_dict = src.tags(ns='RPC')
    return rpc_model.RPCModel(rpc_dict)
예제 #16
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