Skip to content

Commit 6aa83d6

Browse files
authored
Add LoMa (#63)
* add loma * remove tyro dep * Add LoMa-R and fix pixel offset
1 parent 18fc570 commit 6aa83d6

5 files changed

Lines changed: 102 additions & 1 deletion

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,6 @@
9494
path = vismatch/third_party/ZippyPoint
9595
url = https://github.com/menelaoskanakis/ZippyPoint
9696
ignore = untracked
97+
[submodule "vismatch/third_party/LoMa"]
98+
path = vismatch/third_party/LoMa
99+
url = https://github.com/davnords/LoMa

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ We support the following methods:
177177

178178
**Semi-dense**: ```loftr, eloftr, se2loftr, xoftr, minima-loftr, aspanformer, matchformer, xfeat-star, xfeat-star-steerers[-perm/-learned], edm, rdd-star, topicfm[-plus]```
179179

180-
**Sparse**: ```[sift, superpoint, disk, aliked, dedode, doghardnet, gim, xfeat]-lightglue, dedode, steerers, affine-steerers, xfeat-steerers[-perm/learned], dedode-kornia, [sift, orb, doghardnet]-nn, patch2pix, superglue, r2d2, d2net, gim-dkm, xfeat, omniglue, [dedode, xfeat, aliked]-subpx, [sift, superpoint]-sphereglue, minima-superpoint-lightglue, liftfeat, rdd-[sparse,lightglue, aliked], ripe, lisrd, zippypoint```
180+
**Sparse**: ```[sift, superpoint, disk, aliked, dedode, doghardnet, gim, xfeat]-lightglue, dedode, steerers, affine-steerers, xfeat-steerers[-perm/learned], dedode-kornia, [sift, orb, doghardnet]-nn, patch2pix, superglue, r2d2, d2net, gim-dkm, xfeat, omniglue, [dedode, xfeat, aliked]-subpx, [sift, superpoint]-sphereglue, minima-superpoint-lightglue, liftfeat, rdd-[sparse,lightglue, aliked], ripe, lisrd, zippypoint, loma```
181181

182182
See [Model Details](docs/source/model_details.md) to see runtimes, supported devices, source, and license of each model.
183183

vismatch/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"xfeat-steerers-learned",
9696
"xfeat-star-steerers-perm",
9797
"xfeat-star-steerers-learned",
98+
"loma",
9899
]
99100

100101

@@ -478,6 +479,10 @@ def get_matcher(
478479
from vismatch.im_models import zippypoint
479480

480481
return zippypoint.ZippyPointMatcher(device, max_num_keypoints=max_num_keypoints, *args, **kwargs)
482+
elif matcher_name == "loma":
483+
from vismatch.im_models import loma
484+
485+
return loma.LoMaMatcher(device, max_num_keypoints, *args, **kwargs)
481486
else:
482487
raise RuntimeError(
483488
f"Matcher {matcher_name} not yet supported. Consider submitted a PR to add it. Available models: {available_models}"

vismatch/im_models/loma.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import torch
2+
3+
from typing import Literal
4+
5+
from vismatch import THIRD_PARTY_DIR, BaseMatcher # noqa: F401
6+
from vismatch.utils import add_to_path, resize_to_divisible
7+
8+
add_to_path(THIRD_PARTY_DIR.joinpath("LoMa/src"))
9+
10+
from loma.loma import (
11+
LoMaB,
12+
LoMaB128,
13+
LoMaL,
14+
LoMaG,
15+
LoMaR,
16+
LoMa,
17+
filter_matches,
18+
to_pixel_coords,
19+
)
20+
21+
22+
class LoMaMatcher(BaseMatcher):
23+
divisible_size = 14 # for DINOv2 in the descriptor of LoMa-{B, L, G, R}. LoMa-B128 can handle arbitrary resolutions and is more lightweight.
24+
25+
def __init__(
26+
self,
27+
device="cpu",
28+
max_num_keypoints=2048,
29+
arch: Literal["LoMa-B", "LoMa-L", "LoMa-G", "LoMa-B128", "LoMa-R"] = "LoMa-B",
30+
**kwargs,
31+
):
32+
super().__init__(device, **kwargs)
33+
self.max_num_keypoints = max_num_keypoints
34+
35+
if arch == "LoMa-B":
36+
cfg = LoMaB()
37+
elif arch == "LoMa-L":
38+
cfg = LoMaL()
39+
elif arch == "LoMa-G":
40+
cfg = LoMaG()
41+
elif arch == "LoMa-B128":
42+
cfg = LoMaB128()
43+
elif arch == "LoMa-R":
44+
cfg = LoMaR()
45+
else:
46+
raise ValueError(
47+
f"Unsupported architecture '{arch}' for LoMa. Supported: 'LoMa-B', 'LoMa-L', 'LoMa-G', 'LoMa-B128', 'LoMa-R'."
48+
)
49+
50+
# This automatically loads weights using torch.hub.load_state_dict_from_url
51+
self.matcher = LoMa(cfg)
52+
53+
def preprocess(self, img):
54+
_, h, w = img.shape
55+
orig_shape = h, w
56+
img = resize_to_divisible(img, self.divisible_size)
57+
img = img.unsqueeze(0)
58+
return img, orig_shape
59+
60+
def _forward(self, img0, img1):
61+
img0, img0_orig_shape = self.preprocess(img0)
62+
img1, img1_orig_shape = self.preprocess(img1)
63+
64+
H0, W0 = img0.shape[-2:]
65+
H1, W1 = img1.shape[-2:]
66+
67+
kpts0, desc0, _, _ = self.matcher.detect_and_describe(img0, self.max_num_keypoints)
68+
kpts1, desc1, _, _ = self.matcher.detect_and_describe(img1, self.max_num_keypoints)
69+
70+
scores = self.matcher(kpts0, kpts1, desc0, desc1)["scores"]
71+
m0, _, _, _ = filter_matches(scores, self.matcher.cfg.filter_threshold)
72+
73+
valid = m0[0] > -1
74+
matched_kpts0 = to_pixel_coords(kpts0[0][torch.where(valid)[0]], H0, W0)
75+
matched_kpts1 = to_pixel_coords(kpts1[0][m0[0][valid]], H1, W1)
76+
77+
all_kpts0 = to_pixel_coords(kpts0[0], H0, W0)
78+
all_kpts1 = to_pixel_coords(kpts1[0], H1, W1)
79+
80+
matched_kpts0 = self.rescale_coords(matched_kpts0, *img0_orig_shape, H0, W0)
81+
matched_kpts1 = self.rescale_coords(matched_kpts1, *img1_orig_shape, H1, W1)
82+
all_kpts0 = self.rescale_coords(all_kpts0, *img0_orig_shape, H0, W0)
83+
all_kpts1 = self.rescale_coords(all_kpts1, *img1_orig_shape, H1, W1)
84+
85+
# LoMa uses COLMAP convention for pixel coords (see https://github.com/gmberton/vismatch/pull/63) so we subtact 0.5 for repo compatability
86+
offset = 0.5
87+
matched_kpts0 -= offset
88+
matched_kpts1 -= offset
89+
all_kpts0 -= offset
90+
all_kpts1 -= offset
91+
92+
return matched_kpts0, matched_kpts1, all_kpts0, all_kpts1, desc0[0], desc1[0]

vismatch/third_party/LoMa

Submodule LoMa added at 9105854

0 commit comments

Comments
 (0)