def __init__(self, args: DictConfig): super().__init__() self.hparams = args # Will be logged to mlflow # make sure the flags are properly used assert not (args.exp.opencv_display and not args.exp.viz), 'Must use --viz with --opencv_display' assert not (args.exp.opencv_display and not args.exp.fast_viz ), 'Cannot use --opencv_display without --fast_viz' assert not (args.exp.fast_viz and not args.exp.viz), 'Must use --viz with --fast_viz' assert not (args.exp.fast_viz and args.exp.viz_extension == 'pdf'), 'Cannot use pdf extension with --fast_viz' # store viz results # eval_output_dir = Path(f'{ROOT_PATH}/' + args.data.eval_output_dir) # eval_output_dir.mkdir(exist_ok=True, parents=True) # print('Will write visualization images to directory \"{}\"'.format(eval_output_dir)) self.superglue = SuperGlue(args.model.superglue) self.superpoint = SuperPoint(args.model.superpoint) self.lr = None
def __init__(self, config={}): self.config = self.default_config self.config = {**self.config, **config} #self.config = merge_two_dicts(self.config, config) #print(self.config) logging.info("SuperGlue matcher config: ") logging.info(self.config) self.device = 'cuda' if torch.cuda.is_available( ) and self.config["cuda"] else 'cpu' assert self.config['weights'] in ['indoor', 'outdoor'] path_ = path.abspath( path.join(__file__, "../..") ) + '/models/SuperGluePretrainedNetwork/models/weights/superglue_{}.pth'.format( self.config['weights']) self.config["path"] = path_ logging.info("creating SuperGlue matcher...") self.superglue = SuperGlue(self.config).to(self.device)
class SuperGlueLightning(LightningModule): def __init__(self, args: DictConfig): super().__init__() self.hparams = args # Will be logged to mlflow # make sure the flags are properly used assert not (args.exp.opencv_display and not args.exp.viz), 'Must use --viz with --opencv_display' assert not (args.exp.opencv_display and not args.exp.fast_viz ), 'Cannot use --opencv_display without --fast_viz' assert not (args.exp.fast_viz and not args.exp.viz), 'Must use --viz with --fast_viz' assert not (args.exp.fast_viz and args.exp.viz_extension == 'pdf'), 'Cannot use pdf extension with --fast_viz' # store viz results # eval_output_dir = Path(f'{ROOT_PATH}/' + args.data.eval_output_dir) # eval_output_dir.mkdir(exist_ok=True, parents=True) # print('Will write visualization images to directory \"{}\"'.format(eval_output_dir)) self.superglue = SuperGlue(args.model.superglue) self.superpoint = SuperPoint(args.model.superpoint) self.lr = None def prepare_data(self) -> None: self.train_set = SparseDataset( f'{ROOT_PATH}/' + self.hparams.data.train_path, self.hparams.model.superpoint.max_keypoints, self.hparams.data.resize, self.hparams.data.resize_float, self.hparams.model.superglue.min_keypoints, None, self.superpoint) self.val_set = SparseDataset( f'{ROOT_PATH}/' + self.hparams.data.val_path, self.hparams.model.superpoint.max_keypoints, self.hparams.data.resize, self.hparams.data.resize_float, self.hparams.model.superglue.min_keypoints, None, self.superpoint) self.train_set.files = self.train_set.files[:self.hparams.data. train_size] self.val_set.files = self.val_set.files[:self.hparams.data.val_size] def train_dataloader(self) -> torch.utils.data.DataLoader: return torch.utils.data.DataLoader( dataset=self.train_set, shuffle=True, batch_size=self.hparams.data.batch_size.train, drop_last=True) def val_dataloader(self) -> torch.utils.data.DataLoader: return torch.utils.data.DataLoader( dataset=self.val_set, shuffle=True, batch_size=self.hparams.data.batch_size.val, drop_last=True) def configure_optimizers(self): if self.lr is not None: self.hparams.optimizer.learning_rate = self.lr optimizer = torch.optim.Adam(self.superglue.parameters(), lr=self.hparams.optimizer.learning_rate) return optimizer def training_step(self, batch, batch_ind): for k in batch: if k != 'file_name' and k != 'image0' and k != 'image1': if type(batch[k]) == torch.Tensor: batch[k] = Variable(batch[k]) else: batch[k] = Variable(torch.stack(batch[k])) # print(batch['keypoints0'].shape) data = self.superglue(batch) batch = {**batch, **data} # print(batch['keypoints0'].shape) if batch['skip_train']: # image has no keypoint batch['loss'] = torch.zeros(1, requires_grad=True).type_as( batch['scores0']) return batch['loss'] # Loss & Metrics self.log('train_hits_1', hits_at_one(batch).mean(), on_epoch=False, on_step=True, sync_dist=True) self.log('train_loss_m', batch['loss_m'], on_epoch=False, on_step=True, sync_dist=True) self.log('train_loss_um', batch['loss_um'], on_epoch=False, on_step=True, sync_dist=True) self.log('train_loss', batch['loss'], on_epoch=False, on_step=True, sync_dist=True) self.log('bin_score', self.superglue.bin_score, on_epoch=False, on_step=True, sync_dist=True) return batch['loss'] def validation_step(self, batch, batch_ind): for k in batch: if k != 'file_name' and k != 'image0' and k != 'image1': if type(batch[k]) != torch.Tensor: batch[k] = torch.stack(batch[k]) data = self.superglue(batch) batch = {**batch, **data} if batch['skip_train']: # image has no keypoint batch['loss'] = torch.zeros(1).type_as(batch['scores0']) return batch['loss'] # Loss & Metrics self.log('val_hits_1', hits_at_one(batch).mean(), on_epoch=True, on_step=False, sync_dist=True) self.log('val_loss', batch['loss'], on_epoch=True, on_step=False, sync_dist=True) self.log('val_loss_m', batch['loss_m'], on_epoch=True, on_step=False, sync_dist=True) self.log('val_loss_um', batch['loss_um'], on_epoch=True, on_step=False, sync_dist=True) return batch['loss'] @rank_zero_only 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)
}, 'superglue': { 'weights': opt.superglue, 'sinkhorn_iterations': opt.sinkhorn_iterations, 'match_threshold': opt.match_threshold, } } # load training data train_set = SparseDataset(opt.train_path, opt.max_keypoints) train_loader = torch.utils.data.DataLoader(dataset=train_set, shuffle=False, batch_size=opt.batch_size, drop_last=True) superglue = SuperGlue(config.get('superglue', {})) if torch.cuda.is_available(): superglue.cuda() # make sure it trains on GPU else: print("### CUDA not available ###") optimizer = torch.optim.Adam(superglue.parameters(), lr=opt.learning_rate) mean_loss = [] # start training for epoch in range(1, opt.epoch + 1): epoch_loss = 0 superglue.double().train() for i, pred in enumerate(train_loader): for k in pred: if k != 'file_name' and k != 'image0' and k != 'image1':
'./dataset/warped/', './dataset/sp/', numProcess=7) dataBuilder.buildAll(opt.train_path, opt.hand_path, batchSizeMax=64, saveFlag=1, debug=opt.debug) train_set = SparseDatasetOffline('./dataset/sp/') train_loader = torch.utils.data.DataLoader(dataset=train_set, shuffle=False, batch_size=opt.batch_size, drop_last=True) # superpoint = SuperPoint(config.get('superpoint', {})) superglue = SuperGlue(config.get('superglue', {})) if torch.cuda.is_available(): # superpoint.cuda() superglue.cuda() else: print("### CUDA not available ###") optimizer = torch.optim.Adam(superglue.parameters(), lr=opt.learning_rate) N = train_loader.dataset.__len__() // opt.batch_size writer = SummaryWriter("./logs/" + opt.tensorboardLabel) mean_loss = [] cudaKey = set([ 'keypoints0', 'keypoints1', 'descriptors0', 'descriptors1', 'scores0', 'scores1' ]) for epoch in range(0, opt.epoch):
pred = model(data) data = {**pred, **data} if save_folder is not None: if not os.path.isdir(save_folder): os.makedirs(save_folder) visualize(data, save_folder) result = compute_homography(data, correctness_thresh=homography_thresh) correctness.append(result['correctness']) est_H_mean_dist.append(result['mean_dist']) correctness_ave = np.array(correctness).mean(axis=0) homography_auc = auc(correctness_ave, np.array(homography_thresh)) return homography_auc, np.mean(est_H_mean_dist) if __name__ == '__main__': superglue = SuperGlue(config={}) # superglue = torch.load("exp/model_epoch_4_0.0.pth") superglue.load_state_dict( torch.load("models/weights/superglue_outdoor.pth")) superglue.cuda() dataloader = hpatches_test() homography_auc, mean_dist = validation(superglue, dataloader) print(homography_auc, mean_dist)
# Set data loader if opt.detector == 'superpoint': train_set = SuperPointDataset("", image_list='datasets/train.txt', device='cuda', max_keypoints=opt.max_keypoints) else: RuntimeError('Error detector : {}'.format(opt.detector)) train_loader = torch.utils.data.DataLoader(dataset=train_set, shuffle=False, batch_size=opt.batch_size, drop_last=True) superglue = SuperGlue(config.get('superglue', {})) superglue.load_state_dict( torch.load("models/weights/superglue_outdoor.pth")) if torch.cuda.is_available(): superglue.cuda() else: print("### CUDA not available ###") optimizer = torch.optim.Adam(superglue.parameters(), lr=opt.learning_rate) mean_loss = [] for epoch in range(1, opt.epoch + 1): epoch_loss = 0 superglue.train() # train_loader = tqdm(train_loader)
type=str, required=True, help='Path to the directory in which the .npz results are written') parser.add_argument('--hdf5', type=str) opt = parser.parse_args() print(opt) device = 'cuda' if torch.cuda.is_available() else 'cpu' print('Running inference on device {}'.format(device)) config = { 'weights': opt.superglue, 'sinkhorn_iterations': opt.sinkhorn_iterations, 'match_threshold': opt.match_threshold, } matcher = SuperGlue(config).eval().to(device) # matcher = NearestNeighborMatcher({'mutual_check': True}).eval() image_dir = Path(opt.image_dir) 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') feat_file = h5py.File(opt.feature_dir, 'r') if opt.pair_list: 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')