diff --git a/README.md b/README.md
index 7abaf50..ed01e66 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,25 @@
[![Issues][issues-shield]][issues-url]
-
$\color{red}{\textnormal{Image\ Matching\ WebUI}}$
-
Matching Keypoints between two images
+
Image Matching WebUI
+
Matching Keypoints between two images
+
## Description
This simple tool efficiently matches image pairs using multiple famous image matching algorithms. The tool features a Graphical User Interface (GUI) designed using [gradio](https://gradio.app/). You can effortlessly select two images and a matching algorithm and obtain a precise matching result.
**Note**: the images source can be either local images or webcam images.
-Try it on
-
-
-
+Try it on
+
+
Here is a demo of the tool:
diff --git a/config/config.yaml b/config/config.yaml
index 2e1449d..49ea178 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -1,6 +1,6 @@
server:
name: "0.0.0.0"
- port: 7861
+ port: 7860
defaults:
setting_threshold: 0.1
@@ -9,13 +9,40 @@ defaults:
enable_ransac: true
ransac_method: CV2_USAC_MAGSAC
ransac_reproj_threshold: 8
- ransac_confidence: 0.999
+ ransac_confidence: 0.9999
ransac_max_iter: 10000
ransac_num_samples: 4
match_threshold: 0.2
setting_geometry: Homography
matcher_zoo:
+ # example config
+ Example:
+ # show in `Matching Model` or not, default: true
+ enable: false
+ # matcher name
+ matcher: example
+ # skip ci or not, default: false
+ skip_ci: true
+ # dense matcher or not, default: true
+ dense: true
+ # info
+ info:
+ # dispaly name in `Matching Model`
+ name: example(example)
+ # conference/journal/workshop Year
+ source: "CVPR XXXX"
+ # github link
+ github: https://github.com/example/example
+ # paper link
+ paper: https://arxiv.org/abs/xxxx.xxxx
+ # project link
+ project: https://example.com
+ # show in `support algos` table
+ display: false
+ # low, medium, high
+ efficiency: low
+
minima(loftr):
matcher: minima_loftr
dense: true
@@ -23,7 +50,7 @@ matcher_zoo:
name: MINIMA(LoFTR) #dispaly name
source: "ARXIV 2024"
paper: https://arxiv.org/abs/2412.19412
- display: false
+ display: true
minima(RoMa):
matcher: minima_roma
skip_ci: true
@@ -33,6 +60,7 @@ matcher_zoo:
source: "ARXIV 2024"
paper: https://arxiv.org/abs/2412.19412
display: false
+ efficiency: low # low, medium, high
omniglue:
enable: true
matcher: omniglue
@@ -55,6 +83,7 @@ matcher_zoo:
paper: https://arxiv.org/abs/2406.09756
project: https://dust3r.europe.naverlabs.com
display: true
+ efficiency: low # low, medium, high
DUSt3R:
# TODO: duster is under development
enable: true
@@ -80,6 +109,7 @@ matcher_zoo:
paper: https://arxiv.org/abs/2402.11095
project: https://xuelunshen.com/gim
display: true
+ efficiency: low # low, medium, high
RoMa:
matcher: roma
skip_ci: true
@@ -91,6 +121,7 @@ matcher_zoo:
paper: https://arxiv.org/abs/2305.15404
project: https://parskatt.github.io/RoMa
display: true
+ efficiency: low # low, medium, high
dkm:
matcher: dkm
skip_ci: true
@@ -102,6 +133,7 @@ matcher_zoo:
paper: https://arxiv.org/abs/2202.00667
project: https://parskatt.github.io/DKM
display: true
+ efficiency: low # low, medium, high
loftr:
matcher: loftr
dense: true
@@ -144,6 +176,7 @@ matcher_zoo:
paper: https://arxiv.org/abs/2103.14167
project: null
display: true
+ efficiency: low # low, medium, high
topicfm:
matcher: topicfm
dense: true
diff --git a/imcui/ui/app_class.py b/imcui/ui/app_class.py
index 3e8efca..18b4596 100644
--- a/imcui/ui/app_class.py
+++ b/imcui/ui/app_class.py
@@ -47,6 +47,7 @@ def __init__(self, server_name="0.0.0.0", server_port=7860, **kwargs):
self.example_data_root = kwargs.get(
"example_data_root", Path(__file__).parents[1] / "datasets"
)
+ # final step
self.init_interface()
def init_matcher_dropdown(self):
@@ -107,6 +108,8 @@ def init_interface(self):
with gr.Row():
button_reset = gr.Button(value="Reset")
button_run = gr.Button(value="Run Match", variant="primary")
+ with gr.Row():
+ button_stop = gr.Button(value="Force Stop", variant="stop")
with gr.Accordion("Advanced Setting", open=False):
with gr.Accordion("Image Setting", open=True):
@@ -326,7 +329,14 @@ def init_interface(self):
output_pred,
]
# button callbacks
- button_run.click(fn=run_matching, inputs=inputs, outputs=outputs)
+ click_event = button_run.click(
+ fn=run_matching, inputs=inputs, outputs=outputs
+ )
+ # stop button
+ button_stop.click(
+ fn=None, inputs=None, outputs=None, cancels=[click_event]
+ )
+
# Reset images
reset_outputs = [
input_image0,
@@ -519,11 +529,11 @@ def get_link(link, tag="Link"):
markdown_table = "| Algo. | Conference | Code | Project | Paper |\n"
markdown_table += "| ----- | ---------- | ---- | ------- | ----- |\n"
- for k, v in cfg.items():
- if not v["info"]["display"]:
+ for _, v in cfg.items():
+ if not v["info"].get("display", True):
continue
- github_link = get_link(v["info"]["github"])
- project_link = get_link(v["info"]["project"])
+ github_link = get_link(v["info"].get("github", ""))
+ project_link = get_link(v["info"].get("project", ""))
paper_link = get_link(
v["info"]["paper"],
(
@@ -534,8 +544,8 @@ def get_link(link, tag="Link"):
)
markdown_table += "{}|{}|{}|{}|{}\n".format(
- v["info"]["name"], # display name
- v["info"]["source"],
+ v["info"].get("name", ""),
+ v["info"].get("source", ""),
github_link,
project_link,
paper_link,
@@ -547,11 +557,11 @@ def get_link(link, tag="Link"):
continue
data.append(
[
- v["info"]["name"],
- v["info"]["source"],
- v["info"]["github"],
- v["info"]["paper"],
- v["info"]["project"],
+ v["info"].get("name", ""),
+ v["info"].get("source", ""),
+ v["info"].get("github", ""),
+ v["info"].get("paper", ""),
+ v["info"].get("project", ""),
]
)
tab = gr.Dataframe(
diff --git a/imcui/ui/utils.py b/imcui/ui/utils.py
index e2b536e..11a2969 100644
--- a/imcui/ui/utils.py
+++ b/imcui/ui/utils.py
@@ -153,12 +153,14 @@ def parse_match_config(conf):
return {
"matcher": match_dense.confs.get(conf["matcher"]),
"dense": True,
+ "info": conf.get("info", {}),
}
else:
return {
"feature": extract_features.confs.get(conf["feature"]),
"matcher": match_features.confs.get(conf["matcher"]),
"dense": False,
+ "info": conf.get("info", {}),
}
@@ -842,6 +844,32 @@ def run_ransac(
)
+def generate_fake_outputs(
+ output_keypoints,
+ output_matches_raw,
+ output_matches_ransac,
+ match_conf,
+ extract_conf,
+ pred,
+):
+ return (
+ output_keypoints,
+ output_matches_raw,
+ output_matches_ransac,
+ {},
+ {
+ "match_conf": match_conf,
+ "extractor_conf": extract_conf,
+ },
+ {
+ "geom_info": pred.get("geom_info", {}),
+ },
+ None,
+ None,
+ None,
+ )
+
+
def run_matching(
image0: np.ndarray,
image1: np.ndarray,
@@ -911,12 +939,6 @@ def run_matching(
output_matches_raw = None
output_matches_ransac = None
- # super slow!
- if "roma" in key.lower() and DEVICE == "cpu":
- gr.Info(
- f"Success! Please be patient and allow for about 2-3 minutes."
- f" Due to CPU inference, {key} is quiet slow."
- )
t0 = time.time()
model = matcher_zoo[key]
match_conf = model["matcher"]
@@ -924,6 +946,15 @@ def run_matching(
match_conf["model"]["match_threshold"] = match_threshold
match_conf["model"]["max_keypoints"] = extract_max_keypoints
cache_key = "{}_{}".format(key, match_conf["model"]["name"])
+
+ efficiency = model["info"].get("efficiency", "high")
+ if efficiency == "low":
+ gr.Warning(
+ "Matcher {} is time-consuming, please wait for a while".format(
+ model["info"].get("name", "unknown")
+ )
+ )
+
if use_cached_model:
# because of the model cache, we need to update the config
matcher = model_cache.cache_model(cache_key, get_model, match_conf)
@@ -934,6 +965,9 @@ def run_matching(
matcher = get_model(match_conf)
logger.info(f"Loading model using: {time.time()-t0:.3f}s")
t1 = time.time()
+ yield generate_fake_outputs(
+ output_keypoints, output_matches_raw, output_matches_ransac, match_conf, {}, {}
+ )
if model["dense"]:
if not match_conf["preprocessing"].get("force_resize", False):
@@ -997,6 +1031,14 @@ def run_matching(
"Image 1 - Keypoints",
]
output_keypoints = display_keypoints(pred, titles=titles)
+ yield generate_fake_outputs(
+ output_keypoints,
+ output_matches_raw,
+ output_matches_ransac,
+ match_conf,
+ extract_conf,
+ pred,
+ )
# plot images with raw matches
titles = [
@@ -1004,6 +1046,14 @@ def run_matching(
"Image 1 - Raw matched keypoints",
]
output_matches_raw, num_matches_raw = display_matches(pred, titles=titles)
+ yield generate_fake_outputs(
+ output_keypoints,
+ output_matches_raw,
+ output_matches_ransac,
+ match_conf,
+ extract_conf,
+ pred,
+ )
# if enable_ransac:
filter_matches(
@@ -1026,9 +1076,17 @@ def run_matching(
output_matches_ransac, num_matches_ransac = display_matches(
pred, titles=titles, tag="KPTS_RANSAC"
)
+ yield generate_fake_outputs(
+ output_keypoints,
+ output_matches_raw,
+ output_matches_ransac,
+ match_conf,
+ extract_conf,
+ pred,
+ )
+
# gr.Info(f"Display matches done using: {time.time()-t1:.3f}s")
logger.info(f"Display matches done using: {time.time()-t1:.3f}s")
-
t1 = time.time()
# plot wrapped images
output_wrapped, warped_image = generate_warp_images(
@@ -1051,7 +1109,8 @@ def run_matching(
with open(tmp_state_cache, "wb") as f:
pickle.dump(state_cache, f)
logger.info("Dump results done!")
- return (
+
+ yield (
output_keypoints,
output_matches_raw,
output_matches_ransac,
diff --git a/pyproject.toml b/pyproject.toml
index e41546d..bf961d9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "imcui"
description = "Image Matching Webui: A tool for matching images using sota algorithms with a Gradio UI"
-version = "0.0.1"
+version = "0.0.2"
authors = [
{name = "vincentqyw"},
]
@@ -39,3 +39,9 @@ minversion = "6.0"
addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
xfail_strict = true
testpaths = ["tests"]
+filterwarnings = [
+ "ignore::DeprecationWarning",
+ "ignore::UserWarning",
+ "ignore::FutureWarning",
+ "ignore::RuntimeWarning",
+]