Spaces:
Runtime error
Runtime error
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
|