From bd68214d45a9505ec392f6c191ddbba37a30e7da Mon Sep 17 00:00:00 2001 From: Mihai Dusmanu Date: Wed, 9 Jun 2021 15:44:29 +0200 Subject: [PATCH 1/2] Added SIFT features using PyCOLMAP. --- hloc/extract_features.py | 10 +++++++ hloc/extractors/sift.py | 65 ++++++++++++++++++++++++++++++++++++++++ hloc/match_features.py | 8 +++++ 3 files changed, 83 insertions(+) create mode 100644 hloc/extractors/sift.py diff --git a/hloc/extract_features.py b/hloc/extract_features.py index a214a35a..85b22f54 100644 --- a/hloc/extract_features.py +++ b/hloc/extract_features.py @@ -74,6 +74,16 @@ 'resize_max': 1600, }, }, + 'sift': { + 'output': 'feats-sift', + 'model': { + 'name': 'sift' + }, + 'preprocessing': { + 'grayscale': True, + 'resize_max': 1600, + }, + }, 'dir': { 'output': 'global-feats-dir', 'model': { diff --git a/hloc/extractors/sift.py b/hloc/extractors/sift.py new file mode 100644 index 00000000..55736b14 --- /dev/null +++ b/hloc/extractors/sift.py @@ -0,0 +1,65 @@ +import copy +import numpy as np +import torch + +from ..utils.base_model import BaseModel + +import pycolmap + + +EPS = 1e-6 + + +def sift_to_rootsift(descs): + l1_norm_descs = descs / (np.linalg.norm(descs, ord=1, axis=-1)[:, np.newaxis] + EPS) + sqrt_l1_norm_descs = np.sqrt(l1_norm_descs) + root_descs = sqrt_l1_norm_descs / (np.linalg.norm(sqrt_l1_norm_descs, axis=-1)[:, np.newaxis] + EPS) + return root_descs + + +class SIFT(BaseModel): + default_conf = { + 'num_octaves': 4, + 'octave_resolution': 3, + 'first_octave': 0, + 'edge_thresh': 10, + 'peak_thresh': 0.01, + 'upright': False, + 'root': True, + 'max_keypoints': -1 + } + required_inputs = ['image'] + + def _init(self, conf): + self.root = conf['root'] + self.max_keypoints = conf['max_keypoints'] + + vlfeat_conf = copy.deepcopy(conf) + vlfeat_conf.pop('name', None) + vlfeat_conf.pop('root', None) + vlfeat_conf.pop('max_keypoints', None) + self.extract = lambda image: pycolmap.extract_sift( + image, **vlfeat_conf + ) + + def _forward(self, data): + image = data['image'] + + keypoints, scores, descriptors = self.extract(image[0, 0].cpu().numpy()) + keypoints = keypoints[:, : 2] # Keep only x, y. + + if self.root: + descriptors = sift_to_rootsift(descriptors) + + if self.max_keypoints != -1: + # It is unclear whether scores from PyCOLMAP are 100% correct - follow https://github.com/mihaidusmanu/pycolmap/issues/8. + indices = np.argsort(scores)[:: -1][: self.max_keypoints] + keypoints = keypoints[indices, :] + scores = scores[indices] + descriptors = descriptors[indices, :] + + return { + 'keypoints': torch.from_numpy(keypoints)[None], + 'scores': torch.from_numpy(scores)[None], + 'descriptors': torch.from_numpy(descriptors.T)[None], + } diff --git a/hloc/match_features.py b/hloc/match_features.py index bb0db870..52249b70 100644 --- a/hloc/match_features.py +++ b/hloc/match_features.py @@ -33,6 +33,14 @@ 'mutual_check': True, 'distance_threshold': 0.7, }, + }, + 'NN-ratio': { + 'output': 'matches-NN-mutual-ratio.8', + 'model': { + 'name': 'nearest_neighbor', + 'mutual_check': True, + 'ratio_threshold': 0.8, + } } } From d3fa344d786761282676bd629f91b9e985bf27ca Mon Sep 17 00:00:00 2001 From: Mihai Dusmanu Date: Wed, 9 Jun 2021 16:08:42 +0200 Subject: [PATCH 2/2] Added asserts for input image. --- hloc/extractors/sift.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hloc/extractors/sift.py b/hloc/extractors/sift.py index 55736b14..75cb0b44 100644 --- a/hloc/extractors/sift.py +++ b/hloc/extractors/sift.py @@ -44,6 +44,8 @@ def _init(self, conf): def _forward(self, data): image = data['image'] + assert image.shape[1] == 1 + assert image.min() >= -EPS and image.max() <= 1 + EPS keypoints, scores, descriptors = self.extract(image[0, 0].cpu().numpy()) keypoints = keypoints[:, : 2] # Keep only x, y.