Vincentqyw
commited on
Commit
•
60ad158
1
Parent(s):
62cd4d6
update: limit keypoints number
Browse files- app.py +4 -4
- common/utils.py +1 -1
- hloc/__init__.py +2 -1
- hloc/extract_features.py +45 -22
- hloc/extractors/d2net.py +6 -0
- hloc/extractors/darkfeat.py +7 -1
- hloc/extractors/dedode.py +18 -9
- hloc/extractors/dir.py +6 -2
- hloc/extractors/dog.py +4 -1
- hloc/extractors/example.py +0 -1
- hloc/extractors/fire.py +0 -2
- hloc/extractors/fire_local.py +0 -2
- hloc/extractors/lanet.py +11 -9
- hloc/extractors/netvlad.py +9 -3
- hloc/extractors/rekd.py +21 -5
- hloc/extractors/superpoint.py +4 -1
- hloc/match_dense.py +13 -4
- hloc/match_features.py +15 -9
- hloc/matchers/aspanformer.py +29 -6
- hloc/matchers/dkm.py +3 -1
- hloc/matchers/dual_softmax.py +11 -3
- hloc/matchers/gluestick.py +4 -1
- hloc/matchers/nearest_neighbor.py +17 -5
- hloc/matchers/roma.py +3 -1
- hloc/matchers/sgmnet.py +15 -3
- hloc/matchers/sold2.py +1 -0
- hloc/pipelines/4Seasons/localize.py +3 -1
- hloc/pipelines/4Seasons/utils.py +14 -4
- hloc/pipelines/7Scenes/create_gt_sfm.py +12 -2
- hloc/pipelines/7Scenes/pipeline.py +6 -2
- hloc/pipelines/Aachen/pipeline.py +9 -3
- hloc/pipelines/Aachen_v1_1/pipeline.py +9 -3
- hloc/pipelines/Aachen_v1_1/pipeline_loftr.py +3 -1
- hloc/pipelines/CMU/pipeline.py +25 -7
- hloc/pipelines/Cambridge/pipeline.py +15 -3
- hloc/pipelines/Cambridge/utils.py +3 -1
- hloc/pipelines/RobotCar/colmap_from_nvm.py +6 -3
- hloc/pipelines/RobotCar/pipeline.py +6 -2
- hloc/utils/database.py +28 -7
- hloc/utils/geometry.py +6 -6
- hloc/utils/read_write_model.py +46 -16
- hloc/utils/viz.py +10 -2
- hloc/utils/viz_3d.py +9 -3
app.py
CHANGED
@@ -143,7 +143,7 @@ def run(config):
|
|
143 |
# label="Matcher mode",
|
144 |
# value="NN-mutual",
|
145 |
# )
|
146 |
-
with gr.Accordion("RANSAC Setting", open=
|
147 |
with gr.Row(equal_height=False):
|
148 |
enable_ransac = gr.Checkbox(label="Enable RANSAC")
|
149 |
ransac_method = gr.Dropdown(
|
@@ -174,7 +174,7 @@ def run(config):
|
|
174 |
value=10000,
|
175 |
)
|
176 |
|
177 |
-
with gr.Accordion("Geometry Setting", open=
|
178 |
with gr.Row(equal_height=False):
|
179 |
# show_geom = gr.Checkbox(label="Show Geometry")
|
180 |
choice_estimate_geom = gr.Radio(
|
@@ -227,13 +227,13 @@ def run(config):
|
|
227 |
label="Keypoints Matching", type="numpy"
|
228 |
)
|
229 |
with gr.Accordion(
|
230 |
-
"Open for More: Matches Statistics", open=
|
231 |
):
|
232 |
matches_result_info = gr.JSON(label="Matches Statistics")
|
233 |
matcher_info = gr.JSON(label="Match info")
|
234 |
|
235 |
-
output_wrapped = gr.Image(label="Wrapped Pair", type="numpy")
|
236 |
with gr.Accordion("Open for More: Geometry info", open=False):
|
|
|
237 |
geometry_result = gr.JSON(label="Reconstructed Geometry")
|
238 |
|
239 |
# callbacks
|
|
|
143 |
# label="Matcher mode",
|
144 |
# value="NN-mutual",
|
145 |
# )
|
146 |
+
with gr.Accordion("RANSAC Setting", open=True):
|
147 |
with gr.Row(equal_height=False):
|
148 |
enable_ransac = gr.Checkbox(label="Enable RANSAC")
|
149 |
ransac_method = gr.Dropdown(
|
|
|
174 |
value=10000,
|
175 |
)
|
176 |
|
177 |
+
with gr.Accordion("Geometry Setting", open=False):
|
178 |
with gr.Row(equal_height=False):
|
179 |
# show_geom = gr.Checkbox(label="Show Geometry")
|
180 |
choice_estimate_geom = gr.Radio(
|
|
|
227 |
label="Keypoints Matching", type="numpy"
|
228 |
)
|
229 |
with gr.Accordion(
|
230 |
+
"Open for More: Matches Statistics", open=True
|
231 |
):
|
232 |
matches_result_info = gr.JSON(label="Matches Statistics")
|
233 |
matcher_info = gr.JSON(label="Match info")
|
234 |
|
|
|
235 |
with gr.Accordion("Open for More: Geometry info", open=False):
|
236 |
+
output_wrapped = gr.Image(label="Wrapped Pair", type="numpy")
|
237 |
geometry_result = gr.JSON(label="Reconstructed Geometry")
|
238 |
|
239 |
# callbacks
|
common/utils.py
CHANGED
@@ -53,7 +53,7 @@ def gen_examples():
|
|
53 |
match_setting_threshold = 0.1
|
54 |
match_setting_max_features = 2000
|
55 |
detect_keypoints_threshold = 0.01
|
56 |
-
enable_ransac =
|
57 |
ransac_method = "RANSAC"
|
58 |
ransac_reproj_threshold = 8
|
59 |
ransac_confidence = 0.999
|
|
|
53 |
match_setting_threshold = 0.1
|
54 |
match_setting_max_features = 2000
|
55 |
detect_keypoints_threshold = 0.01
|
56 |
+
enable_ransac = True
|
57 |
ransac_method = "RANSAC"
|
58 |
ransac_reproj_threshold = 8
|
59 |
ransac_confidence = 0.999
|
hloc/__init__.py
CHANGED
@@ -4,7 +4,8 @@ from packaging import version
|
|
4 |
__version__ = "1.3"
|
5 |
|
6 |
formatter = logging.Formatter(
|
7 |
-
fmt="[%(asctime)s %(name)s %(levelname)s] %(message)s",
|
|
|
8 |
)
|
9 |
handler = logging.StreamHandler()
|
10 |
handler.setFormatter(formatter)
|
|
|
4 |
__version__ = "1.3"
|
5 |
|
6 |
formatter = logging.Formatter(
|
7 |
+
fmt="[%(asctime)s %(name)s %(levelname)s] %(message)s",
|
8 |
+
datefmt="%Y/%m/%d %H:%M:%S",
|
9 |
)
|
10 |
handler = logging.StreamHandler()
|
11 |
handler.setFormatter(formatter)
|
hloc/extract_features.py
CHANGED
@@ -85,17 +85,18 @@ confs = {
|
|
85 |
"preprocessing": {
|
86 |
"grayscale": False,
|
87 |
"force_resize": True,
|
88 |
-
"resize_max":
|
89 |
"width": 640,
|
90 |
"height": 480,
|
91 |
"dfactor": 8,
|
92 |
},
|
93 |
},
|
94 |
"d2net-ss": {
|
95 |
-
"output": "feats-d2net-ss",
|
96 |
"model": {
|
97 |
"name": "d2net",
|
98 |
"multiscale": False,
|
|
|
99 |
},
|
100 |
"preprocessing": {
|
101 |
"grayscale": False,
|
@@ -103,10 +104,11 @@ confs = {
|
|
103 |
},
|
104 |
},
|
105 |
"d2net-ms": {
|
106 |
-
"output": "feats-d2net-ms",
|
107 |
"model": {
|
108 |
"name": "d2net",
|
109 |
"multiscale": True,
|
|
|
110 |
},
|
111 |
"preprocessing": {
|
112 |
"grayscale": False,
|
@@ -114,7 +116,7 @@ confs = {
|
|
114 |
},
|
115 |
},
|
116 |
"rootsift": {
|
117 |
-
"output": "feats-
|
118 |
"model": {
|
119 |
"name": "dog",
|
120 |
"max_keypoints": 5000,
|
@@ -129,7 +131,7 @@ confs = {
|
|
129 |
},
|
130 |
},
|
131 |
"sift": {
|
132 |
-
"output": "feats-sift",
|
133 |
"model": {
|
134 |
"name": "dog",
|
135 |
"descriptor": "sift",
|
@@ -145,8 +147,12 @@ confs = {
|
|
145 |
},
|
146 |
},
|
147 |
"sosnet": {
|
148 |
-
"output": "feats-sosnet",
|
149 |
-
"model": {
|
|
|
|
|
|
|
|
|
150 |
"preprocessing": {
|
151 |
"grayscale": True,
|
152 |
"resize_max": 1600,
|
@@ -157,8 +163,12 @@ confs = {
|
|
157 |
},
|
158 |
},
|
159 |
"hardnet": {
|
160 |
-
"output": "feats-hardnet",
|
161 |
-
"model": {
|
|
|
|
|
|
|
|
|
162 |
"preprocessing": {
|
163 |
"grayscale": True,
|
164 |
"resize_max": 1600,
|
@@ -169,7 +179,7 @@ confs = {
|
|
169 |
},
|
170 |
},
|
171 |
"disk": {
|
172 |
-
"output": "feats-disk",
|
173 |
"model": {
|
174 |
"name": "disk",
|
175 |
"max_keypoints": 5000,
|
@@ -180,7 +190,7 @@ confs = {
|
|
180 |
},
|
181 |
},
|
182 |
"alike": {
|
183 |
-
"output": "feats-alike",
|
184 |
"model": {
|
185 |
"name": "alike",
|
186 |
"max_keypoints": 5000,
|
@@ -196,7 +206,7 @@ confs = {
|
|
196 |
},
|
197 |
},
|
198 |
"lanet": {
|
199 |
-
"output": "feats-lanet",
|
200 |
"model": {
|
201 |
"name": "lanet",
|
202 |
"keypoint_threshold": 0.1,
|
@@ -208,7 +218,7 @@ confs = {
|
|
208 |
},
|
209 |
},
|
210 |
"darkfeat": {
|
211 |
-
"output": "feats-darkfeat-n5000-
|
212 |
"model": {
|
213 |
"name": "darkfeat",
|
214 |
"max_keypoints": 5000,
|
@@ -225,7 +235,7 @@ confs = {
|
|
225 |
},
|
226 |
},
|
227 |
"dedode": {
|
228 |
-
"output": "feats-dedode-n5000-
|
229 |
"model": {
|
230 |
"name": "dedode",
|
231 |
"max_keypoints": 5000,
|
@@ -233,14 +243,14 @@ confs = {
|
|
233 |
"preprocessing": {
|
234 |
"grayscale": False,
|
235 |
"force_resize": True,
|
236 |
-
"resize_max":
|
237 |
"width": 768,
|
238 |
"height": 768,
|
239 |
"dfactor": 8,
|
240 |
},
|
241 |
},
|
242 |
"example": {
|
243 |
-
"output": "feats-example-
|
244 |
"model": {
|
245 |
"name": "example",
|
246 |
"keypoint_threshold": 0.1,
|
@@ -323,13 +333,17 @@ class ImageDataset(torch.utils.data.Dataset):
|
|
323 |
if isinstance(paths, (Path, str)):
|
324 |
self.names = parse_image_lists(paths)
|
325 |
elif isinstance(paths, collections.Iterable):
|
326 |
-
self.names = [
|
|
|
|
|
327 |
else:
|
328 |
raise ValueError(f"Unknown format for path argument {paths}.")
|
329 |
|
330 |
for name in self.names:
|
331 |
if not (root / name).exists():
|
332 |
-
raise ValueError(
|
|
|
|
|
333 |
|
334 |
def __getitem__(self, idx):
|
335 |
name = self.names[idx]
|
@@ -397,7 +411,10 @@ def extract(model, image_0, conf):
|
|
397 |
|
398 |
# assure that the size is divisible by dfactor
|
399 |
size_new = tuple(
|
400 |
-
map(
|
|
|
|
|
|
|
401 |
)
|
402 |
image = F.resize(image, size=size_new, antialias=True)
|
403 |
input_ = image.to(device, non_blocking=True)[None]
|
@@ -435,7 +452,8 @@ def main(
|
|
435 |
overwrite: bool = False,
|
436 |
) -> Path:
|
437 |
logger.info(
|
438 |
-
"Extracting local features with configuration:"
|
|
|
439 |
)
|
440 |
|
441 |
dataset = ImageDataset(image_dir, conf["preprocessing"], image_list)
|
@@ -443,7 +461,9 @@ def main(
|
|
443 |
feature_path = Path(export_dir, conf["output"] + ".h5")
|
444 |
feature_path.parent.mkdir(exist_ok=True, parents=True)
|
445 |
skip_names = set(
|
446 |
-
list_h5_names(feature_path)
|
|
|
|
|
447 |
)
|
448 |
dataset.names = [n for n in dataset.names if n not in skip_names]
|
449 |
if len(dataset.names) == 0:
|
@@ -507,7 +527,10 @@ if __name__ == "__main__":
|
|
507 |
parser.add_argument("--image_dir", type=Path, required=True)
|
508 |
parser.add_argument("--export_dir", type=Path, required=True)
|
509 |
parser.add_argument(
|
510 |
-
"--conf",
|
|
|
|
|
|
|
511 |
)
|
512 |
parser.add_argument("--as_half", action="store_true")
|
513 |
parser.add_argument("--image_list", type=Path)
|
|
|
85 |
"preprocessing": {
|
86 |
"grayscale": False,
|
87 |
"force_resize": True,
|
88 |
+
"resize_max": 1024,
|
89 |
"width": 640,
|
90 |
"height": 480,
|
91 |
"dfactor": 8,
|
92 |
},
|
93 |
},
|
94 |
"d2net-ss": {
|
95 |
+
"output": "feats-d2net-ss-n5000-r1600",
|
96 |
"model": {
|
97 |
"name": "d2net",
|
98 |
"multiscale": False,
|
99 |
+
"max_keypoints": 5000,
|
100 |
},
|
101 |
"preprocessing": {
|
102 |
"grayscale": False,
|
|
|
104 |
},
|
105 |
},
|
106 |
"d2net-ms": {
|
107 |
+
"output": "feats-d2net-ms-n5000-r1600",
|
108 |
"model": {
|
109 |
"name": "d2net",
|
110 |
"multiscale": True,
|
111 |
+
"max_keypoints": 5000,
|
112 |
},
|
113 |
"preprocessing": {
|
114 |
"grayscale": False,
|
|
|
116 |
},
|
117 |
},
|
118 |
"rootsift": {
|
119 |
+
"output": "feats-rootsift-n5000-r1600",
|
120 |
"model": {
|
121 |
"name": "dog",
|
122 |
"max_keypoints": 5000,
|
|
|
131 |
},
|
132 |
},
|
133 |
"sift": {
|
134 |
+
"output": "feats-sift-n5000-r1600",
|
135 |
"model": {
|
136 |
"name": "dog",
|
137 |
"descriptor": "sift",
|
|
|
147 |
},
|
148 |
},
|
149 |
"sosnet": {
|
150 |
+
"output": "feats-sosnet-n5000-r1600",
|
151 |
+
"model": {
|
152 |
+
"name": "dog",
|
153 |
+
"descriptor": "sosnet",
|
154 |
+
"max_keypoints": 5000,
|
155 |
+
},
|
156 |
"preprocessing": {
|
157 |
"grayscale": True,
|
158 |
"resize_max": 1600,
|
|
|
163 |
},
|
164 |
},
|
165 |
"hardnet": {
|
166 |
+
"output": "feats-hardnet-n5000-r1600",
|
167 |
+
"model": {
|
168 |
+
"name": "dog",
|
169 |
+
"descriptor": "hardnet",
|
170 |
+
"max_keypoints": 5000,
|
171 |
+
},
|
172 |
"preprocessing": {
|
173 |
"grayscale": True,
|
174 |
"resize_max": 1600,
|
|
|
179 |
},
|
180 |
},
|
181 |
"disk": {
|
182 |
+
"output": "feats-disk-n5000-r1600",
|
183 |
"model": {
|
184 |
"name": "disk",
|
185 |
"max_keypoints": 5000,
|
|
|
190 |
},
|
191 |
},
|
192 |
"alike": {
|
193 |
+
"output": "feats-alike-n5000-r1600",
|
194 |
"model": {
|
195 |
"name": "alike",
|
196 |
"max_keypoints": 5000,
|
|
|
206 |
},
|
207 |
},
|
208 |
"lanet": {
|
209 |
+
"output": "feats-lanet-n5000-r1600",
|
210 |
"model": {
|
211 |
"name": "lanet",
|
212 |
"keypoint_threshold": 0.1,
|
|
|
218 |
},
|
219 |
},
|
220 |
"darkfeat": {
|
221 |
+
"output": "feats-darkfeat-n5000-r1600",
|
222 |
"model": {
|
223 |
"name": "darkfeat",
|
224 |
"max_keypoints": 5000,
|
|
|
235 |
},
|
236 |
},
|
237 |
"dedode": {
|
238 |
+
"output": "feats-dedode-n5000-r1600",
|
239 |
"model": {
|
240 |
"name": "dedode",
|
241 |
"max_keypoints": 5000,
|
|
|
243 |
"preprocessing": {
|
244 |
"grayscale": False,
|
245 |
"force_resize": True,
|
246 |
+
"resize_max": 1600,
|
247 |
"width": 768,
|
248 |
"height": 768,
|
249 |
"dfactor": 8,
|
250 |
},
|
251 |
},
|
252 |
"example": {
|
253 |
+
"output": "feats-example-n2000-r1024",
|
254 |
"model": {
|
255 |
"name": "example",
|
256 |
"keypoint_threshold": 0.1,
|
|
|
333 |
if isinstance(paths, (Path, str)):
|
334 |
self.names = parse_image_lists(paths)
|
335 |
elif isinstance(paths, collections.Iterable):
|
336 |
+
self.names = [
|
337 |
+
p.as_posix() if isinstance(p, Path) else p for p in paths
|
338 |
+
]
|
339 |
else:
|
340 |
raise ValueError(f"Unknown format for path argument {paths}.")
|
341 |
|
342 |
for name in self.names:
|
343 |
if not (root / name).exists():
|
344 |
+
raise ValueError(
|
345 |
+
f"Image {name} does not exists in root: {root}."
|
346 |
+
)
|
347 |
|
348 |
def __getitem__(self, idx):
|
349 |
name = self.names[idx]
|
|
|
411 |
|
412 |
# assure that the size is divisible by dfactor
|
413 |
size_new = tuple(
|
414 |
+
map(
|
415 |
+
lambda x: int(x // conf.dfactor * conf.dfactor),
|
416 |
+
image.shape[-2:],
|
417 |
+
)
|
418 |
)
|
419 |
image = F.resize(image, size=size_new, antialias=True)
|
420 |
input_ = image.to(device, non_blocking=True)[None]
|
|
|
452 |
overwrite: bool = False,
|
453 |
) -> Path:
|
454 |
logger.info(
|
455 |
+
"Extracting local features with configuration:"
|
456 |
+
f"\n{pprint.pformat(conf)}"
|
457 |
)
|
458 |
|
459 |
dataset = ImageDataset(image_dir, conf["preprocessing"], image_list)
|
|
|
461 |
feature_path = Path(export_dir, conf["output"] + ".h5")
|
462 |
feature_path.parent.mkdir(exist_ok=True, parents=True)
|
463 |
skip_names = set(
|
464 |
+
list_h5_names(feature_path)
|
465 |
+
if feature_path.exists() and not overwrite
|
466 |
+
else ()
|
467 |
)
|
468 |
dataset.names = [n for n in dataset.names if n not in skip_names]
|
469 |
if len(dataset.names) == 0:
|
|
|
527 |
parser.add_argument("--image_dir", type=Path, required=True)
|
528 |
parser.add_argument("--export_dir", type=Path, required=True)
|
529 |
parser.add_argument(
|
530 |
+
"--conf",
|
531 |
+
type=str,
|
532 |
+
default="superpoint_aachen",
|
533 |
+
choices=list(confs.keys()),
|
534 |
)
|
535 |
parser.add_argument("--as_half", action="store_true")
|
536 |
parser.add_argument("--image_list", type=Path)
|
hloc/extractors/d2net.py
CHANGED
@@ -17,6 +17,7 @@ class D2Net(BaseModel):
|
|
17 |
"checkpoint_dir": d2net_path / "models",
|
18 |
"use_relu": True,
|
19 |
"multiscale": False,
|
|
|
20 |
}
|
21 |
required_inputs = ["image"]
|
22 |
|
@@ -50,6 +51,11 @@ class D2Net(BaseModel):
|
|
50 |
)
|
51 |
keypoints = keypoints[:, [1, 0]] # (x, y) and remove the scale
|
52 |
|
|
|
|
|
|
|
|
|
|
|
53 |
return {
|
54 |
"keypoints": torch.from_numpy(keypoints)[None],
|
55 |
"scores": torch.from_numpy(scores)[None],
|
|
|
17 |
"checkpoint_dir": d2net_path / "models",
|
18 |
"use_relu": True,
|
19 |
"multiscale": False,
|
20 |
+
"max_keypoints": 1024,
|
21 |
}
|
22 |
required_inputs = ["image"]
|
23 |
|
|
|
51 |
)
|
52 |
keypoints = keypoints[:, [1, 0]] # (x, y) and remove the scale
|
53 |
|
54 |
+
idxs = scores.argsort()[-self.conf["max_keypoints"] or None :]
|
55 |
+
keypoints = keypoints[idxs, :2]
|
56 |
+
descriptors = descriptors[idxs]
|
57 |
+
scores = scores[idxs]
|
58 |
+
|
59 |
return {
|
60 |
"keypoints": torch.from_numpy(keypoints)[None],
|
61 |
"scores": torch.from_numpy(scores)[None],
|
hloc/extractors/darkfeat.py
CHANGED
@@ -32,7 +32,9 @@ class DarkFeat(BaseModel):
|
|
32 |
model_path.parent.mkdir(exist_ok=True)
|
33 |
cmd_wo_proxy = ["gdown", link, "-O", str(model_path)]
|
34 |
cmd = ["gdown", link, "-O", str(model_path), "--proxy", self.proxy]
|
35 |
-
logger.info(
|
|
|
|
|
36 |
try:
|
37 |
subprocess.run(cmd_wo_proxy, check=True)
|
38 |
except subprocess.CalledProcessError as e:
|
@@ -50,6 +52,10 @@ class DarkFeat(BaseModel):
|
|
50 |
keypoints = pred["keypoints"]
|
51 |
descriptors = pred["descriptors"]
|
52 |
scores = pred["scores"]
|
|
|
|
|
|
|
|
|
53 |
return {
|
54 |
"keypoints": keypoints[None], # 1 x N x 2
|
55 |
"scores": scores[None], # 1 x N
|
|
|
32 |
model_path.parent.mkdir(exist_ok=True)
|
33 |
cmd_wo_proxy = ["gdown", link, "-O", str(model_path)]
|
34 |
cmd = ["gdown", link, "-O", str(model_path), "--proxy", self.proxy]
|
35 |
+
logger.info(
|
36 |
+
f"Downloading the DarkFeat model with `{cmd_wo_proxy}`."
|
37 |
+
)
|
38 |
try:
|
39 |
subprocess.run(cmd_wo_proxy, check=True)
|
40 |
except subprocess.CalledProcessError as e:
|
|
|
52 |
keypoints = pred["keypoints"]
|
53 |
descriptors = pred["descriptors"]
|
54 |
scores = pred["scores"]
|
55 |
+
idxs = scores.argsort()[-self.conf["max_keypoints"] or None :]
|
56 |
+
keypoints = keypoints[idxs, :2]
|
57 |
+
descriptors = descriptors[:, idxs]
|
58 |
+
scores = scores[idxs]
|
59 |
return {
|
60 |
"keypoints": keypoints[None], # 1 x N x 2
|
61 |
"scores": scores[None], # 1 x N
|
hloc/extractors/dedode.py
CHANGED
@@ -36,7 +36,9 @@ class DeDoDe(BaseModel):
|
|
36 |
|
37 |
# Initialize the line matcher
|
38 |
def _init(self, conf):
|
39 |
-
model_detector_path =
|
|
|
|
|
40 |
model_descriptor_path = (
|
41 |
dedode_path / "pretrained" / conf["model_descriptor_name"]
|
42 |
)
|
@@ -56,17 +58,24 @@ class DeDoDe(BaseModel):
|
|
56 |
model_descriptor_path.parent.mkdir(exist_ok=True)
|
57 |
link = self.weight_urls[conf["model_descriptor_name"]]
|
58 |
cmd = ["wget", link, "-O", str(model_descriptor_path)]
|
59 |
-
logger.info(
|
|
|
|
|
60 |
subprocess.run(cmd, check=True)
|
61 |
|
62 |
logger.info(f"Loading DeDoDe model...")
|
63 |
|
64 |
# load the model
|
65 |
weights_detector = torch.load(model_detector_path, map_location="cpu")
|
66 |
-
weights_descriptor = torch.load(
|
67 |
-
|
68 |
-
|
69 |
-
|
|
|
|
|
|
|
|
|
|
|
70 |
logger.info(f"Load DeDoDe model done.")
|
71 |
|
72 |
def _forward(self, data):
|
@@ -91,9 +100,9 @@ class DeDoDe(BaseModel):
|
|
91 |
|
92 |
# step 2: describe keypoints
|
93 |
# dim: 1 x N x 256
|
94 |
-
description_A = self.descriptor.describe_keypoints(
|
95 |
-
|
96 |
-
]
|
97 |
keypoints_A = to_pixel_coords(keypoints_A, H_A, W_A)
|
98 |
|
99 |
return {
|
|
|
36 |
|
37 |
# Initialize the line matcher
|
38 |
def _init(self, conf):
|
39 |
+
model_detector_path = (
|
40 |
+
dedode_path / "pretrained" / conf["model_detector_name"]
|
41 |
+
)
|
42 |
model_descriptor_path = (
|
43 |
dedode_path / "pretrained" / conf["model_descriptor_name"]
|
44 |
)
|
|
|
58 |
model_descriptor_path.parent.mkdir(exist_ok=True)
|
59 |
link = self.weight_urls[conf["model_descriptor_name"]]
|
60 |
cmd = ["wget", link, "-O", str(model_descriptor_path)]
|
61 |
+
logger.info(
|
62 |
+
f"Downloading the DeDoDe descriptor model with `{cmd}`."
|
63 |
+
)
|
64 |
subprocess.run(cmd, check=True)
|
65 |
|
66 |
logger.info(f"Loading DeDoDe model...")
|
67 |
|
68 |
# load the model
|
69 |
weights_detector = torch.load(model_detector_path, map_location="cpu")
|
70 |
+
weights_descriptor = torch.load(
|
71 |
+
model_descriptor_path, map_location="cpu"
|
72 |
+
)
|
73 |
+
self.detector = dedode_detector_L(
|
74 |
+
weights=weights_detector, device=device
|
75 |
+
)
|
76 |
+
self.descriptor = dedode_descriptor_B(
|
77 |
+
weights=weights_descriptor, device=device
|
78 |
+
)
|
79 |
logger.info(f"Load DeDoDe model done.")
|
80 |
|
81 |
def _forward(self, data):
|
|
|
100 |
|
101 |
# step 2: describe keypoints
|
102 |
# dim: 1 x N x 256
|
103 |
+
description_A = self.descriptor.describe_keypoints(
|
104 |
+
batch_A, keypoints_A
|
105 |
+
)["descriptions"]
|
106 |
keypoints_A = to_pixel_coords(keypoints_A, H_A, W_A)
|
107 |
|
108 |
return {
|
hloc/extractors/dir.py
CHANGED
@@ -8,7 +8,9 @@ import gdown
|
|
8 |
|
9 |
from ..utils.base_model import BaseModel
|
10 |
|
11 |
-
sys.path.append(
|
|
|
|
|
12 |
os.environ["DB_ROOT"] = "" # required by dirtorch
|
13 |
|
14 |
from dirtorch.utils import common # noqa: E402
|
@@ -40,7 +42,9 @@ class DIR(BaseModel):
|
|
40 |
}
|
41 |
|
42 |
def _init(self, conf):
|
43 |
-
checkpoint = Path(
|
|
|
|
|
44 |
if not checkpoint.exists():
|
45 |
checkpoint.parent.mkdir(exist_ok=True, parents=True)
|
46 |
link = self.dir_models[conf["model_name"]]
|
|
|
8 |
|
9 |
from ..utils.base_model import BaseModel
|
10 |
|
11 |
+
sys.path.append(
|
12 |
+
str(Path(__file__).parent / "../../third_party/deep-image-retrieval")
|
13 |
+
)
|
14 |
os.environ["DB_ROOT"] = "" # required by dirtorch
|
15 |
|
16 |
from dirtorch.utils import common # noqa: E402
|
|
|
42 |
}
|
43 |
|
44 |
def _init(self, conf):
|
45 |
+
checkpoint = Path(
|
46 |
+
torch.hub.get_dir(), "dirtorch", conf["model_name"] + ".pt"
|
47 |
+
)
|
48 |
if not checkpoint.exists():
|
49 |
checkpoint.parent.mkdir(exist_ok=True, parents=True)
|
50 |
link = self.dir_models[conf["model_name"]]
|
hloc/extractors/dog.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
import kornia
|
2 |
-
from kornia.feature.laf import
|
|
|
|
|
|
|
3 |
import numpy as np
|
4 |
import torch
|
5 |
import pycolmap
|
|
|
1 |
import kornia
|
2 |
+
from kornia.feature.laf import (
|
3 |
+
laf_from_center_scale_ori,
|
4 |
+
extract_patches_from_pyramid,
|
5 |
+
)
|
6 |
import numpy as np
|
7 |
import torch
|
8 |
import pycolmap
|
hloc/extractors/example.py
CHANGED
@@ -26,7 +26,6 @@ class Example(BaseModel):
|
|
26 |
required_inputs = ["image"]
|
27 |
|
28 |
def _init(self, conf):
|
29 |
-
|
30 |
# set checkpoints paths if needed
|
31 |
model_path = example_path / "checkpoints" / f'{conf["model_name"]}'
|
32 |
if not model_path.exists():
|
|
|
26 |
required_inputs = ["image"]
|
27 |
|
28 |
def _init(self, conf):
|
|
|
29 |
# set checkpoints paths if needed
|
30 |
model_path = example_path / "checkpoints" / f'{conf["model_name"]}'
|
31 |
if not model_path.exists():
|
hloc/extractors/fire.py
CHANGED
@@ -34,7 +34,6 @@ class FIRe(BaseModel):
|
|
34 |
}
|
35 |
|
36 |
def _init(self, conf):
|
37 |
-
|
38 |
assert conf["model_name"] in self.fire_models.keys()
|
39 |
# Config paths
|
40 |
model_path = fire_path / "model" / conf["model_name"]
|
@@ -64,7 +63,6 @@ class FIRe(BaseModel):
|
|
64 |
self.scales = conf["scales"]
|
65 |
|
66 |
def _forward(self, data):
|
67 |
-
|
68 |
image = self.norm_rgb(data["image"])
|
69 |
|
70 |
# Feature extraction.
|
|
|
34 |
}
|
35 |
|
36 |
def _init(self, conf):
|
|
|
37 |
assert conf["model_name"] in self.fire_models.keys()
|
38 |
# Config paths
|
39 |
model_path = fire_path / "model" / conf["model_name"]
|
|
|
63 |
self.scales = conf["scales"]
|
64 |
|
65 |
def _forward(self, data):
|
|
|
66 |
image = self.norm_rgb(data["image"])
|
67 |
|
68 |
# Feature extraction.
|
hloc/extractors/fire_local.py
CHANGED
@@ -41,7 +41,6 @@ class FIRe(BaseModel):
|
|
41 |
}
|
42 |
|
43 |
def _init(self, conf):
|
44 |
-
|
45 |
assert conf["model_name"] in self.fire_models.keys()
|
46 |
|
47 |
# Config paths
|
@@ -75,7 +74,6 @@ class FIRe(BaseModel):
|
|
75 |
self.features_num = conf["features_num"]
|
76 |
|
77 |
def _forward(self, data):
|
78 |
-
|
79 |
image = self.norm_rgb(data["image"])
|
80 |
|
81 |
local_desc = self.net.forward_local(
|
|
|
41 |
}
|
42 |
|
43 |
def _init(self, conf):
|
|
|
44 |
assert conf["model_name"] in self.fire_models.keys()
|
45 |
|
46 |
# Config paths
|
|
|
74 |
self.features_num = conf["features_num"]
|
75 |
|
76 |
def _forward(self, data):
|
|
|
77 |
image = self.norm_rgb(data["image"])
|
78 |
|
79 |
local_desc = self.net.forward_local(
|
hloc/extractors/lanet.py
CHANGED
@@ -21,7 +21,9 @@ class LANet(BaseModel):
|
|
21 |
required_inputs = ["image"]
|
22 |
|
23 |
def _init(self, conf):
|
24 |
-
model_path =
|
|
|
|
|
25 |
if not model_path.exists():
|
26 |
print(f"No model found at {model_path}")
|
27 |
self.net = PointModel(is_test=True)
|
@@ -34,16 +36,16 @@ class LANet(BaseModel):
|
|
34 |
_, _, Hc, Wc = descriptors.shape
|
35 |
|
36 |
# Scores & Descriptors
|
37 |
-
kpts_score = (
|
38 |
-
|
39 |
-
)
|
40 |
-
descriptors = (
|
41 |
-
descriptors.view(256, Hc, Wc).view(256, -1).t()
|
42 |
-
)
|
43 |
|
44 |
# Filter based on confidence threshold
|
45 |
-
descriptors = descriptors[
|
46 |
-
|
|
|
|
|
|
|
|
|
47 |
keypoints = kpts_score[:, 1:]
|
48 |
scores = kpts_score[:, 0]
|
49 |
|
|
|
21 |
required_inputs = ["image"]
|
22 |
|
23 |
def _init(self, conf):
|
24 |
+
model_path = (
|
25 |
+
lanet_path / "checkpoints" / f'PointModel_{conf["model_name"]}.pth'
|
26 |
+
)
|
27 |
if not model_path.exists():
|
28 |
print(f"No model found at {model_path}")
|
29 |
self.net = PointModel(is_test=True)
|
|
|
36 |
_, _, Hc, Wc = descriptors.shape
|
37 |
|
38 |
# Scores & Descriptors
|
39 |
+
kpts_score = torch.cat([keypoints, scores], dim=1).view(3, -1).t()
|
40 |
+
descriptors = descriptors.view(256, Hc, Wc).view(256, -1).t()
|
|
|
|
|
|
|
|
|
41 |
|
42 |
# Filter based on confidence threshold
|
43 |
+
descriptors = descriptors[
|
44 |
+
kpts_score[:, 0] > self.conf["keypoint_threshold"], :
|
45 |
+
]
|
46 |
+
kpts_score = kpts_score[
|
47 |
+
kpts_score[:, 0] > self.conf["keypoint_threshold"], :
|
48 |
+
]
|
49 |
keypoints = kpts_score[:, 1:]
|
50 |
scores = kpts_score[:, 0]
|
51 |
|
hloc/extractors/netvlad.py
CHANGED
@@ -18,7 +18,9 @@ EPS = 1e-6
|
|
18 |
class NetVLADLayer(nn.Module):
|
19 |
def __init__(self, input_dim=512, K=64, score_bias=False, intranorm=True):
|
20 |
super().__init__()
|
21 |
-
self.score_proj = nn.Conv1d(
|
|
|
|
|
22 |
centers = nn.parameter.Parameter(torch.empty([input_dim, K]))
|
23 |
nn.init.xavier_uniform_(centers)
|
24 |
self.register_parameter("centers", centers)
|
@@ -54,7 +56,9 @@ class NetVLAD(BaseModel):
|
|
54 |
assert conf["model_name"] in self.dir_models.keys()
|
55 |
|
56 |
# Download the checkpoint.
|
57 |
-
checkpoint = Path(
|
|
|
|
|
58 |
if not checkpoint.exists():
|
59 |
checkpoint.parent.mkdir(exist_ok=True, parents=True)
|
60 |
link = self.dir_models[conf["model_name"]]
|
@@ -77,7 +81,9 @@ class NetVLAD(BaseModel):
|
|
77 |
mat = loadmat(checkpoint, struct_as_record=False, squeeze_me=True)
|
78 |
|
79 |
# CNN weights.
|
80 |
-
for layer, mat_layer in zip(
|
|
|
|
|
81 |
if isinstance(layer, nn.Conv2d):
|
82 |
w = mat_layer.weights[0] # Shape: S x S x IN x OUT
|
83 |
b = mat_layer.weights[1] # Shape: OUT
|
|
|
18 |
class NetVLADLayer(nn.Module):
|
19 |
def __init__(self, input_dim=512, K=64, score_bias=False, intranorm=True):
|
20 |
super().__init__()
|
21 |
+
self.score_proj = nn.Conv1d(
|
22 |
+
input_dim, K, kernel_size=1, bias=score_bias
|
23 |
+
)
|
24 |
centers = nn.parameter.Parameter(torch.empty([input_dim, K]))
|
25 |
nn.init.xavier_uniform_(centers)
|
26 |
self.register_parameter("centers", centers)
|
|
|
56 |
assert conf["model_name"] in self.dir_models.keys()
|
57 |
|
58 |
# Download the checkpoint.
|
59 |
+
checkpoint = Path(
|
60 |
+
torch.hub.get_dir(), "netvlad", conf["model_name"] + ".mat"
|
61 |
+
)
|
62 |
if not checkpoint.exists():
|
63 |
checkpoint.parent.mkdir(exist_ok=True, parents=True)
|
64 |
link = self.dir_models[conf["model_name"]]
|
|
|
81 |
mat = loadmat(checkpoint, struct_as_record=False, squeeze_me=True)
|
82 |
|
83 |
# CNN weights.
|
84 |
+
for layer, mat_layer in zip(
|
85 |
+
self.backbone.children(), mat["net"].layers
|
86 |
+
):
|
87 |
if isinstance(layer, nn.Conv2d):
|
88 |
w = mat_layer.weights[0] # Shape: S x S x IN x OUT
|
89 |
b = mat_layer.weights[1] # Shape: OUT
|
hloc/extractors/rekd.py
CHANGED
@@ -20,7 +20,9 @@ class REKD(BaseModel):
|
|
20 |
required_inputs = ["image"]
|
21 |
|
22 |
def _init(self, conf):
|
23 |
-
model_path =
|
|
|
|
|
24 |
if not model_path.exists():
|
25 |
print(f"No model found at {model_path}")
|
26 |
self.net = REKD_(is_test=True)
|
@@ -34,15 +36,29 @@ class REKD(BaseModel):
|
|
34 |
|
35 |
# Scores & Descriptors
|
36 |
kpts_score = (
|
37 |
-
torch.cat([keypoints, scores], dim=1)
|
|
|
|
|
|
|
|
|
|
|
38 |
)
|
39 |
descriptors = (
|
40 |
-
descriptors.view(256, Hc, Wc)
|
|
|
|
|
|
|
|
|
|
|
41 |
)
|
42 |
|
43 |
# Filter based on confidence threshold
|
44 |
-
descriptors = descriptors[
|
45 |
-
|
|
|
|
|
|
|
|
|
46 |
keypoints = kpts_score[:, 1:]
|
47 |
scores = kpts_score[:, 0]
|
48 |
|
|
|
20 |
required_inputs = ["image"]
|
21 |
|
22 |
def _init(self, conf):
|
23 |
+
model_path = (
|
24 |
+
rekd_path / "checkpoints" / f'PointModel_{conf["model_name"]}.pth'
|
25 |
+
)
|
26 |
if not model_path.exists():
|
27 |
print(f"No model found at {model_path}")
|
28 |
self.net = REKD_(is_test=True)
|
|
|
36 |
|
37 |
# Scores & Descriptors
|
38 |
kpts_score = (
|
39 |
+
torch.cat([keypoints, scores], dim=1)
|
40 |
+
.view(3, -1)
|
41 |
+
.t()
|
42 |
+
.cpu()
|
43 |
+
.detach()
|
44 |
+
.numpy()
|
45 |
)
|
46 |
descriptors = (
|
47 |
+
descriptors.view(256, Hc, Wc)
|
48 |
+
.view(256, -1)
|
49 |
+
.t()
|
50 |
+
.cpu()
|
51 |
+
.detach()
|
52 |
+
.numpy()
|
53 |
)
|
54 |
|
55 |
# Filter based on confidence threshold
|
56 |
+
descriptors = descriptors[
|
57 |
+
kpts_score[:, 0] > self.conf["keypoint_threshold"], :
|
58 |
+
]
|
59 |
+
kpts_score = kpts_score[
|
60 |
+
kpts_score[:, 0] > self.conf["keypoint_threshold"], :
|
61 |
+
]
|
62 |
keypoints = kpts_score[:, 1:]
|
63 |
scores = kpts_score[:, 0]
|
64 |
|
hloc/extractors/superpoint.py
CHANGED
@@ -16,7 +16,10 @@ def sample_descriptors_fix_sampling(keypoints, descriptors, s: int = 8):
|
|
16 |
keypoints = (keypoints + 0.5) / (keypoints.new_tensor([w, h]) * s)
|
17 |
keypoints = keypoints * 2 - 1 # normalize to (-1, 1)
|
18 |
descriptors = torch.nn.functional.grid_sample(
|
19 |
-
descriptors,
|
|
|
|
|
|
|
20 |
)
|
21 |
descriptors = torch.nn.functional.normalize(
|
22 |
descriptors.reshape(b, c, -1), p=2, dim=1
|
|
|
16 |
keypoints = (keypoints + 0.5) / (keypoints.new_tensor([w, h]) * s)
|
17 |
keypoints = keypoints * 2 - 1 # normalize to (-1, 1)
|
18 |
descriptors = torch.nn.functional.grid_sample(
|
19 |
+
descriptors,
|
20 |
+
keypoints.view(b, 1, -1, 2),
|
21 |
+
mode="bilinear",
|
22 |
+
align_corners=False,
|
23 |
)
|
24 |
descriptors = torch.nn.functional.normalize(
|
25 |
descriptors.reshape(b, c, -1), p=2, dim=1
|
hloc/match_dense.py
CHANGED
@@ -224,7 +224,10 @@ def match(model, path_0, path_1, conf):
|
|
224 |
image = torch.from_numpy(image / 255.0).float()
|
225 |
# assure that the size is divisible by dfactor
|
226 |
size_new = tuple(
|
227 |
-
map(
|
|
|
|
|
|
|
228 |
)
|
229 |
image = F.resize(image, size=size_new, antialias=True)
|
230 |
scale = np.array(size) / np.array(size_new)[::-1]
|
@@ -291,7 +294,10 @@ def match_images(model, image_0, image_1, conf, device="cpu"):
|
|
291 |
|
292 |
# assure that the size is divisible by dfactor
|
293 |
size_new = tuple(
|
294 |
-
map(
|
|
|
|
|
|
|
295 |
)
|
296 |
image = F.resize(image, size=size_new)
|
297 |
scale = np.array(size) / np.array(size_new)[::-1]
|
@@ -348,7 +354,7 @@ def match_images(model, image_0, image_1, conf, device="cpu"):
|
|
348 |
if "mconf" in pred.keys():
|
349 |
ret["mconf"] = pred["mconf"].cpu().numpy()
|
350 |
else:
|
351 |
-
ret["mconf"] = np.ones_like(kpts0.cpu().numpy()[:,0])
|
352 |
if "lines0" in pred.keys() and "lines1" in pred.keys():
|
353 |
if "keypoints0" in pred.keys() and "keypoints1" in pred.keys():
|
354 |
kpts0, kpts1 = pred["keypoints0"], pred["keypoints1"]
|
@@ -357,7 +363,10 @@ def match_images(model, image_0, image_1, conf, device="cpu"):
|
|
357 |
kpts0_origin = kpts0_origin.cpu().numpy()
|
358 |
kpts1_origin = kpts1_origin.cpu().numpy()
|
359 |
else:
|
360 |
-
kpts0_origin, kpts1_origin =
|
|
|
|
|
|
|
361 |
lines0, lines1 = pred["lines0"], pred["lines1"]
|
362 |
lines0_raw, lines1_raw = pred["raw_lines0"], pred["raw_lines1"]
|
363 |
|
|
|
224 |
image = torch.from_numpy(image / 255.0).float()
|
225 |
# assure that the size is divisible by dfactor
|
226 |
size_new = tuple(
|
227 |
+
map(
|
228 |
+
lambda x: int(x // conf.dfactor * conf.dfactor),
|
229 |
+
image.shape[-2:],
|
230 |
+
)
|
231 |
)
|
232 |
image = F.resize(image, size=size_new, antialias=True)
|
233 |
scale = np.array(size) / np.array(size_new)[::-1]
|
|
|
294 |
|
295 |
# assure that the size is divisible by dfactor
|
296 |
size_new = tuple(
|
297 |
+
map(
|
298 |
+
lambda x: int(x // conf.dfactor * conf.dfactor),
|
299 |
+
image.shape[-2:],
|
300 |
+
)
|
301 |
)
|
302 |
image = F.resize(image, size=size_new)
|
303 |
scale = np.array(size) / np.array(size_new)[::-1]
|
|
|
354 |
if "mconf" in pred.keys():
|
355 |
ret["mconf"] = pred["mconf"].cpu().numpy()
|
356 |
else:
|
357 |
+
ret["mconf"] = np.ones_like(kpts0.cpu().numpy()[:, 0])
|
358 |
if "lines0" in pred.keys() and "lines1" in pred.keys():
|
359 |
if "keypoints0" in pred.keys() and "keypoints1" in pred.keys():
|
360 |
kpts0, kpts1 = pred["keypoints0"], pred["keypoints1"]
|
|
|
363 |
kpts0_origin = kpts0_origin.cpu().numpy()
|
364 |
kpts1_origin = kpts1_origin.cpu().numpy()
|
365 |
else:
|
366 |
+
kpts0_origin, kpts1_origin = (
|
367 |
+
None,
|
368 |
+
None,
|
369 |
+
) # np.zeros([0]), np.zeros([0])
|
370 |
lines0, lines1 = pred["lines0"], pred["lines1"]
|
371 |
lines0_raw, lines1_raw = pred["raw_lines0"], pred["raw_lines1"]
|
372 |
|
hloc/match_features.py
CHANGED
@@ -151,7 +151,8 @@ class WorkQueue:
|
|
151 |
def __init__(self, work_fn, num_threads=1):
|
152 |
self.queue = Queue(num_threads)
|
153 |
self.threads = [
|
154 |
-
Thread(target=self.thread_fn, args=(work_fn,))
|
|
|
155 |
]
|
156 |
for thread in self.threads:
|
157 |
thread.start()
|
@@ -220,21 +221,24 @@ def main(
|
|
220 |
features_ref: Optional[Path] = None,
|
221 |
overwrite: bool = False,
|
222 |
) -> Path:
|
223 |
-
|
224 |
if isinstance(features, Path) or Path(features).exists():
|
225 |
features_q = features
|
226 |
if matches is None:
|
227 |
raise ValueError(
|
228 |
-
"Either provide both features and matches as Path"
|
|
|
229 |
)
|
230 |
else:
|
231 |
if export_dir is None:
|
232 |
raise ValueError(
|
233 |
-
"Provide an export_dir if features is not"
|
|
|
234 |
)
|
235 |
features_q = Path(export_dir, features + ".h5")
|
236 |
if matches is None:
|
237 |
-
matches = Path(
|
|
|
|
|
238 |
|
239 |
if features_ref is None:
|
240 |
features_ref = features_q
|
@@ -276,7 +280,8 @@ def match_from_paths(
|
|
276 |
overwrite: bool = False,
|
277 |
) -> Path:
|
278 |
logger.info(
|
279 |
-
"Matching local features with configuration:"
|
|
|
280 |
)
|
281 |
|
282 |
if not feature_path_q.exists():
|
@@ -330,12 +335,11 @@ def match_images(model, feat0, feat1):
|
|
330 |
desc0 = desc0.unsqueeze(0)
|
331 |
if len(desc1.shape) == 2:
|
332 |
desc1 = desc1.unsqueeze(0)
|
333 |
-
|
334 |
if isinstance(feat0["keypoints"], list):
|
335 |
feat0["keypoints"] = feat0["keypoints"][0][None]
|
336 |
if isinstance(feat1["keypoints"], list):
|
337 |
feat1["keypoints"] = feat1["keypoints"][0][None]
|
338 |
-
|
339 |
pred = model(
|
340 |
{
|
341 |
"image0": feat0["image"],
|
@@ -386,7 +390,9 @@ if __name__ == "__main__":
|
|
386 |
parser = argparse.ArgumentParser()
|
387 |
parser.add_argument("--pairs", type=Path, required=True)
|
388 |
parser.add_argument("--export_dir", type=Path)
|
389 |
-
parser.add_argument(
|
|
|
|
|
390 |
parser.add_argument("--matches", type=Path)
|
391 |
parser.add_argument(
|
392 |
"--conf", type=str, default="superglue", choices=list(confs.keys())
|
|
|
151 |
def __init__(self, work_fn, num_threads=1):
|
152 |
self.queue = Queue(num_threads)
|
153 |
self.threads = [
|
154 |
+
Thread(target=self.thread_fn, args=(work_fn,))
|
155 |
+
for _ in range(num_threads)
|
156 |
]
|
157 |
for thread in self.threads:
|
158 |
thread.start()
|
|
|
221 |
features_ref: Optional[Path] = None,
|
222 |
overwrite: bool = False,
|
223 |
) -> Path:
|
|
|
224 |
if isinstance(features, Path) or Path(features).exists():
|
225 |
features_q = features
|
226 |
if matches is None:
|
227 |
raise ValueError(
|
228 |
+
"Either provide both features and matches as Path"
|
229 |
+
" or both as names."
|
230 |
)
|
231 |
else:
|
232 |
if export_dir is None:
|
233 |
raise ValueError(
|
234 |
+
"Provide an export_dir if features is not"
|
235 |
+
f" a file path: {features}."
|
236 |
)
|
237 |
features_q = Path(export_dir, features + ".h5")
|
238 |
if matches is None:
|
239 |
+
matches = Path(
|
240 |
+
export_dir, f'{features}_{conf["output"]}_{pairs.stem}.h5'
|
241 |
+
)
|
242 |
|
243 |
if features_ref is None:
|
244 |
features_ref = features_q
|
|
|
280 |
overwrite: bool = False,
|
281 |
) -> Path:
|
282 |
logger.info(
|
283 |
+
"Matching local features with configuration:"
|
284 |
+
f"\n{pprint.pformat(conf)}"
|
285 |
)
|
286 |
|
287 |
if not feature_path_q.exists():
|
|
|
335 |
desc0 = desc0.unsqueeze(0)
|
336 |
if len(desc1.shape) == 2:
|
337 |
desc1 = desc1.unsqueeze(0)
|
|
|
338 |
if isinstance(feat0["keypoints"], list):
|
339 |
feat0["keypoints"] = feat0["keypoints"][0][None]
|
340 |
if isinstance(feat1["keypoints"], list):
|
341 |
feat1["keypoints"] = feat1["keypoints"][0][None]
|
342 |
+
|
343 |
pred = model(
|
344 |
{
|
345 |
"image0": feat0["image"],
|
|
|
390 |
parser = argparse.ArgumentParser()
|
391 |
parser.add_argument("--pairs", type=Path, required=True)
|
392 |
parser.add_argument("--export_dir", type=Path)
|
393 |
+
parser.add_argument(
|
394 |
+
"--features", type=str, default="feats-superpoint-n4096-r1024"
|
395 |
+
)
|
396 |
parser.add_argument("--matches", type=Path)
|
397 |
parser.add_argument(
|
398 |
"--conf", type=str, default="superglue", choices=list(confs.keys())
|
hloc/matchers/aspanformer.py
CHANGED
@@ -21,6 +21,7 @@ class ASpanFormer(BaseModel):
|
|
21 |
default_conf = {
|
22 |
"weights": "outdoor",
|
23 |
"match_threshold": 0.2,
|
|
|
24 |
"config_path": aspanformer_path / "configs/aspan/outdoor/aspan_test.py",
|
25 |
"model_name": "weights_aspanformer.tar",
|
26 |
}
|
@@ -31,24 +32,39 @@ class ASpanFormer(BaseModel):
|
|
31 |
}
|
32 |
|
33 |
def _init(self, conf):
|
34 |
-
model_path =
|
|
|
|
|
35 |
# Download the model.
|
36 |
if not model_path.exists():
|
37 |
# model_path.parent.mkdir(exist_ok=True)
|
38 |
tar_path = aspanformer_path / conf["model_name"]
|
39 |
if not tar_path.exists():
|
40 |
link = self.aspanformer_models[conf["model_name"]]
|
41 |
-
cmd = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
cmd_wo_proxy = ["gdown", link, "-O", str(tar_path)]
|
43 |
-
logger.info(
|
|
|
|
|
44 |
try:
|
45 |
subprocess.run(cmd_wo_proxy, check=True)
|
46 |
except subprocess.CalledProcessError as e:
|
47 |
-
logger.info(
|
|
|
|
|
48 |
try:
|
49 |
subprocess.run(cmd, check=True)
|
50 |
except subprocess.CalledProcessError as e:
|
51 |
-
logger.error(
|
|
|
|
|
52 |
raise e
|
53 |
|
54 |
do_system(f"cd {str(aspanformer_path)} & tar -xvf {str(tar_path)}")
|
@@ -58,9 +74,16 @@ class ASpanFormer(BaseModel):
|
|
58 |
config = get_cfg_defaults()
|
59 |
config.merge_from_file(conf["config_path"])
|
60 |
_config = lower_config(config)
|
|
|
|
|
|
|
|
|
|
|
61 |
self.net = _ASpanFormer(config=_config["aspan"])
|
62 |
weight_path = model_path
|
63 |
-
state_dict = torch.load(str(weight_path), map_location="cpu")[
|
|
|
|
|
64 |
self.net.load_state_dict(state_dict, strict=False)
|
65 |
|
66 |
def _forward(self, data):
|
|
|
21 |
default_conf = {
|
22 |
"weights": "outdoor",
|
23 |
"match_threshold": 0.2,
|
24 |
+
"sinkhorn_iterations": 20,
|
25 |
"config_path": aspanformer_path / "configs/aspan/outdoor/aspan_test.py",
|
26 |
"model_name": "weights_aspanformer.tar",
|
27 |
}
|
|
|
32 |
}
|
33 |
|
34 |
def _init(self, conf):
|
35 |
+
model_path = (
|
36 |
+
aspanformer_path / "weights" / Path(conf["weights"] + ".ckpt")
|
37 |
+
)
|
38 |
# Download the model.
|
39 |
if not model_path.exists():
|
40 |
# model_path.parent.mkdir(exist_ok=True)
|
41 |
tar_path = aspanformer_path / conf["model_name"]
|
42 |
if not tar_path.exists():
|
43 |
link = self.aspanformer_models[conf["model_name"]]
|
44 |
+
cmd = [
|
45 |
+
"gdown",
|
46 |
+
link,
|
47 |
+
"-O",
|
48 |
+
str(tar_path),
|
49 |
+
"--proxy",
|
50 |
+
self.proxy,
|
51 |
+
]
|
52 |
cmd_wo_proxy = ["gdown", link, "-O", str(tar_path)]
|
53 |
+
logger.info(
|
54 |
+
f"Downloading the Aspanformer model with `{cmd_wo_proxy}`."
|
55 |
+
)
|
56 |
try:
|
57 |
subprocess.run(cmd_wo_proxy, check=True)
|
58 |
except subprocess.CalledProcessError as e:
|
59 |
+
logger.info(
|
60 |
+
f"Downloading the Aspanformer model with `{cmd}`."
|
61 |
+
)
|
62 |
try:
|
63 |
subprocess.run(cmd, check=True)
|
64 |
except subprocess.CalledProcessError as e:
|
65 |
+
logger.error(
|
66 |
+
f"Failed to download the Aspanformer model."
|
67 |
+
)
|
68 |
raise e
|
69 |
|
70 |
do_system(f"cd {str(aspanformer_path)} & tar -xvf {str(tar_path)}")
|
|
|
74 |
config = get_cfg_defaults()
|
75 |
config.merge_from_file(conf["config_path"])
|
76 |
_config = lower_config(config)
|
77 |
+
|
78 |
+
# update: match threshold
|
79 |
+
_config["aspan"]["match_coarse"]["thr"] = conf["match_threshold"]
|
80 |
+
_config["aspan"]["match_coarse"]["skh_iters"] = conf["sinkhorn_iterations"]
|
81 |
+
|
82 |
self.net = _ASpanFormer(config=_config["aspan"])
|
83 |
weight_path = model_path
|
84 |
+
state_dict = torch.load(str(weight_path), map_location="cpu")[
|
85 |
+
"state_dict"
|
86 |
+
]
|
87 |
self.net.load_state_dict(state_dict, strict=False)
|
88 |
|
89 |
def _forward(self, data):
|
hloc/matchers/dkm.py
CHANGED
@@ -55,7 +55,9 @@ class DKMv3(BaseModel):
|
|
55 |
|
56 |
warp, certainty = self.net.match(img0, img1, device=device)
|
57 |
matches, certainty = self.net.sample(warp, certainty)
|
58 |
-
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
|
|
|
|
59 |
pred = {}
|
60 |
pred["keypoints0"], pred["keypoints1"] = kpts1, kpts2
|
61 |
return pred
|
|
|
55 |
|
56 |
warp, certainty = self.net.match(img0, img1, device=device)
|
57 |
matches, certainty = self.net.sample(warp, certainty)
|
58 |
+
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
59 |
+
matches, H_A, W_A, H_B, W_B
|
60 |
+
)
|
61 |
pred = {}
|
62 |
pred["keypoints0"], pred["keypoints1"] = kpts1, kpts2
|
63 |
return pred
|
hloc/matchers/dual_softmax.py
CHANGED
@@ -3,6 +3,7 @@ import torch
|
|
3 |
from ..utils.base_model import BaseModel
|
4 |
import numpy as np
|
5 |
|
|
|
6 |
# borrow from dedode
|
7 |
def dual_softmax_matcher(
|
8 |
desc_A: tuple["B", "C", "N"],
|
@@ -17,7 +18,9 @@ def dual_softmax_matcher(
|
|
17 |
if normalize:
|
18 |
desc_A = desc_A / desc_A.norm(dim=1, keepdim=True)
|
19 |
desc_B = desc_B / desc_B.norm(dim=1, keepdim=True)
|
20 |
-
sim =
|
|
|
|
|
21 |
P = sim.softmax(dim=-2) * sim.softmax(dim=-1)
|
22 |
mask = torch.nonzero(
|
23 |
(P == P.max(dim=-1, keepdim=True).values)
|
@@ -47,9 +50,14 @@ class DualSoftMax(BaseModel):
|
|
47 |
pass
|
48 |
|
49 |
def _forward(self, data):
|
50 |
-
if
|
|
|
|
|
|
|
51 |
matches0 = torch.full(
|
52 |
-
data["descriptors0"].shape[:2],
|
|
|
|
|
53 |
)
|
54 |
return {
|
55 |
"matches0": matches0,
|
|
|
3 |
from ..utils.base_model import BaseModel
|
4 |
import numpy as np
|
5 |
|
6 |
+
|
7 |
# borrow from dedode
|
8 |
def dual_softmax_matcher(
|
9 |
desc_A: tuple["B", "C", "N"],
|
|
|
18 |
if normalize:
|
19 |
desc_A = desc_A / desc_A.norm(dim=1, keepdim=True)
|
20 |
desc_B = desc_B / desc_B.norm(dim=1, keepdim=True)
|
21 |
+
sim = (
|
22 |
+
torch.einsum("b c n, b c m -> b n m", desc_A, desc_B) * inv_temperature
|
23 |
+
)
|
24 |
P = sim.softmax(dim=-2) * sim.softmax(dim=-1)
|
25 |
mask = torch.nonzero(
|
26 |
(P == P.max(dim=-1, keepdim=True).values)
|
|
|
50 |
pass
|
51 |
|
52 |
def _forward(self, data):
|
53 |
+
if (
|
54 |
+
data["descriptors0"].size(-1) == 0
|
55 |
+
or data["descriptors1"].size(-1) == 0
|
56 |
+
):
|
57 |
matches0 = torch.full(
|
58 |
+
data["descriptors0"].shape[:2],
|
59 |
+
-1,
|
60 |
+
device=data["descriptors0"].device,
|
61 |
)
|
62 |
return {
|
63 |
"matches0": matches0,
|
hloc/matchers/gluestick.py
CHANGED
@@ -33,9 +33,12 @@ class GlueStick(BaseModel):
|
|
33 |
gluestick_models = {
|
34 |
"checkpoint_GlueStick_MD.tar": "https://github.com/cvg/GlueStick/releases/download/v0.1_arxiv/checkpoint_GlueStick_MD.tar",
|
35 |
}
|
|
|
36 |
# Initialize the line matcher
|
37 |
def _init(self, conf):
|
38 |
-
model_path =
|
|
|
|
|
39 |
|
40 |
# Download the model.
|
41 |
if not model_path.exists():
|
|
|
33 |
gluestick_models = {
|
34 |
"checkpoint_GlueStick_MD.tar": "https://github.com/cvg/GlueStick/releases/download/v0.1_arxiv/checkpoint_GlueStick_MD.tar",
|
35 |
}
|
36 |
+
|
37 |
# Initialize the line matcher
|
38 |
def _init(self, conf):
|
39 |
+
model_path = (
|
40 |
+
gluestick_path / "resources" / "weights" / conf["model_name"]
|
41 |
+
)
|
42 |
|
43 |
# Download the model.
|
44 |
if not model_path.exists():
|
hloc/matchers/nearest_neighbor.py
CHANGED
@@ -36,24 +36,36 @@ class NearestNeighbor(BaseModel):
|
|
36 |
pass
|
37 |
|
38 |
def _forward(self, data):
|
39 |
-
if
|
|
|
|
|
|
|
40 |
matches0 = torch.full(
|
41 |
-
data["descriptors0"].shape[:2],
|
|
|
|
|
42 |
)
|
43 |
return {
|
44 |
"matches0": matches0,
|
45 |
"matching_scores0": torch.zeros_like(matches0),
|
46 |
}
|
47 |
ratio_threshold = self.conf["ratio_threshold"]
|
48 |
-
if
|
|
|
|
|
|
|
49 |
ratio_threshold = None
|
50 |
-
sim = torch.einsum(
|
|
|
|
|
51 |
matches0, scores0 = find_nn(
|
52 |
sim, ratio_threshold, self.conf["distance_threshold"]
|
53 |
)
|
54 |
if self.conf["do_mutual_check"]:
|
55 |
matches1, scores1 = find_nn(
|
56 |
-
sim.transpose(1, 2),
|
|
|
|
|
57 |
)
|
58 |
matches0 = mutual_check(matches0, matches1)
|
59 |
return {
|
|
|
36 |
pass
|
37 |
|
38 |
def _forward(self, data):
|
39 |
+
if (
|
40 |
+
data["descriptors0"].size(-1) == 0
|
41 |
+
or data["descriptors1"].size(-1) == 0
|
42 |
+
):
|
43 |
matches0 = torch.full(
|
44 |
+
data["descriptors0"].shape[:2],
|
45 |
+
-1,
|
46 |
+
device=data["descriptors0"].device,
|
47 |
)
|
48 |
return {
|
49 |
"matches0": matches0,
|
50 |
"matching_scores0": torch.zeros_like(matches0),
|
51 |
}
|
52 |
ratio_threshold = self.conf["ratio_threshold"]
|
53 |
+
if (
|
54 |
+
data["descriptors0"].size(-1) == 1
|
55 |
+
or data["descriptors1"].size(-1) == 1
|
56 |
+
):
|
57 |
ratio_threshold = None
|
58 |
+
sim = torch.einsum(
|
59 |
+
"bdn,bdm->bnm", data["descriptors0"], data["descriptors1"]
|
60 |
+
)
|
61 |
matches0, scores0 = find_nn(
|
62 |
sim, ratio_threshold, self.conf["distance_threshold"]
|
63 |
)
|
64 |
if self.conf["do_mutual_check"]:
|
65 |
matches1, scores1 = find_nn(
|
66 |
+
sim.transpose(1, 2),
|
67 |
+
ratio_threshold,
|
68 |
+
self.conf["distance_threshold"],
|
69 |
)
|
70 |
matches0 = mutual_check(matches0, matches1)
|
71 |
return {
|
hloc/matchers/roma.py
CHANGED
@@ -84,7 +84,9 @@ class Roma(BaseModel):
|
|
84 |
matches, certainty = self.net.sample(
|
85 |
warp, certainty, num=self.conf["max_keypoints"]
|
86 |
)
|
87 |
-
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
|
|
|
|
88 |
pred = {}
|
89 |
pred["keypoints0"], pred["keypoints1"] = kpts1, kpts2
|
90 |
pred["mconf"] = certainty
|
|
|
84 |
matches, certainty = self.net.sample(
|
85 |
warp, certainty, num=self.conf["max_keypoints"]
|
86 |
)
|
87 |
+
kpts1, kpts2 = self.net.to_pixel_coordinates(
|
88 |
+
matches, H_A, W_A, H_B, W_B
|
89 |
+
)
|
90 |
pred = {}
|
91 |
pred["keypoints0"], pred["keypoints1"] = kpts1, kpts2
|
92 |
pred["mconf"] = certainty
|
hloc/matchers/sgmnet.py
CHANGED
@@ -52,9 +52,18 @@ class SGMNet(BaseModel):
|
|
52 |
# Download the model.
|
53 |
if not sgmnet_weights.exists():
|
54 |
if not tar_path.exists():
|
55 |
-
cmd = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
cmd_wo_proxy = ["gdown", link, "-O", str(tar_path)]
|
57 |
-
logger.info(
|
|
|
|
|
58 |
try:
|
59 |
subprocess.run(cmd_wo_proxy, check=True)
|
60 |
except subprocess.CalledProcessError as e:
|
@@ -73,7 +82,10 @@ class SGMNet(BaseModel):
|
|
73 |
self.net = SGM_Model(config)
|
74 |
checkpoint = torch.load(sgmnet_weights, map_location="cpu")
|
75 |
# for ddp model
|
76 |
-
if
|
|
|
|
|
|
|
77 |
new_stat_dict = OrderedDict()
|
78 |
for key, value in checkpoint["state_dict"].items():
|
79 |
new_stat_dict[key[7:]] = value
|
|
|
52 |
# Download the model.
|
53 |
if not sgmnet_weights.exists():
|
54 |
if not tar_path.exists():
|
55 |
+
cmd = [
|
56 |
+
"gdown",
|
57 |
+
link,
|
58 |
+
"-O",
|
59 |
+
str(tar_path),
|
60 |
+
"--proxy",
|
61 |
+
self.proxy,
|
62 |
+
]
|
63 |
cmd_wo_proxy = ["gdown", link, "-O", str(tar_path)]
|
64 |
+
logger.info(
|
65 |
+
f"Downloading the SGMNet model with `{cmd_wo_proxy}`."
|
66 |
+
)
|
67 |
try:
|
68 |
subprocess.run(cmd_wo_proxy, check=True)
|
69 |
except subprocess.CalledProcessError as e:
|
|
|
82 |
self.net = SGM_Model(config)
|
83 |
checkpoint = torch.load(sgmnet_weights, map_location="cpu")
|
84 |
# for ddp model
|
85 |
+
if (
|
86 |
+
list(checkpoint["state_dict"].items())[0][0].split(".")[0]
|
87 |
+
== "module"
|
88 |
+
):
|
89 |
new_stat_dict = OrderedDict()
|
90 |
for key, value in checkpoint["state_dict"].items():
|
91 |
new_stat_dict[key[7:]] = value
|
hloc/matchers/sold2.py
CHANGED
@@ -35,6 +35,7 @@ class SOLD2(BaseModel):
|
|
35 |
"image0",
|
36 |
"image1",
|
37 |
]
|
|
|
38 |
# Initialize the line matcher
|
39 |
def _init(self, conf):
|
40 |
checkpoint_path = conf["checkpoint_dir"] / conf["weights"]
|
|
|
35 |
"image0",
|
36 |
"image1",
|
37 |
]
|
38 |
+
|
39 |
# Initialize the line matcher
|
40 |
def _init(self, conf):
|
41 |
checkpoint_path = conf["checkpoint_dir"] / conf["weights"]
|
hloc/pipelines/4Seasons/localize.py
CHANGED
@@ -67,7 +67,9 @@ delete_unused_images(seq_images, timestamps)
|
|
67 |
generate_query_lists(timestamps, seq_dir, query_list)
|
68 |
|
69 |
# Generate the localization pairs from the given reference frames.
|
70 |
-
generate_localization_pairs(
|
|
|
|
|
71 |
|
72 |
# Extract, match, amd localize.
|
73 |
ffile = extract_features.main(fconf, seq_images, output_dir)
|
|
|
67 |
generate_query_lists(timestamps, seq_dir, query_list)
|
68 |
|
69 |
# Generate the localization pairs from the given reference frames.
|
70 |
+
generate_localization_pairs(
|
71 |
+
sequence, reloc, num_loc_pairs, ref_pairs, loc_pairs
|
72 |
+
)
|
73 |
|
74 |
# Extract, match, amd localize.
|
75 |
ffile = extract_features.main(fconf, seq_images, output_dir)
|
hloc/pipelines/4Seasons/utils.py
CHANGED
@@ -48,7 +48,11 @@ def camera_from_calibration_file(id_, path):
|
|
48 |
model_name = "PINHOLE"
|
49 |
params = [float(i) for i in [fx, fy, cx, cy]]
|
50 |
camera = Camera(
|
51 |
-
id=id_,
|
|
|
|
|
|
|
|
|
52 |
)
|
53 |
return camera
|
54 |
|
@@ -149,7 +153,9 @@ def generate_localization_pairs(sequence, reloc, num, ref_pairs, out_path):
|
|
149 |
"""
|
150 |
if "test" in sequence:
|
151 |
# hard pairs will be overwritten by easy ones if available
|
152 |
-
relocs = [
|
|
|
|
|
153 |
else:
|
154 |
relocs = [reloc]
|
155 |
query_to_ref_ts = {}
|
@@ -207,8 +213,12 @@ def evaluate_submission(submission_dir, relocs, ths=[0.1, 0.2, 0.5]):
|
|
207 |
"""Compute the relocalization recall from predicted and ground truth poses."""
|
208 |
for reloc in relocs.parent.glob(relocs.name):
|
209 |
poses_gt = parse_relocalization(reloc, has_poses=True)
|
210 |
-
poses_pred = parse_relocalization(
|
211 |
-
|
|
|
|
|
|
|
|
|
212 |
|
213 |
error = []
|
214 |
for ref_ts, q_ts, R_gt, t_gt in poses_gt:
|
|
|
48 |
model_name = "PINHOLE"
|
49 |
params = [float(i) for i in [fx, fy, cx, cy]]
|
50 |
camera = Camera(
|
51 |
+
id=id_,
|
52 |
+
model=model_name,
|
53 |
+
width=int(width),
|
54 |
+
height=int(height),
|
55 |
+
params=params,
|
56 |
)
|
57 |
return camera
|
58 |
|
|
|
153 |
"""
|
154 |
if "test" in sequence:
|
155 |
# hard pairs will be overwritten by easy ones if available
|
156 |
+
relocs = [
|
157 |
+
str(reloc).replace("*", d) for d in ["hard", "moderate", "easy"]
|
158 |
+
]
|
159 |
else:
|
160 |
relocs = [reloc]
|
161 |
query_to_ref_ts = {}
|
|
|
213 |
"""Compute the relocalization recall from predicted and ground truth poses."""
|
214 |
for reloc in relocs.parent.glob(relocs.name):
|
215 |
poses_gt = parse_relocalization(reloc, has_poses=True)
|
216 |
+
poses_pred = parse_relocalization(
|
217 |
+
submission_dir / reloc.name, has_poses=True
|
218 |
+
)
|
219 |
+
poses_pred = {
|
220 |
+
(ref_ts, q_ts): (R, t) for ref_ts, q_ts, R, t in poses_pred
|
221 |
+
}
|
222 |
|
223 |
error = []
|
224 |
for ref_ts, q_ts, R_gt, t_gt in poses_gt:
|
hloc/pipelines/7Scenes/create_gt_sfm.py
CHANGED
@@ -28,7 +28,9 @@ def interpolate_depth(depth, kp):
|
|
28 |
|
29 |
# To maximize the number of points that have depth:
|
30 |
# do bilinear interpolation first and then nearest for the remaining points
|
31 |
-
interp_lin = grid_sample(depth, kp, align_corners=True, mode="bilinear")[
|
|
|
|
|
32 |
interp_nn = torch.nn.functional.grid_sample(
|
33 |
depth, kp, align_corners=True, mode="nearest"
|
34 |
)[0, :, 0]
|
@@ -127,7 +129,15 @@ if __name__ == "__main__":
|
|
127 |
dataset = Path("datasets/7scenes")
|
128 |
outputs = Path("outputs/7Scenes")
|
129 |
|
130 |
-
SCENES = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
for scene in SCENES:
|
132 |
sfm_path = outputs / scene / "sfm_superpoint+superglue"
|
133 |
depth_path = dataset / f"depth/7scenes_{scene}/train/depth"
|
|
|
28 |
|
29 |
# To maximize the number of points that have depth:
|
30 |
# do bilinear interpolation first and then nearest for the remaining points
|
31 |
+
interp_lin = grid_sample(depth, kp, align_corners=True, mode="bilinear")[
|
32 |
+
0, :, 0
|
33 |
+
]
|
34 |
interp_nn = torch.nn.functional.grid_sample(
|
35 |
depth, kp, align_corners=True, mode="nearest"
|
36 |
)[0, :, 0]
|
|
|
129 |
dataset = Path("datasets/7scenes")
|
130 |
outputs = Path("outputs/7Scenes")
|
131 |
|
132 |
+
SCENES = [
|
133 |
+
"chess",
|
134 |
+
"fire",
|
135 |
+
"heads",
|
136 |
+
"office",
|
137 |
+
"pumpkin",
|
138 |
+
"redkitchen",
|
139 |
+
"stairs",
|
140 |
+
]
|
141 |
for scene in SCENES:
|
142 |
sfm_path = outputs / scene / "sfm_superpoint+superglue"
|
143 |
depth_path = dataset / f"depth/7scenes_{scene}/train/depth"
|
hloc/pipelines/7Scenes/pipeline.py
CHANGED
@@ -45,7 +45,9 @@ def run_scene(
|
|
45 |
create_reference_sfm(gt_dir, ref_sfm_sift, test_list)
|
46 |
create_query_list_with_intrinsics(gt_dir, query_list, test_list)
|
47 |
|
48 |
-
features = extract_features.main(
|
|
|
|
|
49 |
|
50 |
sfm_pairs = outputs / f"pairs-db-covis{num_covis}.txt"
|
51 |
pairs_from_covisibility.main(ref_sfm_sift, sfm_pairs, num_matched=num_covis)
|
@@ -112,7 +114,9 @@ if __name__ == "__main__":
|
|
112 |
results = (
|
113 |
args.outputs
|
114 |
/ scene
|
115 |
-
/ "results_{}.txt".format(
|
|
|
|
|
116 |
)
|
117 |
if args.overwrite or not results.exists():
|
118 |
run_scene(
|
|
|
45 |
create_reference_sfm(gt_dir, ref_sfm_sift, test_list)
|
46 |
create_query_list_with_intrinsics(gt_dir, query_list, test_list)
|
47 |
|
48 |
+
features = extract_features.main(
|
49 |
+
feature_conf, images, outputs, as_half=True
|
50 |
+
)
|
51 |
|
52 |
sfm_pairs = outputs / f"pairs-db-covis{num_covis}.txt"
|
53 |
pairs_from_covisibility.main(ref_sfm_sift, sfm_pairs, num_matched=num_covis)
|
|
|
114 |
results = (
|
115 |
args.outputs
|
116 |
/ scene
|
117 |
+
/ "results_{}.txt".format(
|
118 |
+
"dense" if args.use_dense_depth else "sparse"
|
119 |
+
)
|
120 |
)
|
121 |
if args.overwrite or not results.exists():
|
122 |
run_scene(
|
hloc/pipelines/Aachen/pipeline.py
CHANGED
@@ -40,14 +40,18 @@ images = dataset / "images/images_upright/"
|
|
40 |
|
41 |
outputs = args.outputs # where everything will be saved
|
42 |
sift_sfm = outputs / "sfm_sift" # from which we extract the reference poses
|
43 |
-
reference_sfm =
|
|
|
|
|
44 |
sfm_pairs = (
|
45 |
outputs / f"pairs-db-covis{args.num_covis}.txt"
|
46 |
) # top-k most covisible in SIFT model
|
47 |
loc_pairs = (
|
48 |
outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
49 |
) # top-k retrieved by NetVLAD
|
50 |
-
results =
|
|
|
|
|
51 |
|
52 |
# list the standard configurations available
|
53 |
print(f"Configs for feature extractors:\n{pformat(extract_features.confs)}")
|
@@ -71,7 +75,9 @@ sfm_matches = match_features.main(
|
|
71 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
72 |
)
|
73 |
|
74 |
-
triangulation.main(
|
|
|
|
|
75 |
|
76 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
77 |
pairs_from_retrieval.main(
|
|
|
40 |
|
41 |
outputs = args.outputs # where everything will be saved
|
42 |
sift_sfm = outputs / "sfm_sift" # from which we extract the reference poses
|
43 |
+
reference_sfm = (
|
44 |
+
outputs / "sfm_superpoint+superglue"
|
45 |
+
) # the SfM model we will build
|
46 |
sfm_pairs = (
|
47 |
outputs / f"pairs-db-covis{args.num_covis}.txt"
|
48 |
) # top-k most covisible in SIFT model
|
49 |
loc_pairs = (
|
50 |
outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
51 |
) # top-k retrieved by NetVLAD
|
52 |
+
results = (
|
53 |
+
outputs / f"Aachen_hloc_superpoint+superglue_netvlad{args.num_loc}.txt"
|
54 |
+
)
|
55 |
|
56 |
# list the standard configurations available
|
57 |
print(f"Configs for feature extractors:\n{pformat(extract_features.confs)}")
|
|
|
75 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
76 |
)
|
77 |
|
78 |
+
triangulation.main(
|
79 |
+
reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches
|
80 |
+
)
|
81 |
|
82 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
83 |
pairs_from_retrieval.main(
|
hloc/pipelines/Aachen_v1_1/pipeline.py
CHANGED
@@ -39,14 +39,18 @@ images = dataset / "images/images_upright/"
|
|
39 |
sift_sfm = dataset / "3D-models/aachen_v_1_1"
|
40 |
|
41 |
outputs = args.outputs # where everything will be saved
|
42 |
-
reference_sfm =
|
|
|
|
|
43 |
sfm_pairs = (
|
44 |
outputs / f"pairs-db-covis{args.num_covis}.txt"
|
45 |
) # top-k most covisible in SIFT model
|
46 |
loc_pairs = (
|
47 |
outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
48 |
) # top-k retrieved by NetVLAD
|
49 |
-
results =
|
|
|
|
|
50 |
|
51 |
# list the standard configurations available
|
52 |
print(f"Configs for feature extractors:\n{pformat(extract_features.confs)}")
|
@@ -64,7 +68,9 @@ sfm_matches = match_features.main(
|
|
64 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
65 |
)
|
66 |
|
67 |
-
triangulation.main(
|
|
|
|
|
68 |
|
69 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
70 |
pairs_from_retrieval.main(
|
|
|
39 |
sift_sfm = dataset / "3D-models/aachen_v_1_1"
|
40 |
|
41 |
outputs = args.outputs # where everything will be saved
|
42 |
+
reference_sfm = (
|
43 |
+
outputs / "sfm_superpoint+superglue"
|
44 |
+
) # the SfM model we will build
|
45 |
sfm_pairs = (
|
46 |
outputs / f"pairs-db-covis{args.num_covis}.txt"
|
47 |
) # top-k most covisible in SIFT model
|
48 |
loc_pairs = (
|
49 |
outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
50 |
) # top-k retrieved by NetVLAD
|
51 |
+
results = (
|
52 |
+
outputs / f"Aachen-v1.1_hloc_superpoint+superglue_netvlad{args.num_loc}.txt"
|
53 |
+
)
|
54 |
|
55 |
# list the standard configurations available
|
56 |
print(f"Configs for feature extractors:\n{pformat(extract_features.confs)}")
|
|
|
68 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
69 |
)
|
70 |
|
71 |
+
triangulation.main(
|
72 |
+
reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches
|
73 |
+
)
|
74 |
|
75 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
76 |
pairs_from_retrieval.main(
|
hloc/pipelines/Aachen_v1_1/pipeline_loftr.py
CHANGED
@@ -61,7 +61,9 @@ features, sfm_matches = match_dense.main(
|
|
61 |
matcher_conf, sfm_pairs, images, outputs, max_kps=8192, overwrite=False
|
62 |
)
|
63 |
|
64 |
-
triangulation.main(
|
|
|
|
|
65 |
|
66 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
67 |
pairs_from_retrieval.main(
|
|
|
61 |
matcher_conf, sfm_pairs, images, outputs, max_kps=8192, overwrite=False
|
62 |
)
|
63 |
|
64 |
+
triangulation.main(
|
65 |
+
reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches
|
66 |
+
)
|
67 |
|
68 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
69 |
pairs_from_retrieval.main(
|
hloc/pipelines/CMU/pipeline.py
CHANGED
@@ -46,20 +46,34 @@ def run_slice(slice_, root, outputs, num_covis, num_loc):
|
|
46 |
matcher_conf = match_features.confs["superglue"]
|
47 |
|
48 |
pairs_from_covisibility.main(sift_sfm, sfm_pairs, num_matched=num_covis)
|
49 |
-
features = extract_features.main(
|
|
|
|
|
50 |
sfm_matches = match_features.main(
|
51 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
52 |
)
|
53 |
-
triangulation.main(
|
|
|
|
|
54 |
|
55 |
generate_query_list(root, query_list, slice_)
|
56 |
-
global_descriptors = extract_features.main(
|
57 |
-
|
|
|
|
|
|
|
|
|
58 |
pairs_from_retrieval.main(
|
59 |
-
global_descriptors,
|
|
|
|
|
|
|
|
|
60 |
)
|
61 |
|
62 |
-
features = extract_features.main(
|
|
|
|
|
63 |
loc_matches = match_features.main(
|
64 |
matcher_conf, loc_pairs, feature_conf["output"], outputs
|
65 |
)
|
@@ -122,5 +136,9 @@ if __name__ == "__main__":
|
|
122 |
for slice_ in slices:
|
123 |
logger.info("Working on slice %s.", slice_)
|
124 |
run_slice(
|
125 |
-
f"slice{slice_}",
|
|
|
|
|
|
|
|
|
126 |
)
|
|
|
46 |
matcher_conf = match_features.confs["superglue"]
|
47 |
|
48 |
pairs_from_covisibility.main(sift_sfm, sfm_pairs, num_matched=num_covis)
|
49 |
+
features = extract_features.main(
|
50 |
+
feature_conf, ref_images, outputs, as_half=True
|
51 |
+
)
|
52 |
sfm_matches = match_features.main(
|
53 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
54 |
)
|
55 |
+
triangulation.main(
|
56 |
+
ref_sfm, sift_sfm, ref_images, sfm_pairs, features, sfm_matches
|
57 |
+
)
|
58 |
|
59 |
generate_query_list(root, query_list, slice_)
|
60 |
+
global_descriptors = extract_features.main(
|
61 |
+
retrieval_conf, ref_images, outputs
|
62 |
+
)
|
63 |
+
global_descriptors = extract_features.main(
|
64 |
+
retrieval_conf, query_images, outputs
|
65 |
+
)
|
66 |
pairs_from_retrieval.main(
|
67 |
+
global_descriptors,
|
68 |
+
loc_pairs,
|
69 |
+
num_loc,
|
70 |
+
query_list=query_list,
|
71 |
+
db_model=ref_sfm,
|
72 |
)
|
73 |
|
74 |
+
features = extract_features.main(
|
75 |
+
feature_conf, query_images, outputs, as_half=True
|
76 |
+
)
|
77 |
loc_matches = match_features.main(
|
78 |
matcher_conf, loc_pairs, feature_conf["output"], outputs
|
79 |
)
|
|
|
136 |
for slice_ in slices:
|
137 |
logger.info("Working on slice %s.", slice_)
|
138 |
run_slice(
|
139 |
+
f"slice{slice_}",
|
140 |
+
args.dataset,
|
141 |
+
args.outputs,
|
142 |
+
args.num_covis,
|
143 |
+
args.num_loc,
|
144 |
)
|
hloc/pipelines/Cambridge/pipeline.py
CHANGED
@@ -5,7 +5,13 @@ from .utils import create_query_list_with_intrinsics, scale_sfm_images, evaluate
|
|
5 |
from ... import extract_features, match_features, pairs_from_covisibility
|
6 |
from ... import triangulation, localize_sfm, pairs_from_retrieval, logger
|
7 |
|
8 |
-
SCENES = [
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
|
11 |
def run_scene(images, gt_dir, outputs, results, num_covis, num_loc):
|
@@ -35,7 +41,11 @@ def run_scene(images, gt_dir, outputs, results, num_covis, num_loc):
|
|
35 |
retrieval_conf = extract_features.confs["netvlad"]
|
36 |
|
37 |
create_query_list_with_intrinsics(
|
38 |
-
gt_dir / "empty_all",
|
|
|
|
|
|
|
|
|
39 |
)
|
40 |
with open(test_list, "r") as f:
|
41 |
query_seqs = {q.split("/")[0] for q in f.read().rstrip().split("\n")}
|
@@ -49,7 +59,9 @@ def run_scene(images, gt_dir, outputs, results, num_covis, num_loc):
|
|
49 |
query_prefix=query_seqs,
|
50 |
)
|
51 |
|
52 |
-
features = extract_features.main(
|
|
|
|
|
53 |
pairs_from_covisibility.main(ref_sfm_sift, sfm_pairs, num_matched=num_covis)
|
54 |
sfm_matches = match_features.main(
|
55 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
|
|
5 |
from ... import extract_features, match_features, pairs_from_covisibility
|
6 |
from ... import triangulation, localize_sfm, pairs_from_retrieval, logger
|
7 |
|
8 |
+
SCENES = [
|
9 |
+
"KingsCollege",
|
10 |
+
"OldHospital",
|
11 |
+
"ShopFacade",
|
12 |
+
"StMarysChurch",
|
13 |
+
"GreatCourt",
|
14 |
+
]
|
15 |
|
16 |
|
17 |
def run_scene(images, gt_dir, outputs, results, num_covis, num_loc):
|
|
|
41 |
retrieval_conf = extract_features.confs["netvlad"]
|
42 |
|
43 |
create_query_list_with_intrinsics(
|
44 |
+
gt_dir / "empty_all",
|
45 |
+
query_list,
|
46 |
+
test_list,
|
47 |
+
ext=".txt",
|
48 |
+
image_dir=images,
|
49 |
)
|
50 |
with open(test_list, "r") as f:
|
51 |
query_seqs = {q.split("/")[0] for q in f.read().rstrip().split("\n")}
|
|
|
59 |
query_prefix=query_seqs,
|
60 |
)
|
61 |
|
62 |
+
features = extract_features.main(
|
63 |
+
feature_conf, images, outputs, as_half=True
|
64 |
+
)
|
65 |
pairs_from_covisibility.main(ref_sfm_sift, sfm_pairs, num_matched=num_covis)
|
66 |
sfm_matches = match_features.main(
|
67 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
hloc/pipelines/Cambridge/utils.py
CHANGED
@@ -42,7 +42,9 @@ def scale_sfm_images(full_model, scaled_model, image_dir):
|
|
42 |
sy = h / camera.height
|
43 |
assert sx == sy, (sx, sy)
|
44 |
scaled_cameras[cam_id] = camera._replace(
|
45 |
-
width=w,
|
|
|
|
|
46 |
)
|
47 |
|
48 |
write_model(scaled_cameras, images, points3D, scaled_model)
|
|
|
42 |
sy = h / camera.height
|
43 |
assert sx == sy, (sx, sy)
|
44 |
scaled_cameras[cam_id] = camera._replace(
|
45 |
+
width=w,
|
46 |
+
height=h,
|
47 |
+
params=camera.params * np.array([sx, sx, sy, 1.0]),
|
48 |
)
|
49 |
|
50 |
write_model(scaled_cameras, images, points3D, scaled_model)
|
hloc/pipelines/RobotCar/colmap_from_nvm.py
CHANGED
@@ -16,11 +16,14 @@ from ...utils.read_write_model import write_model
|
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
18 |
|
19 |
-
def read_nvm_model(
|
20 |
-
|
|
|
21 |
# Extract the intrinsics from the db file instead of the NVM model
|
22 |
db = sqlite3.connect(str(database_path))
|
23 |
-
ret = db.execute(
|
|
|
|
|
24 |
cameras = {}
|
25 |
for camera_id, camera_model, width, height, params in ret:
|
26 |
params = np.fromstring(params, dtype=np.double).reshape(-1)
|
|
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
18 |
|
19 |
+
def read_nvm_model(
|
20 |
+
nvm_path, database_path, image_ids, camera_ids, skip_points=False
|
21 |
+
):
|
22 |
# Extract the intrinsics from the db file instead of the NVM model
|
23 |
db = sqlite3.connect(str(database_path))
|
24 |
+
ret = db.execute(
|
25 |
+
"SELECT camera_id, model, width, height, params FROM cameras;"
|
26 |
+
)
|
27 |
cameras = {}
|
28 |
for camera_id, camera_model, width, height, params in ret:
|
29 |
params = np.fromstring(params, dtype=np.double).reshape(-1)
|
hloc/pipelines/RobotCar/pipeline.py
CHANGED
@@ -79,7 +79,9 @@ sift_sfm = outputs / "sfm_sift"
|
|
79 |
reference_sfm = outputs / "sfm_superpoint+superglue"
|
80 |
sfm_pairs = outputs / f"pairs-db-covis{args.num_covis}.txt"
|
81 |
loc_pairs = outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
82 |
-
results =
|
|
|
|
|
83 |
|
84 |
# pick one of the configurations for extraction and matching
|
85 |
retrieval_conf = extract_features.confs["netvlad"]
|
@@ -103,7 +105,9 @@ sfm_matches = match_features.main(
|
|
103 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
104 |
)
|
105 |
|
106 |
-
triangulation.main(
|
|
|
|
|
107 |
|
108 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
109 |
# TODO: do per location and per camera
|
|
|
79 |
reference_sfm = outputs / "sfm_superpoint+superglue"
|
80 |
sfm_pairs = outputs / f"pairs-db-covis{args.num_covis}.txt"
|
81 |
loc_pairs = outputs / f"pairs-query-netvlad{args.num_loc}.txt"
|
82 |
+
results = (
|
83 |
+
outputs / f"RobotCar_hloc_superpoint+superglue_netvlad{args.num_loc}.txt"
|
84 |
+
)
|
85 |
|
86 |
# pick one of the configurations for extraction and matching
|
87 |
retrieval_conf = extract_features.confs["netvlad"]
|
|
|
105 |
matcher_conf, sfm_pairs, feature_conf["output"], outputs
|
106 |
)
|
107 |
|
108 |
+
triangulation.main(
|
109 |
+
reference_sfm, sift_sfm, images, sfm_pairs, features, sfm_matches
|
110 |
+
)
|
111 |
|
112 |
global_descriptors = extract_features.main(retrieval_conf, images, outputs)
|
113 |
# TODO: do per location and per camera
|
hloc/utils/database.py
CHANGED
@@ -100,7 +100,9 @@ CREATE_MATCHES_TABLE = """CREATE TABLE IF NOT EXISTS matches (
|
|
100 |
cols INTEGER NOT NULL,
|
101 |
data BLOB)"""
|
102 |
|
103 |
-
CREATE_NAME_INDEX =
|
|
|
|
|
104 |
|
105 |
CREATE_ALL = "; ".join(
|
106 |
[
|
@@ -150,20 +152,34 @@ class COLMAPDatabase(sqlite3.Connection):
|
|
150 |
super(COLMAPDatabase, self).__init__(*args, **kwargs)
|
151 |
|
152 |
self.create_tables = lambda: self.executescript(CREATE_ALL)
|
153 |
-
self.create_cameras_table = lambda: self.executescript(
|
|
|
|
|
154 |
self.create_descriptors_table = lambda: self.executescript(
|
155 |
CREATE_DESCRIPTORS_TABLE
|
156 |
)
|
157 |
-
self.create_images_table = lambda: self.executescript(
|
|
|
|
|
158 |
self.create_two_view_geometries_table = lambda: self.executescript(
|
159 |
CREATE_TWO_VIEW_GEOMETRIES_TABLE
|
160 |
)
|
161 |
-
self.create_keypoints_table = lambda: self.executescript(
|
162 |
-
|
|
|
|
|
|
|
|
|
163 |
self.create_name_index = lambda: self.executescript(CREATE_NAME_INDEX)
|
164 |
|
165 |
def add_camera(
|
166 |
-
self,
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
):
|
168 |
params = np.asarray(params, np.float64)
|
169 |
cursor = self.execute(
|
@@ -298,7 +314,12 @@ def example_usage():
|
|
298 |
|
299 |
# Create dummy cameras.
|
300 |
|
301 |
-
model1, width1, height1, params1 =
|
|
|
|
|
|
|
|
|
|
|
302 |
model2, width2, height2, params2 = (
|
303 |
2,
|
304 |
1024,
|
|
|
100 |
cols INTEGER NOT NULL,
|
101 |
data BLOB)"""
|
102 |
|
103 |
+
CREATE_NAME_INDEX = (
|
104 |
+
"CREATE UNIQUE INDEX IF NOT EXISTS index_name ON images(name)"
|
105 |
+
)
|
106 |
|
107 |
CREATE_ALL = "; ".join(
|
108 |
[
|
|
|
152 |
super(COLMAPDatabase, self).__init__(*args, **kwargs)
|
153 |
|
154 |
self.create_tables = lambda: self.executescript(CREATE_ALL)
|
155 |
+
self.create_cameras_table = lambda: self.executescript(
|
156 |
+
CREATE_CAMERAS_TABLE
|
157 |
+
)
|
158 |
self.create_descriptors_table = lambda: self.executescript(
|
159 |
CREATE_DESCRIPTORS_TABLE
|
160 |
)
|
161 |
+
self.create_images_table = lambda: self.executescript(
|
162 |
+
CREATE_IMAGES_TABLE
|
163 |
+
)
|
164 |
self.create_two_view_geometries_table = lambda: self.executescript(
|
165 |
CREATE_TWO_VIEW_GEOMETRIES_TABLE
|
166 |
)
|
167 |
+
self.create_keypoints_table = lambda: self.executescript(
|
168 |
+
CREATE_KEYPOINTS_TABLE
|
169 |
+
)
|
170 |
+
self.create_matches_table = lambda: self.executescript(
|
171 |
+
CREATE_MATCHES_TABLE
|
172 |
+
)
|
173 |
self.create_name_index = lambda: self.executescript(CREATE_NAME_INDEX)
|
174 |
|
175 |
def add_camera(
|
176 |
+
self,
|
177 |
+
model,
|
178 |
+
width,
|
179 |
+
height,
|
180 |
+
params,
|
181 |
+
prior_focal_length=False,
|
182 |
+
camera_id=None,
|
183 |
):
|
184 |
params = np.asarray(params, np.float64)
|
185 |
cursor = self.execute(
|
|
|
314 |
|
315 |
# Create dummy cameras.
|
316 |
|
317 |
+
model1, width1, height1, params1 = (
|
318 |
+
0,
|
319 |
+
1024,
|
320 |
+
768,
|
321 |
+
np.array((1024.0, 512.0, 384.0)),
|
322 |
+
)
|
323 |
model2, width2, height2, params2 = (
|
324 |
2,
|
325 |
1024,
|
hloc/utils/geometry.py
CHANGED
@@ -16,12 +16,12 @@ def compute_epipolar_errors(qvec_r2t, tvec_r2t, p2d_r, p2d_t):
|
|
16 |
E = vector_to_cross_product_matrix(T_r2t[:3, -1]) @ T_r2t[:3, :3]
|
17 |
l2d_r2t = (E @ to_homogeneous(p2d_r).T).T
|
18 |
l2d_t2r = (E.T @ to_homogeneous(p2d_t).T).T
|
19 |
-
errors_r = np.abs(
|
20 |
-
l2d_t2r
|
21 |
-
)
|
22 |
-
errors_t = np.abs(
|
23 |
-
l2d_r2t
|
24 |
-
)
|
25 |
return E, errors_r, errors_t
|
26 |
|
27 |
|
|
|
16 |
E = vector_to_cross_product_matrix(T_r2t[:3, -1]) @ T_r2t[:3, :3]
|
17 |
l2d_r2t = (E @ to_homogeneous(p2d_r).T).T
|
18 |
l2d_t2r = (E.T @ to_homogeneous(p2d_t).T).T
|
19 |
+
errors_r = np.abs(
|
20 |
+
np.sum(to_homogeneous(p2d_r) * l2d_t2r, axis=1)
|
21 |
+
) / np.linalg.norm(l2d_t2r[:, :2], axis=1)
|
22 |
+
errors_t = np.abs(
|
23 |
+
np.sum(to_homogeneous(p2d_t) * l2d_r2t, axis=1)
|
24 |
+
) / np.linalg.norm(l2d_r2t[:, :2], axis=1)
|
25 |
return E, errors_r, errors_t
|
26 |
|
27 |
|
hloc/utils/read_write_model.py
CHANGED
@@ -42,7 +42,9 @@ logger = logging.getLogger(__name__)
|
|
42 |
CameraModel = collections.namedtuple(
|
43 |
"CameraModel", ["model_id", "model_name", "num_params"]
|
44 |
)
|
45 |
-
Camera = collections.namedtuple(
|
|
|
|
|
46 |
BaseImage = collections.namedtuple(
|
47 |
"Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"]
|
48 |
)
|
@@ -126,7 +128,11 @@ def read_cameras_text(path):
|
|
126 |
height = int(elems[3])
|
127 |
params = np.array(tuple(map(float, elems[4:])))
|
128 |
cameras[camera_id] = Camera(
|
129 |
-
id=camera_id,
|
|
|
|
|
|
|
|
|
130 |
)
|
131 |
return cameras
|
132 |
|
@@ -151,7 +157,9 @@ def read_cameras_binary(path_to_model_file):
|
|
151 |
height = camera_properties[3]
|
152 |
num_params = CAMERA_MODEL_IDS[model_id].num_params
|
153 |
params = read_next_bytes(
|
154 |
-
fid,
|
|
|
|
|
155 |
)
|
156 |
cameras[camera_id] = Camera(
|
157 |
id=camera_id,
|
@@ -222,7 +230,10 @@ def read_images_text(path):
|
|
222 |
image_name = elems[9]
|
223 |
elems = fid.readline().split()
|
224 |
xys = np.column_stack(
|
225 |
-
[
|
|
|
|
|
|
|
226 |
)
|
227 |
point3D_ids = np.array(tuple(map(int, elems[2::3])))
|
228 |
images[image_id] = Image(
|
@@ -259,16 +270,19 @@ def read_images_binary(path_to_model_file):
|
|
259 |
while current_char != b"\x00": # look for the ASCII 0 entry
|
260 |
image_name += current_char.decode("utf-8")
|
261 |
current_char = read_next_bytes(fid, 1, "c")[0]
|
262 |
-
num_points2D = read_next_bytes(
|
263 |
-
|
264 |
-
]
|
265 |
x_y_id_s = read_next_bytes(
|
266 |
fid,
|
267 |
num_bytes=24 * num_points2D,
|
268 |
format_char_sequence="ddq" * num_points2D,
|
269 |
)
|
270 |
xys = np.column_stack(
|
271 |
-
[
|
|
|
|
|
|
|
272 |
)
|
273 |
point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))
|
274 |
images[image_id] = Image(
|
@@ -307,7 +321,13 @@ def write_images_text(images, path):
|
|
307 |
with open(path, "w") as fid:
|
308 |
fid.write(HEADER)
|
309 |
for _, img in images.items():
|
310 |
-
image_header = [
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
first_line = " ".join(map(str, image_header))
|
312 |
fid.write(first_line + "\n")
|
313 |
|
@@ -387,9 +407,9 @@ def read_points3D_binary(path_to_model_file):
|
|
387 |
xyz = np.array(binary_point_line_properties[1:4])
|
388 |
rgb = np.array(binary_point_line_properties[4:7])
|
389 |
error = np.array(binary_point_line_properties[7])
|
390 |
-
track_length = read_next_bytes(
|
391 |
-
|
392 |
-
]
|
393 |
track_elems = read_next_bytes(
|
394 |
fid,
|
395 |
num_bytes=8 * track_length,
|
@@ -478,8 +498,12 @@ def read_model(path, ext=""):
|
|
478 |
ext = ".txt"
|
479 |
else:
|
480 |
try:
|
481 |
-
cameras, images, points3D = read_model(
|
482 |
-
|
|
|
|
|
|
|
|
|
483 |
return cameras, images, points3D
|
484 |
except FileNotFoundError:
|
485 |
raise FileNotFoundError(
|
@@ -571,7 +595,9 @@ def main():
|
|
571 |
)
|
572 |
args = parser.parse_args()
|
573 |
|
574 |
-
cameras, images, points3D = read_model(
|
|
|
|
|
575 |
|
576 |
print("num_cameras:", len(cameras))
|
577 |
print("num_images:", len(images))
|
@@ -579,7 +605,11 @@ def main():
|
|
579 |
|
580 |
if args.output_model is not None:
|
581 |
write_model(
|
582 |
-
cameras,
|
|
|
|
|
|
|
|
|
583 |
)
|
584 |
|
585 |
|
|
|
42 |
CameraModel = collections.namedtuple(
|
43 |
"CameraModel", ["model_id", "model_name", "num_params"]
|
44 |
)
|
45 |
+
Camera = collections.namedtuple(
|
46 |
+
"Camera", ["id", "model", "width", "height", "params"]
|
47 |
+
)
|
48 |
BaseImage = collections.namedtuple(
|
49 |
"Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"]
|
50 |
)
|
|
|
128 |
height = int(elems[3])
|
129 |
params = np.array(tuple(map(float, elems[4:])))
|
130 |
cameras[camera_id] = Camera(
|
131 |
+
id=camera_id,
|
132 |
+
model=model,
|
133 |
+
width=width,
|
134 |
+
height=height,
|
135 |
+
params=params,
|
136 |
)
|
137 |
return cameras
|
138 |
|
|
|
157 |
height = camera_properties[3]
|
158 |
num_params = CAMERA_MODEL_IDS[model_id].num_params
|
159 |
params = read_next_bytes(
|
160 |
+
fid,
|
161 |
+
num_bytes=8 * num_params,
|
162 |
+
format_char_sequence="d" * num_params,
|
163 |
)
|
164 |
cameras[camera_id] = Camera(
|
165 |
id=camera_id,
|
|
|
230 |
image_name = elems[9]
|
231 |
elems = fid.readline().split()
|
232 |
xys = np.column_stack(
|
233 |
+
[
|
234 |
+
tuple(map(float, elems[0::3])),
|
235 |
+
tuple(map(float, elems[1::3])),
|
236 |
+
]
|
237 |
)
|
238 |
point3D_ids = np.array(tuple(map(int, elems[2::3])))
|
239 |
images[image_id] = Image(
|
|
|
270 |
while current_char != b"\x00": # look for the ASCII 0 entry
|
271 |
image_name += current_char.decode("utf-8")
|
272 |
current_char = read_next_bytes(fid, 1, "c")[0]
|
273 |
+
num_points2D = read_next_bytes(
|
274 |
+
fid, num_bytes=8, format_char_sequence="Q"
|
275 |
+
)[0]
|
276 |
x_y_id_s = read_next_bytes(
|
277 |
fid,
|
278 |
num_bytes=24 * num_points2D,
|
279 |
format_char_sequence="ddq" * num_points2D,
|
280 |
)
|
281 |
xys = np.column_stack(
|
282 |
+
[
|
283 |
+
tuple(map(float, x_y_id_s[0::3])),
|
284 |
+
tuple(map(float, x_y_id_s[1::3])),
|
285 |
+
]
|
286 |
)
|
287 |
point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))
|
288 |
images[image_id] = Image(
|
|
|
321 |
with open(path, "w") as fid:
|
322 |
fid.write(HEADER)
|
323 |
for _, img in images.items():
|
324 |
+
image_header = [
|
325 |
+
img.id,
|
326 |
+
*img.qvec,
|
327 |
+
*img.tvec,
|
328 |
+
img.camera_id,
|
329 |
+
img.name,
|
330 |
+
]
|
331 |
first_line = " ".join(map(str, image_header))
|
332 |
fid.write(first_line + "\n")
|
333 |
|
|
|
407 |
xyz = np.array(binary_point_line_properties[1:4])
|
408 |
rgb = np.array(binary_point_line_properties[4:7])
|
409 |
error = np.array(binary_point_line_properties[7])
|
410 |
+
track_length = read_next_bytes(
|
411 |
+
fid, num_bytes=8, format_char_sequence="Q"
|
412 |
+
)[0]
|
413 |
track_elems = read_next_bytes(
|
414 |
fid,
|
415 |
num_bytes=8 * track_length,
|
|
|
498 |
ext = ".txt"
|
499 |
else:
|
500 |
try:
|
501 |
+
cameras, images, points3D = read_model(
|
502 |
+
os.path.join(path, "model/")
|
503 |
+
)
|
504 |
+
logger.warning(
|
505 |
+
"This SfM file structure was deprecated in hloc v1.1"
|
506 |
+
)
|
507 |
return cameras, images, points3D
|
508 |
except FileNotFoundError:
|
509 |
raise FileNotFoundError(
|
|
|
595 |
)
|
596 |
args = parser.parse_args()
|
597 |
|
598 |
+
cameras, images, points3D = read_model(
|
599 |
+
path=args.input_model, ext=args.input_format
|
600 |
+
)
|
601 |
|
602 |
print("num_cameras:", len(cameras))
|
603 |
print("num_images:", len(images))
|
|
|
605 |
|
606 |
if args.output_model is not None:
|
607 |
write_model(
|
608 |
+
cameras,
|
609 |
+
images,
|
610 |
+
points3D,
|
611 |
+
path=args.output_model,
|
612 |
+
ext=args.output_format,
|
613 |
)
|
614 |
|
615 |
|
hloc/utils/viz.py
CHANGED
@@ -19,7 +19,9 @@ def cm_RdGn(x):
|
|
19 |
return np.clip(c, 0, 1)
|
20 |
|
21 |
|
22 |
-
def plot_images(
|
|
|
|
|
23 |
"""Plot a set of images horizontally.
|
24 |
Args:
|
25 |
imgs: a list of NumPy or PyTorch images, RGB (H, W, 3) or mono (H, W).
|
@@ -129,7 +131,13 @@ def add_text(
|
|
129 |
):
|
130 |
ax = plt.gcf().axes[idx]
|
131 |
t = ax.text(
|
132 |
-
*pos,
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
)
|
134 |
if lcolor is not None:
|
135 |
t.set_path_effects(
|
|
|
19 |
return np.clip(c, 0, 1)
|
20 |
|
21 |
|
22 |
+
def plot_images(
|
23 |
+
imgs, titles=None, cmaps="gray", dpi=100, pad=0.5, adaptive=True
|
24 |
+
):
|
25 |
"""Plot a set of images horizontally.
|
26 |
Args:
|
27 |
imgs: a list of NumPy or PyTorch images, RGB (H, W, 3) or mono (H, W).
|
|
|
131 |
):
|
132 |
ax = plt.gcf().axes[idx]
|
133 |
t = ax.text(
|
134 |
+
*pos,
|
135 |
+
text,
|
136 |
+
fontsize=fs,
|
137 |
+
ha=ha,
|
138 |
+
va=va,
|
139 |
+
color=color,
|
140 |
+
transform=ax.transAxes
|
141 |
)
|
142 |
if lcolor is not None:
|
143 |
t.set_path_effects(
|
hloc/utils/viz_3d.py
CHANGED
@@ -46,7 +46,9 @@ def init_figure(height: int = 800) -> go.Figure:
|
|
46 |
dragmode="orbit",
|
47 |
),
|
48 |
margin=dict(l=0, r=0, b=0, t=0, pad=0),
|
49 |
-
legend=dict(
|
|
|
|
|
50 |
)
|
51 |
return fig
|
52 |
|
@@ -68,7 +70,9 @@ def plot_points(
|
|
68 |
mode="markers",
|
69 |
name=name,
|
70 |
legendgroup=name,
|
71 |
-
marker=dict(
|
|
|
|
|
72 |
)
|
73 |
fig.add_trace(tr)
|
74 |
|
@@ -162,7 +166,9 @@ def plot_camera_colmap(
|
|
162 |
)
|
163 |
|
164 |
|
165 |
-
def plot_cameras(
|
|
|
|
|
166 |
"""Plot a camera as a cone with camera frustum."""
|
167 |
for image_id, image in reconstruction.images.items():
|
168 |
plot_camera_colmap(
|
|
|
46 |
dragmode="orbit",
|
47 |
),
|
48 |
margin=dict(l=0, r=0, b=0, t=0, pad=0),
|
49 |
+
legend=dict(
|
50 |
+
orientation="h", yanchor="top", y=0.99, xanchor="left", x=0.1
|
51 |
+
),
|
52 |
)
|
53 |
return fig
|
54 |
|
|
|
70 |
mode="markers",
|
71 |
name=name,
|
72 |
legendgroup=name,
|
73 |
+
marker=dict(
|
74 |
+
size=ps, color=color, line_width=0.0, colorscale=colorscale
|
75 |
+
),
|
76 |
)
|
77 |
fig.add_trace(tr)
|
78 |
|
|
|
166 |
)
|
167 |
|
168 |
|
169 |
+
def plot_cameras(
|
170 |
+
fig: go.Figure, reconstruction: pycolmap.Reconstruction, **kwargs
|
171 |
+
):
|
172 |
"""Plot a camera as a cone with camera frustum."""
|
173 |
for image_id, image in reconstruction.images.items():
|
174 |
plot_camera_colmap(
|