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
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)
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
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)
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
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)
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
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 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)
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)
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
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
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
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)
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