-
Notifications
You must be signed in to change notification settings - Fork 368
/
Copy pathloop_detector_configs.py
294 lines (248 loc) · 20.3 KB
/
loop_detector_configs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
"""
* This file is part of PYSLAM
*
* Copyright (C) 2016-present Luigi Freda <luigi dot freda at gmail dot com>
*
* PYSLAM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PYSLAM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PYSLAM. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import time
import math
import numpy as np
import cv2
from utils_serialization import SerializableEnum, register_class
from utils_sys import getchar, Printer
from parameters import Parameters
from feature_manager import feature_manager_factory
from feature_manager_configs import FeatureManagerConfigs
from feature_types import FeatureDetectorTypes, FeatureDescriptorTypes
from loop_detector_base import LoopDetectorTaskType, LoopDetectKeyframeData, LoopDetectorTask, LoopDetectorOutput, LoopDetectorBase
from loop_detector_dbow3 import LoopDetectorDBoW3
from loop_detector_dbow2 import LoopDetectorDBoW2
from loop_detector_obindex2 import LoopDetectorOBIndex2
from loop_detector_ibow import LoopDetectorIBow
from loop_detector_vpr import LoopDetectorHdcDelf, LoopDetectorEigenPlaces, LoopDetectorNetVLAD, LoopDetectorSad, LoopDetectorAlexNet, LoopDetectorCosPlace
from loop_detector_vlad import LoopDetectorVlad
from loop_detector_vocabulary import OrbVocabularyData, VocabularyData, VladVocabularyData
kVerbose = True
kPrintTrackebackDetails = True
kTimerVerbose = False
kScriptPath = os.path.realpath(__file__)
kScriptFolder = os.path.dirname(kScriptPath)
kRootFolder = kScriptFolder
kDataFolder = kRootFolder + '/data'
@register_class
class GlobalDescriptorType(SerializableEnum):
# These types take the name from the adopted aggregation type and underlying local descriptor type.
DBOW2 = 0 # Bags of Words (BoW). This implementation only works with ORB local features. It needs an ORB vocabulary (available).
# Reference: "Bags of Binary Words for Fast Place Recognition in Image Sequences"
DBOW3 = 1 # Bags of Words (BoW). It needs a vocabulary (available for ORB).
OBINDEX2 = 2 # Hierarchical indexing scheme. Incremental Bags of binary Words. Incrementally builds a vocabulary. If needed transform the non-binary local descriptors into binary descriptors for better performance.
# Reference: "iBoW-LCD: An Appearance-based Loop Closure Detection Approach using Incremental Bags of Binary Words"
IBOW = 3 # Incremental Bags of binary Words (iBoW). Built on the top of OBINDEX2. It incrementally builds a vocabulary. If needed transform the non-binary local descriptors into binary descriptors for better performance.
# Reference: "iBoW-LCD: An Appearance-based Loop Closure Detection Approach using Incremental Bags of Binary Words"
HDC_DELF = 4 # Local DELF descriptor + Hyperdimensional Computing (HDC).
# Reference: "Large-Scale Image Retrieval with Attentive Deep Local Features", "Hyperdimensional Computing as a Framework for Systematic Aggregation of Image Descriptors"
SAD = 5 # Sum of Absolute Differences (SAD)
# Reference: "SeqSLAM: Visual Route-Based Navigation for Sunny Summer Days and Stormy Winter Nights".
ALEXNET = 6 # AlexNetConv3Extractor model.
# Reference: "On the performance of ConvNet features for place recognition".
NETVLAD = 7 # PatchNetVLADFeatureExtractor model.
# Reference: "Patch-NetVLAD: Multi-Scale Fusion of Locally-Global Descriptors for Place Recognition".
COSPLACE = 8 # CosPlaceFeatureExtractor model.
# Reference: "Rethinking Visual Geo-localization for Large-Scale Applications"
EIGENPLACES = 9 # EigenPlacesFeatureExtractor model.
# Reference: "EigenPlaces: Training Viewpoint Robust Models for Visual Place Recognition"
VLAD = 10 # VLAD, Vector of Locally Aggregated Descriptors. It needs a vocabulary (available for ORB).
# Reference: "All about VLAD".
# Additional information about used local descriptors aggregation method
@register_class
class LocalDescriptorAggregationType(SerializableEnum):
NONE = 0
DBOW2 = 1 # Bags of Words (BoW). This version only works with ORB2 local features (DBOW3 is easier to use).
# Reference: "Bags of Binary Words for Fast Place Recognition in Image Sequences"
DBOW3 = 2 # Bags of Words (BoW).
OBINDEX2 = 3 # Hierarchical indexing scheme. Incremental Bags of binary Words.
# Reference: "iBoW-LCD: An Appearance-based Loop Closure Detection Approach using Incremental Bags of Binary Words"
IBOW = 4 # Incremental Bags of binary Words (iBoW). Built on the top of OBINDEX2.
# Reference: "iBoW-LCD: An Appearance-based Loop Closure Detection Approach using Incremental Bags of Binary Words"
HDC = 5 # Hyperdimensional Computing (HDC).
# Reference: "Hyperdimensional Computing as a Framework for Systematic Aggregation of Image Descriptors"
NETVLAD = 6 # Patch-level features from NetVLAD residuals
# Reference: "Patch-NetVLAD: Multi-Scale Fusion of Locally-Global Descriptors for Place Recognition".
VLAD = 7 # VLAD, Vector of Locally Aggregated Descriptors.
# Reference: "All about VLAD"
"""
LoopDetectorConfigs contains a collection of ready-to-used loop detection configurations. These configurations are used by the class LoopDetectingProcess.
You can test any of these configurations separataly (without SLAM) by using the script: test/loopclosing/test_loop_detecting_process.py or test/loopclosing/test_loop_detector.py.
See the README for further details.
Template configuration:
LoopDetectorConfigs.XXX = dict(
global_descriptor_type = GlobalDescriptorType.XXX,
local_feature_manager_config = FeatureManagerConfigs.XXX, # If None the frontend local descriptors will be re-used (must be compatible with the used descriptor aggregator and loaded vocabulary).
# Otherwise, an independent local feature manager is created and used.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.XXX,
vocabulary_data = XXX) # Must be a vocabulary built with the frontend local descriptor type (see the file loop_detector_vocabulary.py)
"""
class LoopDetectorConfigs(object):
DBOW2 = dict(
global_descriptor_type = GlobalDescriptorType.DBOW2,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.DBOW2,
vocabulary_data = OrbVocabularyData()) # Must be a vocabulary built with the frontend local descriptor type
DBOW2_INDEPENDENT = dict(
global_descriptor_type = GlobalDescriptorType.DBOW2,
local_feature_manager_config = FeatureManagerConfigs.ORB2, # Use an independent ORB2 local feature manager for loop detection (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.DBOW2,
vocabulary_data = OrbVocabularyData()) # Must be a vocabulary built with the frontend local descriptor type
DBOW3 = dict(
global_descriptor_type = GlobalDescriptorType.DBOW3,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.DBOW3,
vocabulary_data = OrbVocabularyData()) # Must be a vocabulary built with the frontend local descriptor type
DBOW3_INDEPENDENT = dict(
global_descriptor_type = GlobalDescriptorType.DBOW3,
local_feature_manager_config = FeatureManagerConfigs.ORB2, # Use an independent ORB2 local feature manager for loop detection (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.VLAD,
vocabulary_data = OrbVocabularyData()) # Must be a vocabulary built with the adopted local descriptor type
VLAD = dict(
global_descriptor_type = GlobalDescriptorType.VLAD,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.VLAD,
vocabulary_data = VladVocabularyData()) # Must be a vocabulary built with the adopted local descriptor type
VLAD_INDEPENDENT = dict(
global_descriptor_type = GlobalDescriptorType.VLAD,
local_feature_manager_config = FeatureManagerConfigs.ORB2, # Use an independent ORB2 local feature manager for loop detection (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.VLAD,
vocabulary_data = VladVocabularyData()) # Must be a vocabulary built with the adopted local descriptor type
OBINDEX2 = dict(
global_descriptor_type = GlobalDescriptorType.OBINDEX2,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used. If they are non-binary, they will be converted to binary.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.OBINDEX2,
vocabulary_data = None) # OBIndex2 does not need a vocabulary. It incrementally builds it.
IBOW = dict(
global_descriptor_type = GlobalDescriptorType.IBOW,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used. If they are non-binary, they will be converted to binary.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.IBOW,
vocabulary_data = None) # IBow does not need a vocabulary. It incrementally builds it.
IBOW_INDEPENDENT = dict(
global_descriptor_type = GlobalDescriptorType.IBOW,
local_feature_manager_config = FeatureManagerConfigs.ORB2, # Use an independent ORB2 local feature manager for loop detection (must be compatible with the used descriptor aggregator and loaded vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.IBOW,
vocabulary_data = None) # IBow does not need a vocabulary. It incrementally builds it.
# NOTE: HDC_DELF seems very slow and does not work well with SLAM. In fact,
# the online computation of global descriptors for keyframes struggles to keep up with the real-time demands of SLAM processing.
# You can test HDC_DELF separataly (without SLAM) by using the script: test/loopclosing/test_loop_detecting_process.py.
HDC_DELF = dict(
global_descriptor_type = GlobalDescriptorType.HDC_DELF,
local_feature_manager_config = None, # It does use its own local feature manager: Delf.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.HDC,
vocabulary_data = None) # It does not need a vocabulary
SAD = dict(
global_descriptor_type = GlobalDescriptorType.SAD,
local_feature_manager_config = None, # Not needed.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.NONE,
vocabulary_data = None) # It does not need a vocabulary
# NOTE: ALEXNET seems very slow and does not work well with SLAM. In fact,
# the online computation of global descriptors for keyframes struggles to keep up with the real-time demands of SLAM processing.
# You can test ALEXNET separataly (without SLAM) by using the script: test/loopclosing/test_loop_detecting_process.py.
ALEXNET = dict(
global_descriptor_type = GlobalDescriptorType.ALEXNET,
local_feature_manager_config = None, # Not needed.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.NONE,
vocabulary_data = None) # It does not need a vocabulary
NETVLAD = dict(
global_descriptor_type = GlobalDescriptorType.NETVLAD,
local_feature_manager_config = None, # Not needed.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.NETVLAD,
vocabulary_data = None) # It does not need a vocabulary
COSPLACE = dict(
global_descriptor_type = GlobalDescriptorType.COSPLACE,
local_feature_manager_config = None, # Not needed.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.NONE,
vocabulary_data = None) # It does not need a vocabulary
EIGENPLACES = dict(
global_descriptor_type = GlobalDescriptorType.EIGENPLACES,
local_feature_manager_config = None, # Not needed.
local_descriptor_aggregation_type = LocalDescriptorAggregationType.NONE,
vocabulary_data = None) # It does not need a vocabulary
def loop_detector_factory(
global_descriptor_type = GlobalDescriptorType.DBOW3,
local_feature_manager_config = None, # If None the frontend local descriptors will be re-used (depending on the used descriptor aggregator and vocabulary)
local_descriptor_aggregation_type = LocalDescriptorAggregationType.DBOW3,
vocabulary_data = OrbVocabularyData()):
if vocabulary_data is not None:
vocabulary_data.check_download() # check if the vocabulary exists or we need to download it
# create an independent local feature manager if requested for the loop detection
if local_feature_manager_config is None:
local_feature_manager = None # re-use the frontend local descriptors
else:
local_feature_manager = feature_manager_factory(**local_feature_manager_config) # use an independent local feature manager in loop closing
loop_detector = None
if global_descriptor_type == GlobalDescriptorType.DBOW2:
if local_feature_manager is not None:
if local_feature_manager.descriptor_type != FeatureDescriptorTypes.ORB2 and local_feature_manager.descriptor_type != FeatureDescriptorTypes.ORB:
raise ValueError('loop_detector_factory: local_feature_manager.descriptor_type must be ORB2 or ORB')
loop_detector = LoopDetectorDBoW2(vocabulary_data=vocabulary_data, local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.DBOW3:
loop_detector = LoopDetectorDBoW3(vocabulary_data=vocabulary_data, local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.VLAD:
loop_detector = LoopDetectorVlad(vocabulary_data=vocabulary_data, local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.OBINDEX2:
loop_detector = LoopDetectorOBIndex2(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.IBOW:
loop_detector = LoopDetectorIBow(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.HDC_DELF:
loop_detector = LoopDetectorHdcDelf(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.SAD:
loop_detector = LoopDetectorSad(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.ALEXNET:
loop_detector = LoopDetectorAlexNet(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.NETVLAD:
loop_detector = LoopDetectorNetVLAD(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.COSPLACE:
loop_detector = LoopDetectorCosPlace(local_feature_manager=local_feature_manager)
elif global_descriptor_type == GlobalDescriptorType.EIGENPLACES:
loop_detector = LoopDetectorEigenPlaces(local_feature_manager=local_feature_manager)
else:
raise ValueError('loop_detector_factory: unknown global_descriptor_type')
if loop_detector is not None:
loop_detector.global_descriptor_type = global_descriptor_type
loop_detector.local_descriptor_aggregation_type = local_descriptor_aggregation_type
loop_detector.local_feature_manager = local_feature_manager
loop_detector.vocabulary_data = vocabulary_data
return loop_detector
def loop_detector_config_check(loop_detector: LoopDetectorBase, slam_feature_descriptor_type: FeatureDescriptorTypes):
descriptor_type_to_check = None
if loop_detector.local_feature_manager is None:
descriptor_type_to_check = slam_feature_descriptor_type
else:
descriptor_type_to_check = loop_detector.local_feature_manager.descriptor_type
if loop_detector.global_descriptor_type == GlobalDescriptorType.DBOW2:
if descriptor_type_to_check != FeatureDescriptorTypes.ORB2 and descriptor_type_to_check != FeatureDescriptorTypes.ORB:
message = f'loop_detector_config_check: ERROR: incompatible descriptor_type! With DBOW2, the only available voculary that is loaded needs a local_feature_manager with ORB2 or ORB.'
message += f'\n\t As a quick solution, you can set the loop detector to work with an independent ORB2 local_feature_manager: Use the configuration DBOW2_INDEPENDENT.'
message += f'\n\t Alternatively, you may want to use DBOW3 instead and then create a vocabulary with your favorite descriptors (see the pyslam README file) and then load it.'
Printer.red(message)
raise ValueError(message)
if loop_detector.vocabulary_data is not None:
if descriptor_type_to_check != loop_detector.vocabulary_data.descriptor_type:
message = f'loop_detector_config_check: ERROR: incompatible vocabulary type!'
message += f'\n\t The loaded vocabulary_data.descriptor_type is {loop_detector.vocabulary_data.descriptor_type.name}.'
message += f'\n\t On the other end, the loop detector is configured to work with a {descriptor_type_to_check.name} local_feature_manager.'
message += f'\n\t If you dont have a vocabulary with {descriptor_type_to_check.name} then you must create it (see the pyslam README file) and then load it.'
message += f'\n\t Otherwise, you can set the loop detector to work with an independent {loop_detector.vocabulary_data.descriptor_type.name} local_feature_manager.'
message += f'\n\t See the file loop_detector_configs.py for further details.'
Printer.red(message)
raise ValueError(message)