import argparse
import gradio as gr
from hloc import extract_features
from extra_utils.utils import (
matcher_zoo,
device,
match_dense,
match_features,
get_model,
get_feature_model,
display_matches
)
def run_matching(
match_threshold, extract_max_keypoints, keypoint_threshold, key, image0, image1
):
# image0 and image1 is RGB mode
if image0 is None or image1 is None:
raise gr.Error("Error: No images found! Please upload two images.")
model = matcher_zoo[key]
match_conf = model["config"]
# update match config
match_conf["model"]["match_threshold"] = match_threshold
match_conf["model"]["max_keypoints"] = extract_max_keypoints
matcher = get_model(match_conf)
if model["dense"]:
pred = match_dense.match_images(
matcher, image0, image1, match_conf["preprocessing"], device=device
)
del matcher
extract_conf = None
else:
extract_conf = model["config_feature"]
# update extract config
extract_conf["model"]["max_keypoints"] = extract_max_keypoints
extract_conf["model"]["keypoint_threshold"] = keypoint_threshold
extractor = get_feature_model(extract_conf)
pred0 = extract_features.extract(
extractor, image0, extract_conf["preprocessing"]
)
pred1 = extract_features.extract(
extractor, image1, extract_conf["preprocessing"]
)
pred = match_features.match_images(matcher, pred0, pred1)
del extractor
fig, num_inliers = display_matches(pred)
del pred
return (
fig,
{"matches number": num_inliers},
{"match_conf": match_conf, "extractor_conf": extract_conf},
)
def ui_change_imagebox(choice):
return {"value": None, "source": choice, "__type__": "update"}
def ui_reset_state(
match_threshold, extract_max_keypoints, keypoint_threshold, key, image0, image1
):
match_threshold = 0.2
extract_max_keypoints = 1000
keypoint_threshold = 0.015
key = list(matcher_zoo.keys())[0]
image0 = None
image1 = None
return (
match_threshold,
extract_max_keypoints,
keypoint_threshold,
key,
image0,
image1,
{"value": None, "source": "upload", "__type__": "update"},
{"value": None, "source": "upload", "__type__": "update"},
"upload",
None,
{},
{},
)
def run(config):
with gr.Blocks(
theme=gr.themes.Monochrome(), css="footer {visibility: hidden}"
) as app:
gr.Markdown(
"""
Image Matching WebUI
"""
)
with gr.Row(equal_height=False):
with gr.Column():
with gr.Row():
matcher_list = gr.Dropdown(
choices=list(matcher_zoo.keys()),
value="disk+lightglue",
label="Matching Model",
interactive=True,
)
match_image_src = gr.Radio(
["upload", "webcam", "canvas"],
label="Image Source",
value="upload",
)
with gr.Row():
match_setting_threshold = gr.Slider(
minimum=0.0,
maximum=1,
step=0.001,
label="Match threshold",
value=0.1,
)
match_setting_max_features = gr.Slider(
minimum=10,
maximum=10000,
step=10,
label="Max number of features",
value=1000,
)
# TODO: add line settings
with gr.Row():
detect_keypoints_threshold = gr.Slider(
minimum=0,
maximum=1,
step=0.001,
label="Keypoint threshold",
value=0.015,
)
detect_line_threshold = gr.Slider(
minimum=0.1,
maximum=1,
step=0.01,
label="Line threshold",
value=0.2,
)
# matcher_lists = gr.Radio(
# ["NN-mutual", "Dual-Softmax"],
# label="Matcher mode",
# value="NN-mutual",
# )
with gr.Row():
input_image0 = gr.Image(
label="Image 0",
type="numpy",
interactive=True,
image_mode="RGB",
)
input_image1 = gr.Image(
label="Image 1",
type="numpy",
interactive=True,
image_mode="RGB",
)
with gr.Row():
button_reset = gr.Button(label="Reset", value="Reset")
button_run = gr.Button(
label="Run Match", value="Run Match", variant="primary"
)
with gr.Accordion("Open for More!", open=False):
gr.Markdown(
f"""
Supported Algorithms
{", ".join(matcher_zoo.keys())}
"""
)
# collect inputs
inputs = [
match_setting_threshold,
match_setting_max_features,
detect_keypoints_threshold,
matcher_list,
input_image0,
input_image1,
]
# Add some examples
with gr.Row():
examples = [
[
0.1,
2000,
0.015,
"disk+lightglue",
"datasets/sacre_coeur/mapping/71295362_4051449754.jpg",
"datasets/sacre_coeur/mapping/93341989_396310999.jpg",
],
[
0.1,
2000,
0.015,
"loftr",
"datasets/sacre_coeur/mapping/03903474_1471484089.jpg",
"datasets/sacre_coeur/mapping/02928139_3448003521.jpg",
],
[
0.1,
2000,
0.015,
"disk",
"datasets/sacre_coeur/mapping/10265353_3838484249.jpg",
"datasets/sacre_coeur/mapping/51091044_3486849416.jpg",
],
[
0.1,
2000,
0.015,
"topicfm",
"datasets/sacre_coeur/mapping/44120379_8371960244.jpg",
"datasets/sacre_coeur/mapping/93341989_396310999.jpg",
],
[
0.1,
2000,
0.015,
"superpoint+superglue",
"datasets/sacre_coeur/mapping/17295357_9106075285.jpg",
"datasets/sacre_coeur/mapping/44120379_8371960244.jpg",
],
]
# Example inputs
gr.Examples(
examples=examples,
inputs=inputs,
outputs=[],
fn=run_matching,
cache_examples=False,
label="Examples (click one of the images below to Run Match)",
)
with gr.Column():
output_mkpts = gr.Image(label="Keypoints Matching", type="numpy")
matches_result_info = gr.JSON(label="Matches Statistics")
matcher_info = gr.JSON(label="Match info")
# callbacks
match_image_src.change(
fn=ui_change_imagebox, inputs=match_image_src, outputs=input_image0
)
match_image_src.change(
fn=ui_change_imagebox, inputs=match_image_src, outputs=input_image1
)
# collect outputs
outputs = [
output_mkpts,
matches_result_info,
matcher_info,
]
# button callbacks
button_run.click(fn=run_matching, inputs=inputs, outputs=outputs)
# Reset images
reset_outputs = [
match_setting_threshold,
match_setting_max_features,
detect_keypoints_threshold,
matcher_list,
input_image0,
input_image1,
input_image0,
input_image1,
match_image_src,
output_mkpts,
matches_result_info,
matcher_info,
]
button_reset.click(fn=ui_reset_state, inputs=inputs, outputs=reset_outputs)
app.launch(share=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--config_path", type=str, default="config.yaml", help="configuration file path"
)
args = parser.parse_args()
config = None
run(config)