Spaces:
Running
Running
#! /usr/bin/env python3 | |
# | |
# %BANNER_BEGIN% | |
# --------------------------------------------------------------------- | |
# %COPYRIGHT_BEGIN% | |
# | |
# Magic Leap, Inc. ("COMPANY") CONFIDENTIAL | |
# | |
# Unpublished Copyright (c) 2020 | |
# Magic Leap, Inc., All Rights Reserved. | |
# | |
# NOTICE: All information contained herein is, and remains the property | |
# of COMPANY. The intellectual and technical concepts contained herein | |
# are proprietary to COMPANY and may be covered by U.S. and Foreign | |
# Patents, patents in process, and are protected by trade secret or | |
# copyright law. Dissemination of this information or reproduction of | |
# this material is strictly forbidden unless prior written permission is | |
# obtained from COMPANY. Access to the source code contained herein is | |
# hereby forbidden to anyone except current COMPANY employees, managers | |
# or contractors who have executed Confidentiality and Non-disclosure | |
# agreements explicitly covering such access. | |
# | |
# The copyright notice above does not evidence any actual or intended | |
# publication or disclosure of this source code, which includes | |
# information that is confidential and/or proprietary, and is a trade | |
# secret, of COMPANY. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, | |
# PUBLIC PERFORMANCE, OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS | |
# SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS | |
# STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE LAWS AND | |
# INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE | |
# CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS | |
# TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, | |
# USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. | |
# | |
# %COPYRIGHT_END% | |
# ---------------------------------------------------------------------- | |
# %AUTHORS_BEGIN% | |
# | |
# Originating Authors: Paul-Edouard Sarlin | |
# Daniel DeTone | |
# Tomasz Malisiewicz | |
# | |
# %AUTHORS_END% | |
# --------------------------------------------------------------------*/ | |
# %BANNER_END% | |
from pathlib import Path | |
import argparse | |
import cv2 | |
import matplotlib.cm as cm | |
import torch | |
from models.matching import Matching | |
from models.utils import (AverageTimer, VideoStreamer, | |
make_matching_plot_fast, frame2tensor) | |
torch.set_grad_enabled(False) | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser( | |
description='SuperGlue demo', | |
formatter_class=argparse.ArgumentDefaultsHelpFormatter) | |
parser.add_argument( | |
'--input', type=str, default='0', | |
help='ID of a USB webcam, URL of an IP camera, ' | |
'or path to an image directory or movie file') | |
parser.add_argument( | |
'--output_dir', type=str, default=None, | |
help='Directory where to write output frames (If None, no output)') | |
parser.add_argument( | |
'--image_glob', type=str, nargs='+', default=['*.png', '*.jpg', '*.jpeg'], | |
help='Glob if a directory of images is specified') | |
parser.add_argument( | |
'--skip', type=int, default=1, | |
help='Images to skip if input is a movie or directory') | |
parser.add_argument( | |
'--max_length', type=int, default=1000000, | |
help='Maximum length if input is a movie or directory') | |
parser.add_argument( | |
'--resize', type=int, nargs='+', default=[640, 480], | |
help='Resize the input image before running inference. If two numbers, ' | |
'resize to the exact dimensions, if one number, resize the max ' | |
'dimension, if -1, do not resize') | |
parser.add_argument( | |
'--superglue', choices={'indoor', 'outdoor'}, default='indoor', | |
help='SuperGlue weights') | |
parser.add_argument( | |
'--max_keypoints', type=int, default=-1, | |
help='Maximum number of keypoints detected by Superpoint' | |
' (\'-1\' keeps all keypoints)') | |
parser.add_argument( | |
'--keypoint_threshold', type=float, default=0.005, | |
help='SuperPoint keypoint detector confidence threshold') | |
parser.add_argument( | |
'--nms_radius', type=int, default=4, | |
help='SuperPoint Non Maximum Suppression (NMS) radius' | |
' (Must be positive)') | |
parser.add_argument( | |
'--sinkhorn_iterations', type=int, default=20, | |
help='Number of Sinkhorn iterations performed by SuperGlue') | |
parser.add_argument( | |
'--match_threshold', type=float, default=0.2, | |
help='SuperGlue match threshold') | |
parser.add_argument( | |
'--show_keypoints', action='store_true', | |
help='Show the detected keypoints') | |
parser.add_argument( | |
'--no_display', action='store_true', | |
help='Do not display images to screen. Useful if running remotely') | |
parser.add_argument( | |
'--force_cpu', action='store_true', | |
help='Force pytorch to run in CPU mode.') | |
opt = parser.parse_args() | |
print(opt) | |
if len(opt.resize) == 2 and opt.resize[1] == -1: | |
opt.resize = opt.resize[0:1] | |
if len(opt.resize) == 2: | |
print('Will resize to {}x{} (WxH)'.format( | |
opt.resize[0], opt.resize[1])) | |
elif len(opt.resize) == 1 and opt.resize[0] > 0: | |
print('Will resize max dimension to {}'.format(opt.resize[0])) | |
elif len(opt.resize) == 1: | |
print('Will not resize images') | |
else: | |
raise ValueError('Cannot specify more than two integers for --resize') | |
device = 'cuda' if torch.cuda.is_available() and not opt.force_cpu else 'cpu' | |
print('Running inference on device \"{}\"'.format(device)) | |
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, | |
} | |
} | |
matching = Matching(config).eval().to(device) | |
keys = ['keypoints', 'scores', 'descriptors'] | |
vs = VideoStreamer(opt.input, opt.resize, opt.skip, | |
opt.image_glob, opt.max_length) | |
frame, ret = vs.next_frame() | |
assert ret, 'Error when reading the first frame (try different --input?)' | |
frame_tensor = frame2tensor(frame, device) | |
last_data = matching.superpoint({'image': frame_tensor}) | |
last_data = {k+'0': last_data[k] for k in keys} | |
last_data['image0'] = frame_tensor | |
last_frame = frame | |
last_image_id = 0 | |
if opt.output_dir is not None: | |
print('==> Will write outputs to {}'.format(opt.output_dir)) | |
Path(opt.output_dir).mkdir(exist_ok=True) | |
# Create a window to display the demo. | |
if not opt.no_display: | |
cv2.namedWindow('SuperGlue matches', cv2.WINDOW_NORMAL) | |
cv2.resizeWindow('SuperGlue matches', 640*2, 480) | |
else: | |
print('Skipping visualization, will not show a GUI.') | |
# Print the keyboard help menu. | |
print('==> Keyboard control:\n' | |
'\tn: select the current frame as the anchor\n' | |
'\te/r: increase/decrease the keypoint confidence threshold\n' | |
'\td/f: increase/decrease the match filtering threshold\n' | |
'\tk: toggle the visualization of keypoints\n' | |
'\tq: quit') | |
timer = AverageTimer() | |
while True: | |
frame, ret = vs.next_frame() | |
if not ret: | |
print('Finished demo_superglue.py') | |
break | |
timer.update('data') | |
stem0, stem1 = last_image_id, vs.i - 1 | |
frame_tensor = frame2tensor(frame, device) | |
pred = matching({**last_data, 'image1': frame_tensor}) | |
kpts0 = last_data['keypoints0'][0].cpu().numpy() | |
kpts1 = pred['keypoints1'][0].cpu().numpy() | |
matches = pred['matches0'][0].cpu().numpy() | |
confidence = pred['matching_scores0'][0].cpu().numpy() | |
timer.update('forward') | |
valid = matches > -1 | |
mkpts0 = kpts0[valid] | |
mkpts1 = kpts1[matches[valid]] | |
color = cm.jet(confidence[valid]) | |
text = [ | |
'SuperGlue', | |
'Keypoints: {}:{}'.format(len(kpts0), len(kpts1)), | |
'Matches: {}'.format(len(mkpts0)) | |
] | |
k_thresh = matching.superpoint.config['keypoint_threshold'] | |
m_thresh = matching.superglue.config['match_threshold'] | |
small_text = [ | |
'Keypoint Threshold: {:.4f}'.format(k_thresh), | |
'Match Threshold: {:.2f}'.format(m_thresh), | |
'Image Pair: {:06}:{:06}'.format(stem0, stem1), | |
] | |
out = make_matching_plot_fast( | |
last_frame, frame, kpts0, kpts1, mkpts0, mkpts1, color, text, | |
path=None, show_keypoints=opt.show_keypoints, small_text=small_text) | |
if not opt.no_display: | |
cv2.imshow('SuperGlue matches', out) | |
key = chr(cv2.waitKey(1) & 0xFF) | |
if key == 'q': | |
vs.cleanup() | |
print('Exiting (via q) demo_superglue.py') | |
break | |
elif key == 'n': # set the current frame as anchor | |
last_data = {k+'0': pred[k+'1'] for k in keys} | |
last_data['image0'] = frame_tensor | |
last_frame = frame | |
last_image_id = (vs.i - 1) | |
elif key in ['e', 'r']: | |
# Increase/decrease keypoint threshold by 10% each keypress. | |
d = 0.1 * (-1 if key == 'e' else 1) | |
matching.superpoint.config['keypoint_threshold'] = min(max( | |
0.0001, matching.superpoint.config['keypoint_threshold']*(1+d)), 1) | |
print('\nChanged the keypoint threshold to {:.4f}'.format( | |
matching.superpoint.config['keypoint_threshold'])) | |
elif key in ['d', 'f']: | |
# Increase/decrease match threshold by 0.05 each keypress. | |
d = 0.05 * (-1 if key == 'd' else 1) | |
matching.superglue.config['match_threshold'] = min(max( | |
0.05, matching.superglue.config['match_threshold']+d), .95) | |
print('\nChanged the match threshold to {:.2f}'.format( | |
matching.superglue.config['match_threshold'])) | |
elif key == 'k': | |
opt.show_keypoints = not opt.show_keypoints | |
timer.update('viz') | |
timer.print() | |
if opt.output_dir is not None: | |
#stem = 'matches_{:06}_{:06}'.format(last_image_id, vs.i-1) | |
stem = 'matches_{:06}_{:06}'.format(stem0, stem1) | |
out_file = str(Path(opt.output_dir, stem + '.png')) | |
print('\nWriting image to {}'.format(out_file)) | |
cv2.imwrite(out_file, out) | |
cv2.destroyAllWindows() | |
vs.cleanup() | |