|
import numpy as np |
|
import torch |
|
import torchvision.transforms.functional as F |
|
from types import SimpleNamespace |
|
from .extract_features import read_image, resize_image |
|
import cv2 |
|
|
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
|
confs = { |
|
|
|
"loftr": { |
|
"output": "matches-loftr", |
|
"model": { |
|
"name": "loftr", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": True, |
|
"resize_max": 1024, |
|
"dfactor": 8, |
|
"width": 640, |
|
"height": 480, |
|
"force_resize": True, |
|
}, |
|
"max_error": 1, |
|
"cell_size": 1, |
|
}, |
|
|
|
"loftr_aachen": { |
|
"output": "matches-loftr_aachen", |
|
"model": { |
|
"name": "loftr", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": {"grayscale": True, "resize_max": 1024, "dfactor": 8}, |
|
"max_error": 2, |
|
"cell_size": 8, |
|
}, |
|
|
|
"loftr_superpoint": { |
|
"output": "matches-loftr_aachen", |
|
"model": { |
|
"name": "loftr", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": {"grayscale": True, "resize_max": 1024, "dfactor": 8}, |
|
"max_error": 4, |
|
"cell_size": 4, |
|
}, |
|
|
|
"topicfm": { |
|
"output": "matches-topicfm", |
|
"model": { |
|
"name": "topicfm", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": True, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"dfactor": 8, |
|
"width": 640, |
|
"height": 480, |
|
}, |
|
}, |
|
|
|
"aspanformer": { |
|
"output": "matches-aspanformer", |
|
"model": { |
|
"name": "aspanformer", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": True, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 640, |
|
"height": 480, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
"dkm": { |
|
"output": "matches-dkm", |
|
"model": { |
|
"name": "dkm", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": False, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 80, |
|
"height": 60, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
"roma": { |
|
"output": "matches-roma", |
|
"model": { |
|
"name": "roma", |
|
"weights": "outdoor", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": False, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 320, |
|
"height": 240, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
"dedode_sparse": { |
|
"output": "matches-dedode", |
|
"model": { |
|
"name": "dedode", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
"dense": False, |
|
}, |
|
"preprocessing": { |
|
"grayscale": False, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 768, |
|
"height": 768, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
"sold2": { |
|
"output": "matches-sold2", |
|
"model": { |
|
"name": "sold2", |
|
"max_keypoints": 2000, |
|
"match_threshold": 0.2, |
|
}, |
|
"preprocessing": { |
|
"grayscale": True, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 640, |
|
"height": 480, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
"gluestick": { |
|
"output": "matches-gluestick", |
|
"model": { |
|
"name": "gluestick", |
|
"use_lines": True, |
|
"max_keypoints": 1000, |
|
"max_lines": 300, |
|
"force_num_keypoints": False, |
|
}, |
|
"preprocessing": { |
|
"grayscale": True, |
|
"force_resize": True, |
|
"resize_max": 1024, |
|
"width": 640, |
|
"height": 480, |
|
"dfactor": 8, |
|
}, |
|
}, |
|
} |
|
|
|
|
|
def scale_keypoints(kpts, scale): |
|
if np.any(scale != 1.0): |
|
kpts *= kpts.new_tensor(scale) |
|
return kpts |
|
|
|
|
|
def scale_lines(lines, scale): |
|
if np.any(scale != 1.0): |
|
lines *= lines.new_tensor(scale) |
|
return lines |
|
|
|
|
|
def match(model, path_0, path_1, conf): |
|
default_conf = { |
|
"grayscale": True, |
|
"resize_max": 1024, |
|
"dfactor": 8, |
|
"cache_images": False, |
|
"force_resize": False, |
|
"width": 320, |
|
"height": 240, |
|
} |
|
|
|
def preprocess(image: np.ndarray): |
|
image = image.astype(np.float32, copy=False) |
|
size = image.shape[:2][::-1] |
|
scale = np.array([1.0, 1.0]) |
|
if conf.resize_max: |
|
scale = conf.resize_max / max(size) |
|
if scale < 1.0: |
|
size_new = tuple(int(round(x * scale)) for x in size) |
|
image = resize_image(image, size_new, "cv2_area") |
|
scale = np.array(size) / np.array(size_new) |
|
if conf.force_resize: |
|
size = image.shape[:2][::-1] |
|
image = resize_image(image, (conf.width, conf.height), "cv2_area") |
|
size_new = (conf.width, conf.height) |
|
scale = np.array(size) / np.array(size_new) |
|
if conf.grayscale: |
|
assert image.ndim == 2, image.shape |
|
image = image[None] |
|
else: |
|
image = image.transpose((2, 0, 1)) |
|
image = torch.from_numpy(image / 255.0).float() |
|
|
|
size_new = tuple( |
|
map( |
|
lambda x: int(x // conf.dfactor * conf.dfactor), |
|
image.shape[-2:], |
|
) |
|
) |
|
image = F.resize(image, size=size_new, antialias=True) |
|
scale = np.array(size) / np.array(size_new)[::-1] |
|
return image, scale |
|
|
|
conf = SimpleNamespace(**{**default_conf, **conf}) |
|
image0 = read_image(path_0, conf.grayscale) |
|
image1 = read_image(path_1, conf.grayscale) |
|
image0, scale0 = preprocess(image0) |
|
image1, scale1 = preprocess(image1) |
|
image0 = image0.to(device)[None] |
|
image1 = image1.to(device)[None] |
|
pred = model({"image0": image0, "image1": image1}) |
|
|
|
|
|
kpts0, kpts1 = pred["keypoints0"], pred["keypoints1"] |
|
kpts0 = scale_keypoints(kpts0 + 0.5, scale0) - 0.5 |
|
kpts1 = scale_keypoints(kpts1 + 0.5, scale1) - 0.5 |
|
|
|
ret = { |
|
"image0": image0.squeeze().cpu().numpy(), |
|
"image1": image1.squeeze().cpu().numpy(), |
|
"keypoints0": kpts0.cpu().numpy(), |
|
"keypoints1": kpts1.cpu().numpy(), |
|
} |
|
if "mconf" in pred.keys(): |
|
ret["mconf"] = pred["mconf"].cpu().numpy() |
|
return ret |
|
|
|
|
|
@torch.no_grad() |
|
def match_images(model, image_0, image_1, conf, device="cpu"): |
|
default_conf = { |
|
"grayscale": True, |
|
"resize_max": 1024, |
|
"dfactor": 8, |
|
"cache_images": False, |
|
"force_resize": False, |
|
"width": 320, |
|
"height": 240, |
|
} |
|
|
|
def preprocess(image: np.ndarray): |
|
image = image.astype(np.float32, copy=False) |
|
size = image.shape[:2][::-1] |
|
scale = np.array([1.0, 1.0]) |
|
if conf.resize_max: |
|
scale = conf.resize_max / max(size) |
|
if scale < 1.0: |
|
size_new = tuple(int(round(x * scale)) for x in size) |
|
image = resize_image(image, size_new, "cv2_area") |
|
scale = np.array(size) / np.array(size_new) |
|
if conf.force_resize: |
|
size = image.shape[:2][::-1] |
|
image = resize_image(image, (conf.width, conf.height), "cv2_area") |
|
size_new = (conf.width, conf.height) |
|
scale = np.array(size) / np.array(size_new) |
|
if conf.grayscale: |
|
assert image.ndim == 2, image.shape |
|
image = image[None] |
|
else: |
|
image = image.transpose((2, 0, 1)) |
|
image = torch.from_numpy(image / 255.0).float() |
|
|
|
|
|
size_new = tuple( |
|
map( |
|
lambda x: int(x // conf.dfactor * conf.dfactor), |
|
image.shape[-2:], |
|
) |
|
) |
|
image = F.resize(image, size=size_new) |
|
scale = np.array(size) / np.array(size_new)[::-1] |
|
return image, scale |
|
|
|
conf = SimpleNamespace(**{**default_conf, **conf}) |
|
|
|
if len(image_0.shape) == 3 and conf.grayscale: |
|
image0 = cv2.cvtColor(image_0, cv2.COLOR_RGB2GRAY) |
|
else: |
|
image0 = image_0 |
|
if len(image_0.shape) == 3 and conf.grayscale: |
|
image1 = cv2.cvtColor(image_1, cv2.COLOR_RGB2GRAY) |
|
else: |
|
image1 = image_1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
image0, scale0 = preprocess(image0) |
|
image1, scale1 = preprocess(image1) |
|
image0 = image0.to(device)[None] |
|
image1 = image1.to(device)[None] |
|
pred = model({"image0": image0, "image1": image1}) |
|
|
|
s0 = np.array(image_0.shape[:2][::-1]) / np.array(image0.shape[-2:][::-1]) |
|
s1 = np.array(image_1.shape[:2][::-1]) / np.array(image1.shape[-2:][::-1]) |
|
|
|
|
|
if "keypoints0" in pred.keys() and "keypoints1" in pred.keys(): |
|
kpts0, kpts1 = pred["keypoints0"], pred["keypoints1"] |
|
kpts0_origin = scale_keypoints(kpts0 + 0.5, s0) - 0.5 |
|
kpts1_origin = scale_keypoints(kpts1 + 0.5, s1) - 0.5 |
|
|
|
ret = { |
|
"image0": image0.squeeze().cpu().numpy(), |
|
"image1": image1.squeeze().cpu().numpy(), |
|
"image0_orig": image_0, |
|
"image1_orig": image_1, |
|
"keypoints0": kpts0_origin.cpu().numpy(), |
|
"keypoints1": kpts1_origin.cpu().numpy(), |
|
"keypoints0_orig": kpts0_origin.cpu().numpy(), |
|
"keypoints1_orig": kpts1_origin.cpu().numpy(), |
|
"original_size0": np.array(image_0.shape[:2][::-1]), |
|
"original_size1": np.array(image_1.shape[:2][::-1]), |
|
"new_size0": np.array(image0.shape[-2:][::-1]), |
|
"new_size1": np.array(image1.shape[-2:][::-1]), |
|
"scale0": s0, |
|
"scale1": s1, |
|
} |
|
if "mconf" in pred.keys(): |
|
ret["mconf"] = pred["mconf"].cpu().numpy() |
|
else: |
|
ret["mconf"] = np.ones_like(kpts0.cpu().numpy()[:, 0]) |
|
if "lines0" in pred.keys() and "lines1" in pred.keys(): |
|
if "keypoints0" in pred.keys() and "keypoints1" in pred.keys(): |
|
kpts0, kpts1 = pred["keypoints0"], pred["keypoints1"] |
|
kpts0_origin = scale_keypoints(kpts0 + 0.5, s0) - 0.5 |
|
kpts1_origin = scale_keypoints(kpts1 + 0.5, s1) - 0.5 |
|
kpts0_origin = kpts0_origin.cpu().numpy() |
|
kpts1_origin = kpts1_origin.cpu().numpy() |
|
else: |
|
kpts0_origin, kpts1_origin = ( |
|
None, |
|
None, |
|
) |
|
lines0, lines1 = pred["lines0"], pred["lines1"] |
|
lines0_raw, lines1_raw = pred["raw_lines0"], pred["raw_lines1"] |
|
|
|
lines0_raw = torch.from_numpy(lines0_raw.copy()) |
|
lines1_raw = torch.from_numpy(lines1_raw.copy()) |
|
lines0_raw = scale_lines(lines0_raw + 0.5, s0) - 0.5 |
|
lines1_raw = scale_lines(lines1_raw + 0.5, s1) - 0.5 |
|
|
|
lines0 = torch.from_numpy(lines0.copy()) |
|
lines1 = torch.from_numpy(lines1.copy()) |
|
lines0 = scale_lines(lines0 + 0.5, s0) - 0.5 |
|
lines1 = scale_lines(lines1 + 0.5, s1) - 0.5 |
|
|
|
ret = { |
|
"image0_orig": image_0, |
|
"image1_orig": image_1, |
|
"line0": lines0_raw.cpu().numpy(), |
|
"line1": lines1_raw.cpu().numpy(), |
|
"line0_orig": lines0.cpu().numpy(), |
|
"line1_orig": lines1.cpu().numpy(), |
|
"line_keypoints0_orig": kpts0_origin, |
|
"line_keypoints1_orig": kpts1_origin, |
|
} |
|
del pred |
|
torch.cuda.empty_cache() |
|
return ret |
|
|