|
|
import torch |
|
|
import numpy as np |
|
|
import cv2 |
|
|
from lightglue import LightGlue |
|
|
from lightglue.utils import rbd |
|
|
from lightglue import SuperPoint, SIFT |
|
|
from lightglue.utils import load_image |
|
|
|
|
|
|
|
|
def unrotate_kps_W(kps_rot, k, H, W): |
|
|
|
|
|
if hasattr(kps_rot, 'cpu'): kps_rot = kps_rot.cpu().numpy() |
|
|
if hasattr(k, 'cpu'): k = k.cpu().numpy() |
|
|
|
|
|
|
|
|
if k.ndim > 1: k = k.squeeze() |
|
|
if kps_rot.ndim > 2: kps_rot = kps_rot.squeeze() |
|
|
|
|
|
x_r = kps_rot[:, 0] |
|
|
y_r = kps_rot[:, 1] |
|
|
|
|
|
x = np.zeros_like(x_r) |
|
|
y = np.zeros_like(y_r) |
|
|
|
|
|
mask0 = (k == 0) |
|
|
x[mask0], y[mask0] = x_r[mask0], y_r[mask0] |
|
|
|
|
|
mask1 = (k == 1) |
|
|
x[mask1], y[mask1] = (W - 1) - y_r[mask1], x_r[mask1] |
|
|
|
|
|
mask2 = (k == 2) |
|
|
x[mask2], y[mask2] = (W - 1) - x_r[mask2], (H - 1) - y_r[mask2] |
|
|
|
|
|
mask3 = (k == 3) |
|
|
x[mask3], y[mask3] = y_r[mask3], (H - 1) - x_r[mask3] |
|
|
|
|
|
return np.stack([x, y], axis=-1) |
|
|
|
|
|
def extract_keypoints(path_to_image0, features='superpoint', rotations = [0,1,2,3]): |
|
|
|
|
|
device = 'cuda' if torch.cuda.is_available() else 'cpu' |
|
|
|
|
|
|
|
|
timg = load_image(path_to_image0).to(device) |
|
|
_, h, w = timg.shape |
|
|
|
|
|
if features == 'sift': |
|
|
extractor = SIFT(max_num_keypoints=2048).eval().to(device) |
|
|
feats = extractor.extract(timg) |
|
|
return feats , h, w |
|
|
|
|
|
if features == 'superpoint': |
|
|
extractor = SuperPoint(max_num_keypoints=2048).eval().to(device) |
|
|
|
|
|
|
|
|
feats = {} |
|
|
for k in (rotations): |
|
|
timg_rotated = torch.rot90(timg, k, dims=(1, 2)) |
|
|
feats[k] = extractor.extract(timg_rotated) |
|
|
|
|
|
|
|
|
|
|
|
all_keypoints = [] |
|
|
all_scores = [] |
|
|
all_descriptors = [] |
|
|
all_rotations = [] |
|
|
for k, feat in feats.items(): |
|
|
kpts = feat['keypoints'] |
|
|
num_kpts = kpts.shape[1] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rot_indices = torch.full((1, num_kpts), k, dtype=torch.long, device=device) |
|
|
all_keypoints.append(feat['keypoints']) |
|
|
all_scores.append(feat['keypoint_scores']) |
|
|
all_descriptors.append(feat['descriptors']) |
|
|
all_rotations.append(rot_indices) |
|
|
|
|
|
|
|
|
feats_merged = { |
|
|
'keypoints': torch.cat(all_keypoints, dim=1), |
|
|
'keypoint_scores': torch.cat(all_scores, dim=1), |
|
|
'descriptors': torch.cat(all_descriptors, dim=1), |
|
|
'rotations': torch.cat(all_rotations, dim=1) |
|
|
} |
|
|
|
|
|
num_kpts = feats_merged['keypoints'].shape[1] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return feats_merged , feats, h, w |
|
|
|
|
|
def lightglue_matching(feats0, feats1, matcher = None): |
|
|
if matcher is None: |
|
|
device = 'cuda' if torch.cuda.is_available() else 'cpu' |
|
|
matcher = LightGlue(features='superpoint').eval().to(device) |
|
|
|
|
|
out_k = matcher({'image0': feats0, 'image1': feats1}) |
|
|
_, _, out_k = [rbd(x) for x in [feats0, feats1, out_k]] |
|
|
return out_k['matches'] |
|
|
|
|
|
def feature_matching(feats0, feats1, matcher = None, exhaustive = True): |
|
|
best_rot = 0 |
|
|
best_num_matches = 0 |
|
|
matches_tensor = None |
|
|
|
|
|
|
|
|
for rot in [0,1,2,3]: |
|
|
matches_tensor_rot = lightglue_matching(feats0[0], feats1[rot], matcher = matcher) |
|
|
if (len(matches_tensor_rot) > best_num_matches): |
|
|
best_num_matches = len(matches_tensor_rot) |
|
|
best_rot = rot |
|
|
matches_tensor = matches_tensor_rot |
|
|
|
|
|
if matches_tensor is not None and len(matches_tensor) > 0: |
|
|
matches_np = matches_tensor.cpu().numpy().astype(np.uint32) |
|
|
else: |
|
|
return None |
|
|
|
|
|
|
|
|
for k in range(best_rot): |
|
|
matches_np[:,1] += feats1[k]['keypoints'].shape[1] |
|
|
all_matches = [matches_np] |
|
|
|
|
|
if not exhaustive: |
|
|
return matches_np |
|
|
|
|
|
|
|
|
rots = [] |
|
|
for rot in [1, 2, 3]: |
|
|
rot_i = best_rot + rot |
|
|
if rot_i >=4: |
|
|
rot_i = rot_i -4 |
|
|
rots.append(rot_i) |
|
|
|
|
|
|
|
|
for rot_i in [1,2,3]: |
|
|
rot_j = rots[rot_i-1] |
|
|
|
|
|
matches_tensor_rot = lightglue_matching(feats0[rot_i], feats1[rot_j], matcher = matcher) |
|
|
matches_np_i = matches_tensor_rot.cpu().numpy().astype(np.uint32) |
|
|
if rot_i > 0: |
|
|
for k in range(rot_i): |
|
|
matches_np_i[:,0] += feats0[k]['keypoints'].shape[1] |
|
|
if rot_j > 0: |
|
|
for k in range(rot_j): |
|
|
matches_np_i[:,1] += feats1[k]['keypoints'].shape[1] |
|
|
|
|
|
all_matches.append(matches_np_i) |
|
|
print(f"Rotation {rot_i} vs {rot_j}: {len(matches_tensor_rot)} matches") |
|
|
|
|
|
|
|
|
matches_stacked = ( |
|
|
np.vstack(all_matches) if len(all_matches) and all_matches[0].size else |
|
|
np.empty((0, 2), dtype=np.uint32) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return matches_stacked |
|
|
|
|
|
|
|
|
|