Skip to content

Commit 74d7bd5

Browse files
authored
Merge pull request #119 from lcmrl/add_new_local_features
Add new local features
2 parents 1f838a6 + 7e3ea25 commit 74d7bd5

213 files changed

Lines changed: 192798 additions & 39 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
Multivew matcher for SfM software. Support both deep-learning based and hand-crafted local features and matchers and export keypoints and matches directly in a COLMAP database or to Agisoft Metashape by importing the reconstruction in Bundler format. Now, it supports both OpenMVG and MicMac. Feel free to collaborate!
2020

21-
While `dev` branch is more frequently updated, `master` is the default more stable branch and is updated from `dev` less frequently. If you are looking for the newest developments, please switch to `dev`.
22-
2321
For how to use DIM, check the <a href="https://3dom-fbk.github.io/deep-image-matching/">Documentation</a> (updated for the master branch).
2422

2523
**Please, note that `deep-image-matching` is under active development** and it is still in an experimental stage. If you find any bug, please open an issue. **For the licence of individual local features and matchers please refer to the authors' original projects**.
@@ -32,25 +30,47 @@ Key features:
3230
- Support for image rotations
3331
- Compatibility with several SfM software
3432
- Support image retrieval with deep-learning local features
35-
36-
| Supported Extractors | Supported Matchers |
37-
| ---------------------------------- | --------------------------------------------------------- |
38-
| &check; SuperPoint | &check; Lightglue (with Superpoint, Disk, and ALIKED) |
39-
| &check; DISK | &check; SuperGlue (with Superpoint) |
40-
| &check; Superpoint free | &check; Nearest neighbor (with KORNIA Descriptor Matcher) |
41-
| &check; SRIF | &check; LoFTR (only GPU) |
42-
| &check; ALIKED | &check; SE2-LoFTR (no tiling and only GPU) |
43-
| &check; KeyNet + OriNet + HardNet8 | &check; RoMa |
44-
| &check; DeDoDe (only GPU) | &#x2610; GlueStick |
45-
| &check; SIFT (from Opencv) |
46-
| &check; ORB (from Opencv) |
47-
48-
| Supported SfM software |
49-
| --------------------------------------------- |
50-
| &check; COLMAP |
51-
| &check; OpenMVG |
52-
| &check; MICMAC |
53-
| &check; Agisoft Metashape |
33+
- Graph-based clustering
34+
- Run SfM directly in DIM (pycolmap, openmvg, etc)
35+
36+
### Supported Extractors
37+
38+
| Algorithm | Year | Paper link | Github link | Notes |
39+
| --------- | ---- | ----------- | ---------- | ----- |
40+
| RIPE | 2025 | [link](https://arxiv.org/abs/2507.04839) | [link](https://github.com/fraunhoferhhi/RIPE) | supported |
41+
| RDD sparse | 2025 | [link](https://arxiv.org/abs/2505.08013) | [link](https://github.com/xtcpete/rdd) | supported |
42+
| LiftFeat | 2025 | [link](https://www.arxiv.org/abs/2505.03422) | [link](https://github.com/lyp-deeplearning/LiftFeat) | supported |
43+
| XFeat | 2024 | [link](https://arxiv.org/abs/2404.19174) | [link](https://github.com/verlab/accelerated_features) | supported |
44+
| DeDoDe | 2024 | [link](https://arxiv.org/abs/2308.08479) | [link](https://github.com/Parskatt/DeDoDe) | only GPU |
45+
| ALIKED | 2023 | [link](https://arxiv.org/pdf/2304.03608) | [link](https://github.com/Shiaoming/ALIKED) | supported |
46+
| SRIF | 2023 | [link](https://www.sciencedirect.com/science/article/abs/pii/S0924271623002277) | [link](https://github.com/LJY-RS/SRIF) | supported |
47+
| DISK | 2020 | [link](https://arxiv.org/abs/2006.13566) | [link](https://github.com/cvlab-epfl/disk) | supported |
48+
| KeyNet | 2019 | [link](https://arxiv.org/abs/1904.00889) | [link](https://github.com/axelBarroso/Key.Net) | supported |
49+
| SuperPoint | 2018 | [link](https://arxiv.org/abs/1712.07629) | [link](https://github.com/magicleap/SuperPointPretrainedNetwork) | supported |
50+
| Superpoint open | 2018 | [link](https://arxiv.org/abs/1712.07629) | [link](https://github.com/rpautrat/SuperPoint) | supported |
51+
| HardNet | 2017 | [link](https://arxiv.org/abs/1705.10872) | [link](https://github.com/DagnyT/hardnet) | supported |
52+
| ORB | 2011 | [link](https://docs.opencv.org/3.4/d1/d89/tutorial_py_orb.html) | [link](https://ieeexplore.ieee.org/abstract/document/6126544) | from OpenCV |
53+
| SIFT | 2004 | [link](https://docs.opencv.org/4.x/da/df5/tutorial_py_sift_intro.html) | [link](https://www.cs.ubc.ca/~lsigal/425_2024W1/ijcv04.pdf) | from OpenCV |
54+
55+
56+
### Supported Matchers
57+
58+
| Algorithm | Year | Paper link | Github link | Notes |
59+
| --------- | ---- | ----------- | ---------- | ----- |
60+
| LightGlue | 2023 | [link](https://arxiv.org/pdf/2306.13643) | [link](https://github.com/cvg/LightGlue) | with SuperPoint, DISK, and ALIKED |
61+
| LighterGlue | 2023 | [link](https://arxiv.org/pdf/2306.13643) | [link](https://github.com/cvg/LightGlue) | with XFeat |
62+
| RoMa | 2023 | [link](https://arxiv.org/abs/2305.15404) | [link](https://github.com/Parskatt/RoMa) | supported |
63+
| SE2-LoFTR | 2022 | [link](https://openaccess.thecvf.com/content/CVPR2022W/IMW/papers/Bokman_A_Case_for_Using_Rotation_Invariant_Features_in_State_of_CVPRW_2022_paper.pdf) | [link](https://github.com/georg-bn/se2-loftr) | no tiling and only GPU |
64+
| LoFTR | 2021 | [link](https://arxiv.org/abs/2104.00680) | [link](https://github.com/zju3dv/LoFTR) | only GPU |
65+
| SuperGlue | 2020 | [link](https://arxiv.org/abs/1911.11763) | [link](https://github.com/magicleap/SuperGluePretrainedNetwork) | with SuperPoint |
66+
| Nearest Neighbor | - | - | - | from KORNIA |
67+
68+
### Supported SfM software
69+
70+
| &check; [COLMAP](https://github.com/colmap/colmap) |
71+
| &check; [OpenMVG](https://github.com/openMVG/openMVG) |
72+
| &check; [MICMAC](https://github.com/micmacIGN/micmac) |
73+
| &check; [Agisoft Metashape](https://www.agisoft.com/) |
5474
| &check; Software that supports bundler format |
5575

5676
## Colab demo and notebooks
@@ -189,13 +209,14 @@ python ./join_databases.py --input path/to/dir/with/databases --output path/to/o
189209

190210
### Exporting the solution to Metashape
191211

192-
To export the solution to Metashape, you can export the COLMAP database to Bundler format and then import it into Metashape.
193-
This can be done from Metashape GUI, by first importing the images and then use the function `Import Cameras` (File -> Import -> Import Cameras) to select Bundler file (e.g., bundler.out) and the image list file (e.g., bundler_list.txt).
212+
Suggested solution:
213+
* It is now possible to run SfM directly in Metashape using 2D observations extracted in DIM. You can use the script `export_to_bundler.py` from the scripts folder. It will create a fake bundler file. Then in Metashape import all the images you need, import camera poses using the bundler file, select all images and reset the alignment. Finally right click, align selected cameras (see [issue](https://github.com/3DOM-FBK/deep-image-matching/issues/94)).
214+
215+
216+
Other solutions:
217+
* To export the solution to Metashape, you can export the COLMAP database to Bundler format and then import it into Metashape. This can be done from Metashape GUI, by first importing the images and then use the function `Import Cameras` (File -> Import -> Import Cameras) to select Bundler file (e.g., bundler.out) and the image list file (e.g., bundler_list.txt).
194218

195-
Alternatevely, you can use the `export_to_metashape.py` script to automatically create a Metashape project from a reconstruction saved in Bundler format.
196-
The script `export_to_metashape.py` takes as input the solution in Bundler format and the images and it exports the solution to Metashape.
197-
It requires to install Metashape as a Python module in your environment and to have a valid license.
198-
Please, refer to the instructions at [https://github.com/franioli/metashape](https://github.com/franioli/metashape).
219+
* Alternatevely, you can use the `export_to_metashape.py` script to automatically create a Metashape project from a reconstruction saved in Bundler format. The script `export_to_metashape.py` takes as input the solution in Bundler format and the images and it exports the solution to Metashape. It requires to install Metashape as a Python module in your environment and to have a valid license. Please, refer to the instructions at [https://github.com/franioli/metashape](https://github.com/franioli/metashape).
199220

200221
## How to contribute
201222

notes.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
- [ ] Testing on very large datasets ([Issue [#29](https://github.com/3DOM-FBK/deep-image-matching/issues/29)])
99
- [ ] Use Github submodules instead of copying thirdpary code inside the repo
1010
- [ ] Add subpixel refinement of the matches (e.g., cross-correlation or [pixel-perfect-sfm](https://github.com/cvg/pixel-perfect-sfm))
11-
- [ ] Make semi-dense matcher work with multi-camera (Issue [[#24](https://github.com/3DOM-FBK/deep-image-matching/issues/24)])
12-
- [ ] Improve usage of multiple descriptors together
13-
- [ ] Finish extending compatibility to OpenMVG
1411

1512
## Bugs and Issues
1613

@@ -26,7 +23,6 @@
2623
## Other enhancements
2724

2825
- [ ] Improve configuration management [_Hydra_](https://hydra.cc/docs/tutorials/structured_config/schema/) to make using yaml files, command line and GUI (Issue [[#48](https://github.com/3DOM-FBK/deep-image-matching/issues/48)])
29-
- [ ] Tests on satellite images
3026
- [ ] Add steerers + DeDoDe
3127
- [ ] Add Silk features
3228
- [ ] Add SIFT + LightGlue
@@ -59,3 +55,7 @@
5955
- [x] Add tests, documentation and examples (e.g. colab, ..)
6056
- [x] Cleanup repository to removed large files from Git history
6157
- [x] Update README CLI options
58+
- [x] Make semi-dense matcher work with multi-camera (Issue [[#24](https://github.com/3DOM-FBK/deep-image-matching/issues/24)])
59+
- [x] Improve usage of multiple descriptors together
60+
- [x] Finish extending compatibility to OpenMVG
61+
- [x] Tests on satellite images

src/deep_image_matching/config.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,22 @@
157157
},
158158
"matcher": {"name": "kornia_matcher", "match_mode": "smnn", "th": 0.95},
159159
},
160+
"liftfeat+kornia_matcher": {
161+
"extractor": {
162+
"name": "liftfeat",
163+
"max_keypoints": 4096,
164+
"detect_threshold": 0.05,
165+
},
166+
"matcher": {"name": "kornia_matcher", "match_mode": "smnn", "th": 0.99},
167+
},
168+
"ripe+kornia_matcher": {
169+
"extractor": {
170+
"name": "ripe",
171+
"max_keypoints": 4096,
172+
"detect_threshold": 0.5,
173+
},
174+
"matcher": {"name": "kornia_matcher", "match_mode": "smnn", "th": 0.95},
175+
},
160176
"disk+lightglue": {
161177
"extractor": {
162178
"name": "disk",
@@ -169,6 +185,15 @@
169185
"name": "lightglue",
170186
},
171187
},
188+
"xfeat+lighterglue": {
189+
"extractor": {
190+
"name": "xfeat",
191+
"max_num_keypoints": 4096,
192+
},
193+
"matcher": {
194+
"name": "lighterglue",
195+
},
196+
},
172197
"aliked+lightglue": {
173198
"extractor": {
174199
"name": "aliked",
@@ -185,6 +210,21 @@
185210
"filter_threshold": 0.1, # match threshold
186211
},
187212
},
213+
"rdd_sparse+lightglue": {
214+
"extractor": {
215+
"name": "rdd_sparse",
216+
"max_num_keypoints": 4000,
217+
},
218+
"matcher": {
219+
"name": "lightglue",
220+
"n_layers": 9,
221+
"depth_confidence": 0.95, # early stopping, disable with -1
222+
"width_confidence": 0.99, # point pruning, disable with -1
223+
"filter_threshold": 0.1, # match threshold
224+
"input_dim": 256, # RDD descriptor dimension
225+
"weights": '../../rdd/RDD/weights/RDD_lg-v2.pth', # path to the weights
226+
},
227+
},
188228
"orb+kornia_matcher": {
189229
"extractor": {
190230
"name": "orb",
@@ -267,6 +307,10 @@
267307
"orb",
268308
"sift",
269309
"no_extractor",
310+
"rdd_sparse",
311+
"liftfeat",
312+
"ripe",
313+
"xfeat",
270314
],
271315
"matchers": [
272316
"superglue",
@@ -277,6 +321,7 @@
277321
"adalam",
278322
"kornia_matcher",
279323
"roma",
324+
"lighterglue",
280325
],
281326
"retrieval": ["netvlad", "openibl", "cosplace", "dir"],
282327
"matching_strategy": [
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import yaml
2+
import numpy as np
3+
import torch
4+
from pathlib import Path
5+
6+
from ..thirdparty.liftfeat.models.liftfeat_wrapper import MODEL_PATH, LiftFeat
7+
from .extractor_base import ExtractorBase
8+
9+
10+
class LiftFeatExtractor(ExtractorBase):
11+
_default_conf = {
12+
"name": "liftfeat",
13+
"max_keypoints": 4000,
14+
"detect_threshold": 0.05,
15+
}
16+
required_inputs = []
17+
grayscale = False
18+
descriptor_size = 128
19+
20+
21+
def __init__(self, config: dict):
22+
# Init the base class
23+
super().__init__(config)
24+
25+
# Load extractor
26+
cfg = self.config.get("extractor")
27+
detct_threshold = cfg.get("detect_threshold", self._default_conf["detect_threshold"])
28+
29+
self._extractor = LiftFeat(weight=MODEL_PATH,detect_threshold=detct_threshold)
30+
self.max_num_keypoints = cfg.get("max_keypoints", self._default_conf["max_keypoints"])
31+
32+
@torch.no_grad()
33+
def _extract(self, image: np.ndarray) -> np.ndarray:
34+
# Extract features using LiftFeat's extract method (expects numpy array)
35+
feats = self._extractor.extract(image)
36+
37+
# Convert tensors to numpy arrays
38+
feats = {k: v.cpu().numpy() for k, v in feats.items()}
39+
40+
# Keep only the best keypoints based on scores
41+
scores = feats["scores"]
42+
if len(scores) > self.max_num_keypoints:
43+
# Get indices of top max_num_keypoints by score
44+
top_indices = np.argsort(scores)[::-1][:self.max_num_keypoints]
45+
feats["keypoints"] = feats["keypoints"][top_indices, :]
46+
feats["descriptors"] = feats["descriptors"][top_indices, :]
47+
feats["scores"] = scores[top_indices]
48+
49+
feats["descriptors"] = feats["descriptors"].T
50+
51+
return feats
52+
53+
def _frame2tensor(self, image: np.ndarray, device: str = "cuda"):
54+
"""
55+
Convert a frame to a tensor.
56+
57+
Args:
58+
image: The image to be converted
59+
device: The device to convert to (defaults to 'cuda')
60+
"""
61+
if len(image.shape) == 2:
62+
image = image[None][None]
63+
elif len(image.shape) == 3:
64+
image = image.transpose(2, 0, 1)[None]
65+
return torch.tensor(image / 255.0, dtype=torch.float).to(device)
66+
67+
def _rbd(self, data: dict) -> dict:
68+
"""Remove batch dimension from elements in data"""
69+
return {
70+
k: v[0] if isinstance(v, (torch.Tensor, np.ndarray, list)) else v
71+
for k, v in data.items()
72+
}
73+
74+
75+
if __name__ == "__main__":
76+
pass
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import yaml
2+
import numpy as np
3+
import torch
4+
from pathlib import Path
5+
6+
from ..thirdparty.rdd.RDD import RDD
7+
from .extractor_base import ExtractorBase
8+
9+
10+
class RDDSparseExtractor(ExtractorBase):
11+
_default_conf = {
12+
"name": "aliked",
13+
"max_num_keypoints": 4000,
14+
}
15+
required_inputs = []
16+
grayscale = False
17+
descriptor_size = 128
18+
19+
20+
def __init__(self, config: dict):
21+
# Init the base class
22+
super().__init__(config)
23+
24+
# Load extractor
25+
cfg = self.config.get("extractor")
26+
config_path = Path(__file__).parent.parent / 'thirdparty' / 'rdd' / 'configs' / 'default.yaml'
27+
weights_path = Path(__file__).parent.parent / 'thirdparty' / 'rdd' / 'RDD' / 'weights' / 'RDD-v2.pth'
28+
29+
with open(config_path, 'r') as f:
30+
network_config = yaml.safe_load(f)
31+
self._extractor = RDD.build(config=network_config, weights=str(weights_path))
32+
self.max_num_keypoints = cfg.get("max_num_keypoints", self._default_conf["max_num_keypoints"])
33+
34+
@torch.no_grad()
35+
def _extract(self, image: np.ndarray) -> np.ndarray:
36+
image_ = self._frame2tensor(image, self._device)
37+
38+
# Extract features using RDD's extract method
39+
feats_list = self._extractor.extract(image_)
40+
41+
# Get the first batch element (batch size is 1)
42+
feats = feats_list[0]
43+
44+
# Convert tensors to numpy arrays
45+
feats = {k: v.cpu().numpy() for k, v in feats.items()}
46+
47+
feats["keypoints"] = feats["keypoints"][:self.max_num_keypoints, :]
48+
feats["descriptors"] = feats["descriptors"][:self.max_num_keypoints, :]
49+
50+
return feats
51+
52+
def _frame2tensor(self, image: np.ndarray, device: str = "cuda"):
53+
"""
54+
Convert a frame to a tensor.
55+
56+
Args:
57+
image: The image to be converted
58+
device: The device to convert to (defaults to 'cuda')
59+
"""
60+
if len(image.shape) == 2:
61+
image = image[None][None]
62+
elif len(image.shape) == 3:
63+
image = image.transpose(2, 0, 1)[None]
64+
return torch.tensor(image / 255.0, dtype=torch.float).to(device)
65+
66+
def _rbd(self, data: dict) -> dict:
67+
"""Remove batch dimension from elements in data"""
68+
return {
69+
k: v[0] if isinstance(v, (torch.Tensor, np.ndarray, list)) else v
70+
for k, v in data.items()
71+
}
72+
73+
74+
if __name__ == "__main__":
75+
pass

0 commit comments

Comments
 (0)