Spaces:
No application file
No application file
Realcat
commited on
Commit
•
3c77caa
1
Parent(s):
b5957bd
add: dkm & ense matcher max_keypoints
Browse files- common/app_class.py +7 -7
- common/config.yaml +11 -1
- common/utils.py +10 -10
- hloc/match_dense.py +1 -1
- hloc/matchers/aspanformer.py +10 -1
- hloc/matchers/dkm.py +12 -4
- hloc/matchers/loftr.py +4 -1
- hloc/matchers/topicfm.py +11 -0
common/app_class.py
CHANGED
@@ -162,7 +162,7 @@ class ImageMatchingApp:
|
|
162 |
|
163 |
with gr.Accordion("Geometry Setting", open=False):
|
164 |
with gr.Row(equal_height=False):
|
165 |
-
|
166 |
["Fundamental", "Homography"],
|
167 |
label="Reconstruct Geometry",
|
168 |
value=self.cfg["defaults"][
|
@@ -182,7 +182,7 @@ class ImageMatchingApp:
|
|
182 |
ransac_reproj_threshold,
|
183 |
ransac_confidence,
|
184 |
ransac_max_iter,
|
185 |
-
|
186 |
gr.State(self.matcher_zoo),
|
187 |
]
|
188 |
|
@@ -282,20 +282,20 @@ class ImageMatchingApp:
|
|
282 |
ransac_reproj_threshold,
|
283 |
ransac_confidence,
|
284 |
ransac_max_iter,
|
285 |
-
|
286 |
]
|
287 |
button_reset.click(
|
288 |
fn=self.ui_reset_state, inputs=None, outputs=reset_outputs
|
289 |
)
|
290 |
|
291 |
# estimate geo
|
292 |
-
|
293 |
fn=change_estimate_geom,
|
294 |
inputs=[
|
295 |
input_image0,
|
296 |
input_image1,
|
297 |
geometry_result,
|
298 |
-
|
299 |
],
|
300 |
outputs=[output_wrapped, geometry_result],
|
301 |
)
|
@@ -441,12 +441,12 @@ class ImageMatchingApp:
|
|
441 |
v["info"]["name"],
|
442 |
v["info"]["source"],
|
443 |
v["info"]["github"],
|
444 |
-
v["info"]["project"],
|
445 |
v["info"]["paper"],
|
|
|
446 |
]
|
447 |
)
|
448 |
tab = gr.Dataframe(
|
449 |
-
headers=["Algo.", "Conference", "Code", "
|
450 |
datatype=["str", "str", "str", "str", "str"],
|
451 |
col_count=(5, "fixed"),
|
452 |
value=data,
|
|
|
162 |
|
163 |
with gr.Accordion("Geometry Setting", open=False):
|
164 |
with gr.Row(equal_height=False):
|
165 |
+
choice_geometry_type = gr.Radio(
|
166 |
["Fundamental", "Homography"],
|
167 |
label="Reconstruct Geometry",
|
168 |
value=self.cfg["defaults"][
|
|
|
182 |
ransac_reproj_threshold,
|
183 |
ransac_confidence,
|
184 |
ransac_max_iter,
|
185 |
+
choice_geometry_type,
|
186 |
gr.State(self.matcher_zoo),
|
187 |
]
|
188 |
|
|
|
282 |
ransac_reproj_threshold,
|
283 |
ransac_confidence,
|
284 |
ransac_max_iter,
|
285 |
+
choice_geometry_type,
|
286 |
]
|
287 |
button_reset.click(
|
288 |
fn=self.ui_reset_state, inputs=None, outputs=reset_outputs
|
289 |
)
|
290 |
|
291 |
# estimate geo
|
292 |
+
choice_geometry_type.change(
|
293 |
fn=change_estimate_geom,
|
294 |
inputs=[
|
295 |
input_image0,
|
296 |
input_image1,
|
297 |
geometry_result,
|
298 |
+
choice_geometry_type,
|
299 |
],
|
300 |
outputs=[output_wrapped, geometry_result],
|
301 |
)
|
|
|
441 |
v["info"]["name"],
|
442 |
v["info"]["source"],
|
443 |
v["info"]["github"],
|
|
|
444 |
v["info"]["paper"],
|
445 |
+
v["info"]["project"],
|
446 |
]
|
447 |
)
|
448 |
tab = gr.Dataframe(
|
449 |
+
headers=["Algo.", "Conference", "Code", "Paper", "Project"],
|
450 |
datatype=["str", "str", "str", "str", "str"],
|
451 |
col_count=(5, "fixed"),
|
452 |
value=data,
|
common/config.yaml
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
server:
|
2 |
name: "0.0.0.0"
|
3 |
-
port:
|
4 |
|
5 |
defaults:
|
6 |
setting_threshold: 0.1
|
@@ -26,6 +26,16 @@ matcher_zoo:
|
|
26 |
paper: https://arxiv.org/abs/2305.15404
|
27 |
project: https://parskatt.github.io/RoMa
|
28 |
display: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
loftr:
|
30 |
matcher: loftr
|
31 |
dense: true
|
|
|
1 |
server:
|
2 |
name: "0.0.0.0"
|
3 |
+
port: 7861
|
4 |
|
5 |
defaults:
|
6 |
setting_threshold: 0.1
|
|
|
26 |
paper: https://arxiv.org/abs/2305.15404
|
27 |
project: https://parskatt.github.io/RoMa
|
28 |
display: true
|
29 |
+
dkm:
|
30 |
+
matcher: dkm
|
31 |
+
dense: true
|
32 |
+
info:
|
33 |
+
name: DKM #dispaly name
|
34 |
+
source: "CVPR 2023"
|
35 |
+
github: https://github.com/Parskatt/DKM
|
36 |
+
paper: https://arxiv.org/abs/2202.00667
|
37 |
+
project: https://parskatt.github.io/DKM
|
38 |
+
display: true
|
39 |
loftr:
|
40 |
matcher: loftr
|
41 |
dense: true
|
common/utils.py
CHANGED
@@ -21,6 +21,7 @@ from .viz import (
|
|
21 |
import time
|
22 |
import matplotlib.pyplot as plt
|
23 |
import warnings
|
|
|
24 |
warnings.simplefilter("ignore")
|
25 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
26 |
|
@@ -41,6 +42,7 @@ GRADIO_VERSION = gr.__version__.split(".")[0]
|
|
41 |
MATCHER_ZOO = None
|
42 |
models_already_loaded = {}
|
43 |
|
|
|
44 |
def load_config(config_name: str) -> Dict[str, Any]:
|
45 |
"""
|
46 |
Load a YAML configuration file.
|
@@ -417,7 +419,7 @@ def run_matching(
|
|
417 |
ransac_reproj_threshold: int = DEFAULT_RANSAC_REPROJ_THRESHOLD,
|
418 |
ransac_confidence: float = DEFAULT_RANSAC_CONFIDENCE,
|
419 |
ransac_max_iter: int = DEFAULT_RANSAC_MAX_ITER,
|
420 |
-
|
421 |
matcher_zoo: Dict[str, Any] = None,
|
422 |
) -> Tuple[
|
423 |
np.ndarray,
|
@@ -441,7 +443,7 @@ def run_matching(
|
|
441 |
ransac_reproj_threshold (int, optional): RANSAC reprojection threshold.
|
442 |
ransac_confidence (float, optional): RANSAC confidence level.
|
443 |
ransac_max_iter (int, optional): RANSAC maximum number of iterations.
|
444 |
-
|
445 |
|
446 |
Returns:
|
447 |
tuple:
|
@@ -476,8 +478,8 @@ def run_matching(
|
|
476 |
cache_key = match_conf["model"]["name"]
|
477 |
if cache_key in models_already_loaded:
|
478 |
matcher = models_already_loaded[cache_key]
|
479 |
-
matcher.conf[
|
480 |
-
matcher.conf[
|
481 |
logger.info(f"Loaded cached model {cache_key}")
|
482 |
else:
|
483 |
matcher = get_model(match_conf)
|
@@ -485,7 +487,7 @@ def run_matching(
|
|
485 |
gr.Info(f"Loading model using: {time.time()-t0:.3f}s")
|
486 |
logger.info(f"Loading model using: {time.time()-t0:.3f}s")
|
487 |
t1 = time.time()
|
488 |
-
|
489 |
if model["dense"]:
|
490 |
pred = match_dense.match_images(
|
491 |
matcher, image0, image1, match_conf["preprocessing"], device=device
|
@@ -500,8 +502,8 @@ def run_matching(
|
|
500 |
cache_key = extract_conf["model"]["name"]
|
501 |
if cache_key in models_already_loaded:
|
502 |
extractor = models_already_loaded[cache_key]
|
503 |
-
extractor.conf[
|
504 |
-
extractor.conf[
|
505 |
logger.info(f"Loaded cached model {cache_key}")
|
506 |
else:
|
507 |
extractor = get_feature_model(extract_conf)
|
@@ -570,10 +572,8 @@ def run_matching(
|
|
570 |
pred["image0_orig"],
|
571 |
pred["image1_orig"],
|
572 |
{"geom_info": geom_info},
|
573 |
-
|
574 |
)
|
575 |
-
gr.Info(f"Compute geometry done using: {time.time()-t1:.3f}s")
|
576 |
-
logger.info(f"Compute geometry done using: {time.time()-t1:.3f}s")
|
577 |
plt.close("all")
|
578 |
del pred
|
579 |
logger.info(f"TOTAL time: {time.time()-t0:.3f}s")
|
|
|
21 |
import time
|
22 |
import matplotlib.pyplot as plt
|
23 |
import warnings
|
24 |
+
|
25 |
warnings.simplefilter("ignore")
|
26 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
27 |
|
|
|
42 |
MATCHER_ZOO = None
|
43 |
models_already_loaded = {}
|
44 |
|
45 |
+
|
46 |
def load_config(config_name: str) -> Dict[str, Any]:
|
47 |
"""
|
48 |
Load a YAML configuration file.
|
|
|
419 |
ransac_reproj_threshold: int = DEFAULT_RANSAC_REPROJ_THRESHOLD,
|
420 |
ransac_confidence: float = DEFAULT_RANSAC_CONFIDENCE,
|
421 |
ransac_max_iter: int = DEFAULT_RANSAC_MAX_ITER,
|
422 |
+
choice_geometry_type: str = DEFAULT_SETTING_GEOMETRY,
|
423 |
matcher_zoo: Dict[str, Any] = None,
|
424 |
) -> Tuple[
|
425 |
np.ndarray,
|
|
|
443 |
ransac_reproj_threshold (int, optional): RANSAC reprojection threshold.
|
444 |
ransac_confidence (float, optional): RANSAC confidence level.
|
445 |
ransac_max_iter (int, optional): RANSAC maximum number of iterations.
|
446 |
+
choice_geometry_type (str, optional): setting of geometry estimation.
|
447 |
|
448 |
Returns:
|
449 |
tuple:
|
|
|
478 |
cache_key = match_conf["model"]["name"]
|
479 |
if cache_key in models_already_loaded:
|
480 |
matcher = models_already_loaded[cache_key]
|
481 |
+
matcher.conf["max_keypoints"] = extract_max_keypoints
|
482 |
+
matcher.conf["match_threshold"] = match_threshold
|
483 |
logger.info(f"Loaded cached model {cache_key}")
|
484 |
else:
|
485 |
matcher = get_model(match_conf)
|
|
|
487 |
gr.Info(f"Loading model using: {time.time()-t0:.3f}s")
|
488 |
logger.info(f"Loading model using: {time.time()-t0:.3f}s")
|
489 |
t1 = time.time()
|
490 |
+
|
491 |
if model["dense"]:
|
492 |
pred = match_dense.match_images(
|
493 |
matcher, image0, image1, match_conf["preprocessing"], device=device
|
|
|
502 |
cache_key = extract_conf["model"]["name"]
|
503 |
if cache_key in models_already_loaded:
|
504 |
extractor = models_already_loaded[cache_key]
|
505 |
+
extractor.conf["max_keypoints"] = extract_max_keypoints
|
506 |
+
extractor.conf["keypoint_threshold"] = keypoint_threshold
|
507 |
logger.info(f"Loaded cached model {cache_key}")
|
508 |
else:
|
509 |
extractor = get_feature_model(extract_conf)
|
|
|
572 |
pred["image0_orig"],
|
573 |
pred["image1_orig"],
|
574 |
{"geom_info": geom_info},
|
575 |
+
choice_geometry_type,
|
576 |
)
|
|
|
|
|
577 |
plt.close("all")
|
578 |
del pred
|
579 |
logger.info(f"TOTAL time: {time.time()-t0:.3f}s")
|
hloc/match_dense.py
CHANGED
@@ -368,7 +368,7 @@ def match_images(model, image_0, image_1, conf, device="cpu"):
|
|
368 |
}
|
369 |
if "mconf" in pred.keys():
|
370 |
ret["mconf"] = pred["mconf"].cpu().numpy()
|
371 |
-
elif "scores" in pred.keys():
|
372 |
ret["mconf"] = pred["scores"].cpu().numpy()
|
373 |
else:
|
374 |
ret["mconf"] = np.ones_like(kpts0.cpu().numpy()[:, 0])
|
|
|
368 |
}
|
369 |
if "mconf" in pred.keys():
|
370 |
ret["mconf"] = pred["mconf"].cpu().numpy()
|
371 |
+
elif "scores" in pred.keys(): # adapting loftr
|
372 |
ret["mconf"] = pred["scores"].cpu().numpy()
|
373 |
else:
|
374 |
ret["mconf"] = np.ones_like(kpts0.cpu().numpy()[:, 0])
|
hloc/matchers/aspanformer.py
CHANGED
@@ -69,7 +69,6 @@ class ASpanFormer(BaseModel):
|
|
69 |
|
70 |
do_system(f"cd {str(aspanformer_path)} & tar -xvf {str(tar_path)}")
|
71 |
|
72 |
-
|
73 |
config = get_cfg_defaults()
|
74 |
config.merge_from_file(conf["config_path"])
|
75 |
_config = lower_config(config)
|
@@ -99,4 +98,14 @@ class ASpanFormer(BaseModel):
|
|
99 |
"keypoints1": data_["mkpts1_f"],
|
100 |
"mconf": data_["mconf"],
|
101 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
return pred
|
|
|
69 |
|
70 |
do_system(f"cd {str(aspanformer_path)} & tar -xvf {str(tar_path)}")
|
71 |
|
|
|
72 |
config = get_cfg_defaults()
|
73 |
config.merge_from_file(conf["config_path"])
|
74 |
_config = lower_config(config)
|
|
|
98 |
"keypoints1": data_["mkpts1_f"],
|
99 |
"mconf": data_["mconf"],
|
100 |
}
|
101 |
+
scores = data_["mconf"]
|
102 |
+
top_k = self.conf["max_keypoints"]
|
103 |
+
if top_k is not None and len(scores) > top_k:
|
104 |
+
keep = torch.argsort(scores, descending=True)[:top_k]
|
105 |
+
scores = scores[keep]
|
106 |
+
pred["keypoints0"], pred["keypoints1"], pred["mconf"] = (
|
107 |
+
pred["keypoints0"][keep],
|
108 |
+
pred["keypoints1"][keep],
|
109 |
+
scores,
|
110 |
+
)
|
111 |
return pred
|
hloc/matchers/dkm.py
CHANGED
@@ -12,11 +12,13 @@ from DKM.dkm import DKMv3_outdoor
|
|
12 |
dkm_path = Path(__file__).parent / "../../third_party/DKM"
|
13 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
14 |
|
|
|
15 |
class DKMv3(BaseModel):
|
16 |
default_conf = {
|
17 |
"model_name": "DKMv3_outdoor.pth",
|
18 |
"match_threshold": 0.2,
|
19 |
"checkpoint_dir": dkm_path / "pretrained",
|
|
|
20 |
}
|
21 |
required_inputs = [
|
22 |
"image0",
|
@@ -38,8 +40,8 @@ class DKMv3(BaseModel):
|
|
38 |
cmd = ["wget", link, "-O", str(model_path)]
|
39 |
logger.info(f"Downloading the DKMv3 model with `{cmd}`.")
|
40 |
subprocess.run(cmd, check=True)
|
41 |
-
logger.info(f"Loading DKMv3 model...")
|
42 |
self.net = DKMv3_outdoor(path_to_weights=str(model_path), device=device)
|
|
|
43 |
|
44 |
def _forward(self, data):
|
45 |
img0 = data["image0"].cpu().numpy().squeeze() * 255
|
@@ -52,10 +54,16 @@ class DKMv3(BaseModel):
|
|
52 |
W_B, H_B = img1.size
|
53 |
|
54 |
warp, certainty = self.net.match(img0, img1, device=device)
|
55 |
-
matches, certainty = self.net.sample(
|
|
|
|
|
56 |
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
57 |
matches, H_A, W_A, H_B, W_B
|
58 |
)
|
59 |
-
pred = {
|
60 |
-
|
|
|
|
|
|
|
|
|
61 |
return pred
|
|
|
12 |
dkm_path = Path(__file__).parent / "../../third_party/DKM"
|
13 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
14 |
|
15 |
+
|
16 |
class DKMv3(BaseModel):
|
17 |
default_conf = {
|
18 |
"model_name": "DKMv3_outdoor.pth",
|
19 |
"match_threshold": 0.2,
|
20 |
"checkpoint_dir": dkm_path / "pretrained",
|
21 |
+
"max_keypoints": -1,
|
22 |
}
|
23 |
required_inputs = [
|
24 |
"image0",
|
|
|
40 |
cmd = ["wget", link, "-O", str(model_path)]
|
41 |
logger.info(f"Downloading the DKMv3 model with `{cmd}`.")
|
42 |
subprocess.run(cmd, check=True)
|
|
|
43 |
self.net = DKMv3_outdoor(path_to_weights=str(model_path), device=device)
|
44 |
+
logger.info(f"Loading DKMv3 model done")
|
45 |
|
46 |
def _forward(self, data):
|
47 |
img0 = data["image0"].cpu().numpy().squeeze() * 255
|
|
|
54 |
W_B, H_B = img1.size
|
55 |
|
56 |
warp, certainty = self.net.match(img0, img1, device=device)
|
57 |
+
matches, certainty = self.net.sample(
|
58 |
+
warp, certainty, num=self.conf["max_keypoints"]
|
59 |
+
)
|
60 |
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
61 |
matches, H_A, W_A, H_B, W_B
|
62 |
)
|
63 |
+
pred = {
|
64 |
+
"keypoints0": kpts1,
|
65 |
+
"keypoints1": kpts2,
|
66 |
+
"mconf": certainty,
|
67 |
+
}
|
68 |
+
breakpoint()
|
69 |
return pred
|
hloc/matchers/loftr.py
CHANGED
@@ -10,15 +10,18 @@ class LoFTR(BaseModel):
|
|
10 |
default_conf = {
|
11 |
"weights": "outdoor",
|
12 |
"match_threshold": 0.2,
|
13 |
-
"
|
|
|
14 |
}
|
15 |
required_inputs = ["image0", "image1"]
|
16 |
|
17 |
def _init(self, conf):
|
18 |
cfg = default_cfg
|
19 |
cfg["match_coarse"]["thr"] = conf["match_threshold"]
|
|
|
20 |
self.net = LoFTR_(pretrained=conf["weights"], config=cfg)
|
21 |
logger.info(f"Loaded LoFTR with weights {conf['weights']}")
|
|
|
22 |
def _forward(self, data):
|
23 |
# For consistency with hloc pairs, we refine kpts in image0!
|
24 |
rename = {
|
|
|
10 |
default_conf = {
|
11 |
"weights": "outdoor",
|
12 |
"match_threshold": 0.2,
|
13 |
+
"sinkhorn_iterations": 20,
|
14 |
+
"max_keypoints": -1,
|
15 |
}
|
16 |
required_inputs = ["image0", "image1"]
|
17 |
|
18 |
def _init(self, conf):
|
19 |
cfg = default_cfg
|
20 |
cfg["match_coarse"]["thr"] = conf["match_threshold"]
|
21 |
+
cfg["match_coarse"]["skh_iters"] = conf["sinkhorn_iterations"]
|
22 |
self.net = LoFTR_(pretrained=conf["weights"], config=cfg)
|
23 |
logger.info(f"Loaded LoFTR with weights {conf['weights']}")
|
24 |
+
|
25 |
def _forward(self, data):
|
26 |
# For consistency with hloc pairs, we refine kpts in image0!
|
27 |
rename = {
|
hloc/matchers/topicfm.py
CHANGED
@@ -16,6 +16,7 @@ class TopicFM(BaseModel):
|
|
16 |
"weights": "outdoor",
|
17 |
"match_threshold": 0.2,
|
18 |
"n_sampling_topics": 4,
|
|
|
19 |
}
|
20 |
required_inputs = ["image0", "image1"]
|
21 |
|
@@ -39,4 +40,14 @@ class TopicFM(BaseModel):
|
|
39 |
"keypoints1": data_["mkpts1_f"],
|
40 |
"mconf": data_["mconf"],
|
41 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
return pred
|
|
|
16 |
"weights": "outdoor",
|
17 |
"match_threshold": 0.2,
|
18 |
"n_sampling_topics": 4,
|
19 |
+
"max_keypoints": -1,
|
20 |
}
|
21 |
required_inputs = ["image0", "image1"]
|
22 |
|
|
|
40 |
"keypoints1": data_["mkpts1_f"],
|
41 |
"mconf": data_["mconf"],
|
42 |
}
|
43 |
+
scores = data_["mconf"]
|
44 |
+
top_k = self.conf["max_keypoints"]
|
45 |
+
if top_k is not None and len(scores) > top_k:
|
46 |
+
keep = torch.argsort(scores, descending=True)[:top_k]
|
47 |
+
scores = scores[keep]
|
48 |
+
pred["keypoints0"], pred["keypoints1"], pred["mconf"] = (
|
49 |
+
pred["keypoints0"][keep],
|
50 |
+
pred["keypoints1"][keep],
|
51 |
+
scores,
|
52 |
+
)
|
53 |
return pred
|