def read_frame(i): ts, path = frames[i] frame, inp, scales = read_image(str(Path(opt.input, path)), device, opt.resize, 0, False) ts_depth = associate_ts(ts, depths) depth = cv2.imread(str(Path(opt.input, depths[ts_depth])), cv2.IMREAD_ANYDEPTH) depth = depth.astype(np.float) / 5000 assert depth.shape[:2] == frame.shape[:2] return frame, inp, depth, scales
def set_anchor(self, img_name): anchor_tensor = read_image(self.input_dir + img_name) self.data['image0'] = anchor_tensor anchor_feature = self.superpoint({'image': anchor_tensor}) self.data = { **self.data, **{k + '0': v for k, v in anchor_feature.items()} }
def set_match(self, match_name): # put image to match into the data # 似乎更慢了 match_tensor = read_image(self.input_dir + match_name) self.data['image1'] = match_tensor match_feature = self.superpoint({'image': match_tensor}) if self.data == None: self.data = {k + '1': v for k, v in match_feature.items()} else: self.data = { **self.data, **{k + '1': v for k, v in match_feature.items()} }
def matching_2d(query_path, img_candidate_path, matching, device, scale=False): resize_outdoor = [-1] resize_float = True rot0, rot1 = 0, 0 image0, inp0, scales0 = read_image(query_path, device, resize_outdoor, rot0, resize_float) image1, inp1, scales1 = read_image(img_candidate_path, device, resize_outdoor, rot1, resize_float) # if image0 is None or image1 is None: # print('Problem reading image pair: {} {}'.format( # input_dir/name0, input_dir/name1)) # exit(1) if image0.shape != image1.shape: print('Size of image is not same') exit(1) pred = matching({'image0': inp0, 'image1': inp1}) pred = {k: v[0].cpu().detach().numpy() for k, v in pred.items()} kpts0, kpts1 = pred['keypoints0'], pred['keypoints1'] matches, conf = pred['matches0'], pred['matching_scores0'] valid = matches > -1 mkpts0 = kpts0[valid] mkpts1 = kpts1[matches[valid]] mconf = conf[valid] if scale: query_xy = mkpts0 * scales0 db_xy = mkpts1 * scales1 return query_xy, db_xy, mconf return mkpts0, mkpts1
def on_train_epoch_end(self, outputs) -> None: for eval_data in self.hparams.eval: with open(f'{ROOT_PATH}/' + eval_data.pairs_list, 'r') as f: pairs = [l.split() for l in f.readlines()] if eval_data.max_length > -1: pairs = pairs[0:np.min([len(pairs), eval_data.max_length])] if eval_data.shuffle: random.Random(0).shuffle(pairs) if not all([len(p) == 38 for p in pairs]): raise ValueError( 'All pairs should have ground truth info for evaluation.' 'File \"{}\" needs 38 valid entries per row'.format( eval_data.pairs_list)) # Load the SuperPoint and SuperGlue models. device = 'cuda' if torch.cuda.is_available() else 'cpu' print('Running inference on device \"{}\"'.format(device)) config = { 'superpoint': { 'nms_radius': eval_data.nms_radius, 'keypoint_threshold': eval_data.keypoint_threshold, 'max_keypoints': eval_data.max_keypoints }, 'superglue': self.hparams.model.superglue, } matching = Matching(config).eval().to(device) matching.superglue.load_state_dict(self.superglue.state_dict()) # Create the output directories if they do not exist already. data_dir = Path(f'{ROOT_PATH}/' + eval_data.data_dir) # moving_dir = Path(f'{ROOT_PATH}/' + 'data/ScanNet/test_subset') print('Looking for data in directory \"{}\"'.format(data_dir)) results_dir = Path(os.getcwd() + '/' + eval_data.results_dir) results_dir.mkdir(exist_ok=True, parents=True) print('Will write matches to directory \"{}\"'.format(results_dir)) timer = AverageTimer(newline=True) for i, pair in enumerate(pairs): name0, name1 = pair[:2] stem0, stem1 = Path(name0).stem, Path(name1).stem matches_path = results_dir / '{}_{}_matches.npz'.format( stem0, stem1) eval_path = results_dir / '{}_{}_evaluation.npz'.format( stem0, stem1) viz_path = results_dir / '{}_{}_matches.{}'.format( stem0, stem1, self.hparams.exp.viz_extension) viz_eval_path = results_dir / \ '{}_{}_evaluation.{}'.format(stem0, stem1, self.hparams.exp.viz_extension) # Handle --cache logic. do_match = True do_eval = True do_viz = self.hparams.exp.viz do_viz_eval = self.hparams.exp.viz # if opt.cache: # if matches_path.exists(): # try: # results = np.load(matches_path) # except: # raise IOError('Cannot load matches .npz file: %s' % # matches_path) # # kpts0, kpts1 = results['keypoints0'], results['keypoints1'] # matches, conf = results['matches'], results['match_confidence'] # do_match = False # if opt.eval and eval_path.exists(): # try: # results = np.load(eval_path) # except: # raise IOError('Cannot load eval .npz file: %s' % eval_path) # err_R, err_t = results['error_R'], results['error_t'] # precision = results['precision'] # matching_score = results['matching_score'] # num_correct = results['num_correct'] # epi_errs = results['epipolar_errors'] # do_eval = False # if opt.viz and viz_path.exists(): # do_viz = False # if opt.viz and opt.eval and viz_eval_path.exists(): # do_viz_eval = False # timer.update('load_cache') if not (do_match or do_eval or do_viz or do_viz_eval): timer.print('Finished pair {:5} of {:5}'.format( i, len(pairs))) continue # If a rotation integer is provided (e.g. from EXIF data), use it: if len(pair) >= 5: rot0, rot1 = int(pair[2]), int(pair[3]) else: rot0, rot1 = 0, 0 # Load the image pair. image0, inp0, scales0 = read_image(data_dir / name0, eval_data.resize, rot0, eval_data.resize_float) image1, inp1, scales1 = read_image(data_dir / name1, eval_data.resize, rot1, eval_data.resize_float) # Moving # os.makedirs(os.path.dirname(moving_dir / name0), exist_ok=True) # os.makedirs(os.path.dirname(moving_dir / name1), exist_ok=True) # shutil.copy(data_dir / name0, moving_dir / name0) # shutil.copy(data_dir / name1, moving_dir / name1) if image0 is None or image1 is None: print('Problem reading image pair: {} {}'.format( data_dir / name0, data_dir / name1)) exit(1) timer.update('load_image') if do_match: # Perform the matching. with torch.no_grad(): pred = matching({ 'image0': inp0.cuda(), 'image1': inp1.cuda() }) pred_np = {} for (k, v) in pred.items(): if isinstance(v, list): pred_np[k] = v[0].cpu().numpy() elif isinstance(v, torch.Tensor): pred_np[k] = v[0].cpu().numpy() pred = pred_np # pred = {k: v[0].cpu().numpy() for k, v in pred.items() if isinstance(v, torch.Tensor)} kpts0, kpts1 = pred['keypoints0'], pred['keypoints1'] matches, conf = pred['matches0'], pred['matching_scores0'] timer.update('matcher') # Write the matches to disk. out_matches = { 'keypoints0': kpts0, 'keypoints1': kpts1, 'matches': matches, 'match_confidence': conf } np.savez(str(matches_path), **out_matches) # Keep the matching keypoints. valid = matches > -1 mkpts0 = kpts0[valid] mkpts1 = kpts1[matches[valid]] mconf = conf[valid] if do_eval: # Estimate the pose and compute the pose error. assert len( pair) == 38, 'Pair does not have ground truth info' K0 = np.array(pair[4:13]).astype(float).reshape(3, 3) K1 = np.array(pair[13:22]).astype(float).reshape(3, 3) T_0to1 = np.array(pair[22:]).astype(float).reshape(4, 4) # Scale the intrinsics to resized image. K0 = scale_intrinsics(K0, scales0) K1 = scale_intrinsics(K1, scales1) # Update the intrinsics + extrinsics if EXIF rotation was found. if rot0 != 0 or rot1 != 0: cam0_T_w = np.eye(4) cam1_T_w = T_0to1 if rot0 != 0: K0 = rotate_intrinsics(K0, image0.shape, rot0) cam0_T_w = rotate_pose_inplane(cam0_T_w, rot0) if rot1 != 0: K1 = rotate_intrinsics(K1, image1.shape, rot1) cam1_T_w = rotate_pose_inplane(cam1_T_w, rot1) cam1_T_cam0 = cam1_T_w @ np.linalg.inv(cam0_T_w) T_0to1 = cam1_T_cam0 epi_errs = compute_epipolar_error(mkpts0, mkpts1, T_0to1, K0, K1) correct = epi_errs < 5e-4 num_correct = np.sum(correct) precision = np.mean(correct) if len(correct) > 0 else 0 matching_score = num_correct / len(kpts0) if len( kpts0) > 0 else 0 thresh = 1. # In pixels relative to resized image size. ret = estimate_pose(mkpts0, mkpts1, K0, K1, thresh) if ret is None: err_t, err_R = np.inf, np.inf else: R, t, inliers = ret err_t, err_R = compute_pose_error(T_0to1, R, t) # Write the evaluation results to disk. out_eval = { 'error_t': err_t, 'error_R': err_R, 'precision': precision, 'matching_score': matching_score, 'num_correct': num_correct, 'epipolar_errors': epi_errs } np.savez(str(eval_path), **out_eval) timer.update('eval') # if do_viz: # # Visualize the matches. # color = cm.jet(mconf) # text = [ # 'SuperGlue', # 'Keypoints: {}:{}'.format(len(kpts0), len(kpts1)), # 'Matches: {}'.format(len(mkpts0)), # ] # if rot0 != 0 or rot1 != 0: # text.append('Rotation: {}:{}'.format(rot0, rot1)) # # make_matching_plot( # image0, image1, kpts0, kpts1, mkpts0, mkpts1, color, # text, viz_path, stem0, stem1, opt.show_keypoints, # opt.fast_viz, opt.opencv_display, 'Matches') # # timer.update('viz_match') # # if do_viz_eval: # # Visualize the evaluation results for the image pair. # color = np.clip((epi_errs - 0) / (1e-3 - 0), 0, 1) # color = error_colormap(1 - color) # deg, delta = ' deg', 'Delta ' # if not opt.fast_viz: # deg, delta = '°', '$\\Delta$' # e_t = 'FAIL' if np.isinf(err_t) else '{:.1f}{}'.format(err_t, deg) # e_R = 'FAIL' if np.isinf(err_R) else '{:.1f}{}'.format(err_R, deg) # text = [ # 'SuperGlue', # '{}R: {}'.format(delta, e_R), '{}t: {}'.format(delta, e_t), # 'inliers: {}/{}'.format(num_correct, (matches > -1).sum()), # ] # if rot0 != 0 or rot1 != 0: # text.append('Rotation: {}:{}'.format(rot0, rot1)) # # make_matching_plot( # image0, image1, kpts0, kpts1, mkpts0, # mkpts1, color, text, viz_eval_path, # stem0, stem1, opt.show_keypoints, # opt.fast_viz, opt.opencv_display, 'Relative Pose') # # timer.update('viz_eval') timer.print('Finished pair {:5} of {:5}'.format(i, len(pairs))) # Collate the results into a final table and print to terminal. pose_errors = [] precisions = [] matching_scores = [] for pair in pairs: name0, name1 = pair[:2] stem0, stem1 = Path(name0).stem, Path(name1).stem eval_path = results_dir / \ '{}_{}_evaluation.npz'.format(stem0, stem1) results = np.load(eval_path) pose_error = np.maximum(results['error_t'], results['error_R']) pose_errors.append(pose_error) precisions.append(results['precision']) matching_scores.append(results['matching_score']) thresholds = [5, 10, 20] aucs = pose_auc(pose_errors, thresholds) aucs = [100. * yy for yy in aucs] prec = 100. * np.mean(precisions) ms = 100. * np.mean(matching_scores) print('Evaluation Results (mean over {} pairs):'.format( len(pairs))) print('AUC@5\t AUC@10\t AUC@20\t Prec\t MScore\t') print('{:.2f}\t {:.2f}\t {:.2f}\t {:.2f}\t {:.2f}\t'.format( aucs[0], aucs[1], aucs[2], prec, ms)) self.log(f'{eval_data.name}/AUC_5', aucs[0], on_epoch=True, on_step=False) self.log(f'{eval_data.name}/AUC_10', aucs[1], on_epoch=True, on_step=False) self.log(f'{eval_data.name}/AUC_20', aucs[2], on_epoch=True, on_step=False) self.log(f'{eval_data.name}/Prec', prec, on_epoch=True, on_step=False) self.log(f'{eval_data.name}/MScore', ms, on_epoch=True, on_step=False)
timer = AverageTimer(newline=True) for i, pair in enumerate(pairs): name0, name1, id0, id1 = pair[:4] stem0, stem1 = Path(name0).stem, Path(name1).stem viz_path = output_dir / '{}_{}_matches.png'.format(id0, id1) # Handle --cache logic. do_viz = opt.viz # If a rotation integer is provided (e.g. from EXIF data), use it: mvg_match_num = pair[-1] rot0, rot1 = 0, 0 # Load the image pair. if id0 not in image_tensor.keys(): _, inp0, _ = read_image(input_dir / name0, device, opt.resize, rot0, opt.resize_float) image_tensor[id0] = inp0 if id1 not in image_tensor.keys(): _, inp1, _ = read_image(input_dir / name1, device, opt.resize, rot1, opt.resize_float) image_tensor[id1] = inp1 if image_tensor[id0] is None or image_tensor[id1] is None: print('Problem reading image pair: {} {}'.format( input_dir / name0, input_dir / name1)) exit(1) timer.update('load_image') if id0 not in keypoints_tensor.keys(): pred0 = superpoint({'image': image_tensor[id0]}) keypoints_tensor[id0] = pred0 response[id0] = pred0['response'].cpu().numpy()
timer.update('load_cache') if not (do_match or do_eval or do_viz or do_viz_eval): timer.print('Finished pair {:5} of {:5}'.format( i, len(pairs))) continue # If a rotation integer is provided (e.g. from EXIF data), use it: if len(pair) >= 5: rot0, rot1 = int(pair[2]), int(pair[3]) else: rot0, rot1 = 0, 0 # Load the image pair. image0, inp0, scales0 = read_image(eval_input_dir / name0, opt.resize, rot0, opt.resize_float) image1, inp1, scales1 = read_image(eval_input_dir / name1, opt.resize, rot1, opt.resize_float) if image0 is None or image1 is None: print('Problem reading image pair: {} {}'.format( eval_input_dir / name0, eval_input_dir / name1)) exit(1) timer.update('load_image') if do_match: # Perform the matching. # pred_eval = matching({'image0': inp0, 'image1': inp1}) data = {'image0': inp0, 'image1': inp1}
if opt.viz and opt.eval and viz_eval_path.exists(): do_viz_eval = False timer.update('load_cache') if not (do_match or do_eval or do_viz or do_viz_eval): timer.print('Finished pair {:5} of {:5}'.format(i, len(pairs))) continue # If a rotation integer is provided (e.g. from EXIF data), use it: if len(pair) >= 5: rot0, rot1 = int(pair[2]), int(pair[3]) else: rot0, rot1 = 0, 0 # Load the image pair. image0, inp0, scales0 = read_image(data_dir / name0, device, opt.resize, rot0, opt.resize_float) image1, inp1, scales1 = read_image(data_dir / name1, device, opt.resize, rot1, opt.resize_float) if image0 is None or image1 is None: print('Problem reading image pair: {} {}'.format( data_dir / name0, data_dir / name1)) exit(1) timer.update('load_image') if do_match: # Perform the matching. pred = matching({'image0': inp0, 'image1': inp1}) pred = {k: v[0].cpu().numpy() for k, v in pred.items()} kpts0, kpts1 = pred['keypoints0'], pred['keypoints1'] matches, conf = pred['matches0'], pred['matching_scores0'] timer.update('matcher')
def get_keypoints(img1: np.ndarray, img2: np.ndarray, max_keypoints: int, num_img: str, visualize: bool, resize: list, match_path_exists: bool, dataset: str, mode: str): ''' Retrieves pose from the keypoints of the images based on the given keypoint matching algorithm. Inputs: - img1: left image (undistorted) - img2: right image (undistorted) - max_keypoints: maximum number of keypoints matching tool should consider - num_img: frame number - visualize: indicates whether visualizations should be done and saved - resize: dimensions at which images should be resized - match_path_exists: indicates for SuperGlue if there are saved matches or if it should redo the matches - mode: keypoint matching algorithm in use - dataset: current dataset being evaluated Outputs: - R: recovered rotation matrix - T: recovered translation vector - mkpts1: matched keypoints in left image - mkpts2: matched keypoints in right image ''' left, _inp, left_scale = read_image(img1, device, resize, 0, False) right, _inp, right_scale = read_image(img2, device, resize, 0, False) left = left.astype('uint8') right = right.astype('uint8') i1, K1, distCoeffs1 = read("data/intrinsics/" + dataset + "_left.yaml") i2, K2, distCoeffs2 = read("data/intrinsics/" + dataset + "_right.yaml") K1 = scale_intrinsics(K1, left_scale) K2 = scale_intrinsics(K2, right_scale) if mode == "superglue": input_pair = "data/pairs/kitti_pairs_" + num_img + ".txt" npz_name = "left_" + num_img + "_right_" + num_img out_dir = "data/matches/" mkpts1, mkpts2 = get_SuperGlue_keypoints(input_pair, out_dir, npz_name, max_keypoints, visualize, resize, match_path_exists) elif mode == "sift": mkpts1, mkpts2 = get_SIFT_keypoints(left, right, max_keypoints) elif mode == "orb": mkpts1, mkpts2 = get_ORB_keypoints(left, right, max_keypoints) R, T, F, _E = recover_pose(mkpts1, mkpts2, K1, K2) left_rectified, right_rectified = rectify(left, right, K1, distCoeffs1, K2, distCoeffs2, R, kitti_T_gt) if visualize: text = [mode, "Best 100 of " + str(max_keypoints) + " keypoints"] colors = np.array(['red'] * len(mkpts1)) res_path = str("results/matches/" + mode + "/") match_dir = Path(res_path) match_dir.mkdir(parents=True, exist_ok=True) path = res_path + dataset + "_" + mode + "_matches_" + num_img + ".png" make_matching_plot(left, right, mkpts1, mkpts2, mkpts1, mkpts2, colors, text, path, show_keypoints=False, fast_viz=False, opencv_display=False, opencv_title='matches', small_text=[]) save_disp_path = "results/disparity/" + mode + "/" disp_dir = Path(save_disp_path) disp_dir.mkdir(parents=True, exist_ok=True) disp = get_disparity(left_rectified, right_rectified, maxDisparity=128) plt.imsave(save_disp_path + dataset + "_" + mode + "_disp_" + num_img + ".png", disp, cmap="jet") return R, T, mkpts1, mkpts2
from models.matching import Matching from models.utils import read_image from functions import root_dir config = { 'superpoint': { 'nms_radius': 4, 'keypoint_threshold': 0.005, 'max_keypoints': 1024 }, 'superglue': { 'weights': 'outdoor', 'sinkhorn_iterations': 20, 'match_threshold': 0.2 } } # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") device = "cpu" matching = Matching(config).eval().to(device) name0 = root_dir+'/data/snapshots-2016-02-26/img-2016-02-26T13-23-27devID1.jpg' name1 = root_dir+'/data/snapshots-2016-02-26/img-2016-02-26T13-23-27devID2.jpg' rot0, rot1 = 0, 0 # Load the image pair. image0, inp0, scales0 = read_image(name0, device, [1920, 1920], rot0, False) image1, inp1, scales1 = read_image(name1, device, [1920, 1920], rot1, False) # Perform the matching pred = matching({'image0': inp0, 'image1': inp1})
if opt.viz and opt.eval and viz_eval_path.exists(): do_viz_eval = False timer.update('load_cache') if not (do_match or do_eval or do_viz or do_viz_eval): timer.print('Finished pair {:5} of {:5}'.format(i, len(pairs))) continue # If a rotation integer is provided (e.g. from EXIF data), use it: if len(pair) >= 5: rot0, rot1 = int(pair[2]), int(pair[3]) else: rot0, rot1 = 0, 0 # SuperPoint+SuperGlue : Load the image pair. image0, inp0, scales0 = read_image(input_dirQ / name0, device, opt.resize, rot0, opt.resize_float) image1, inp1, scales1 = read_image(input_dirDB / name1, device, opt.resize, rot1, opt.resize_float) if image0 is None or image1 is None: print('Problem reading image pair: {} {}'.format( input_dirQ / name0, input_dirDB / name1)) exit(1) timer.update('load_image') if do_match: # SuperPoint+SuperGlue : Perform the matching. pred = matching({'image0': inp0, 'image1': inp1}) pred = {k: v[0].cpu().numpy() for k, v in pred.items()} kpts0, kpts1 = pred['keypoints0'], pred['keypoints1'] matches, conf = pred['matches0'], pred['matching_scores0']
print('Matching from pair list') with open(opt.pair_list, 'r') as f: pairs = [l.split() for l in f.readlines()] else: print('Exhaustive matching of all images') images = image_dir.rglob(f'*.{opt.image_glob}') images = sorted([str(p.relative_to(image_dir)) for p in images]) pairs = [] # Ordered by (largest key, smallest key) for i in range(len(images)): for j in range(i): pairs.append((images[i], images[j])) for pair in tqdm(pairs): name0, name1 = pair[:2] image0, inp0, scales0 = read_image(image_dir / name0, device, [-1], 0, True, False) image1, inp1, scales1 = read_image(image_dir / name1, device, [-1], 0, True, False) feats = {} if opt.hdf5: feats['keypoints0'] = feat_file[name0]['keypoints'].__array__() feats['descriptors0'] = feat_file[name0]['descriptors'].__array__() feats['scores0'] = feat_file[name0]['scores'].__array__() feats['keypoints1'] = feat_file[name1]['keypoints'].__array__() feats['descriptors1'] = feat_file[name1]['descriptors'].__array__() feats['scores1'] = feat_file[name1]['scores'].__array__() else: with np.load(str(Path(opt.feature_dir, name0 + '.npz'))) as npz: feats['keypoints0'] = npz['keypoints'] feats['descriptors0'] = npz['descriptors']
config = { 'superpoint': { 'nms_radius': opt.nms_radius, 'keypoint_threshold': opt.keypoint_threshold, 'max_keypoints': opt.max_keypoints }, # 'superglue': { # 'weights': opt.superglue, # 'sinkhorn_iterations': opt.sinkhorn_iterations, # 'match_threshold': opt.match_threshold, # } } # Load the SuperPoint and SuperGlue models. device = 'cuda' if torch.cuda.is_available() else 'cpu' superpoint = SuperPoint(config.get('superpoint', {})).cuda() # Load the image pair. image0, inp0, scales0 = read_image(opt.image_path, opt.resize, 0, 1.0) print('inp0 = {}'.format(inp0)) pred = {} # Extract SuperPoint (keypoints, scores, descriptors) if not provided pred0 = superpoint({'image': inp0}) pred = {**pred, **{k + '0': v for k, v in pred0.items()}} print('pred = {}'.format(pred))
'nms_radius': opt.nms_radius, 'refinement_radius': opt.refinement_radius, 'do_quadratic_refinement': opt.quadratic_refinement, } frontend = SuperPoint(config).eval().to(device) results_dir = Path(opt.results_dir) results_dir.mkdir(exist_ok=True, parents=True) if opt.hdf5: hfile = h5py.File(str(results_dir / opt.hdf5), 'w') for name in tqdm(image_list): image, inp, scales = read_image(opt.image_dir / name, device, opt.resize, 0, True, resize_force=not opt.no_resize_force, interp=cv2.INTER_CUBIC) pred = frontend({'image': inp}) pred = {k: v[0].cpu().numpy() for k, v in pred.items()} kpts = (pred['keypoints'] + .5) * np.array([scales]) - .5 out = { 'keypoints': kpts, 'descriptors': pred['descriptors'], 'scores': pred['scores'] } if opt.hdf5:
def set_anchor(self, img_name): anchor_tensor = read_image(self.input_dir + img_name) self.anchor = anchor_tensor self.data.append(self.anchor)
def set_match(self, match_name): # put image to match into the data # 似乎更慢了 match_tensor = read_image(self.input_dir + match_name) self.match = match_tensor self.data.append(self.match)
disp_dir.mkdir(parents=True, exist_ok=True) plt.imsave(save_disp_path + dataset + "_" + mode + "_avg_pose_disp.png", disp, cmap="jet") if do_eval: show_errors(R_ests, kitti_R_gt, dataset, modes) left_imgs = [] right_imgs = [] for i in range(len(img_nums)): img1_path = "data/imgs/left_" + img_nums[i] + ".png" img2_path = "data/imgs/right_" + img_nums[i] + ".png" left, _inp, _left_scale = read_image(img1_path, device, resize, 0, False) right, _inp, _right_scale = read_image(img2_path, device, resize, 0, False) left_imgs.append(left) right_imgs.append(right) visualize_match_distribution(left_imgs, right_imgs, frame_matches_per_mode, modes, dataset, img_nums) if not visualize and not match_path_exists: plot_times(dataset, times_per_mode, modes) frame_matches_per_mode.clear() print("finished " + dataset)