19
19
from builtin_interfaces .msg import Time
20
20
from cv_bridge import CvBridge
21
21
from geometry_msgs .msg import PoseWithCovariance , PoseWithCovarianceStamped
22
- from kornia .feature import LoFTR
22
+ from kornia .feature import DISK , LightGlueMatcher , laf_from_center_scale_ori
23
23
from rclpy .node import Node
24
24
from rclpy .qos import QoSPresetProfiles
25
25
from robot_localization .srv import SetPose
47
47
# TODO: make error model and generate covariance matrix dynamically
48
48
# Create dummy covariance matrix
49
49
_covariance_matrix = np .zeros ((6 , 6 ))
50
- np .fill_diagonal (_covariance_matrix , 36 ) # 3 meter SD = 9 variance
51
- _covariance_matrix [3 , 3 ] = np .radians (15 ** 2 ) # angle error should be set quite small
50
+ np .fill_diagonal (_covariance_matrix , 9 ) # 3 meter SD = 9 variance
51
+ _covariance_matrix [3 , 3 ] = np .radians (5 ** 2 ) # angle error should be set quite small
52
52
_covariance_matrix [4 , 4 ] = _covariance_matrix [3 , 3 ]
53
53
_covariance_matrix [5 , 5 ] = _covariance_matrix [3 , 3 ]
54
54
_COVARIANCE_LIST = _covariance_matrix .flatten ().tolist ()
@@ -66,7 +66,7 @@ class PoseNode(Node):
66
66
Stricter threshold for shallow matching because mistakes accumulate in VO
67
67
"""
68
68
69
- CONFIDENCE_THRESHOLD_DEEP_MATCH = 0.7
69
+ CONFIDENCE_THRESHOLD_DEEP_MATCH = 0.8
70
70
"""Confidence threshold for filtering out bad keypoint matches for
71
71
deep matching
72
72
"""
@@ -84,8 +84,19 @@ def __init__(self, *args, **kwargs):
84
84
self ._device = torch .device ("cuda" if torch .cuda .is_available () else "cpu" )
85
85
86
86
# Initialize DL model for map matching (noisy global position, no drift)
87
- self ._model = LoFTR (pretrained = "outdoor" )
88
- self ._model .to (self ._device )
87
+ self ._matcher = (
88
+ LightGlueMatcher (
89
+ "disk" ,
90
+ params = {
91
+ "filter_threshold" : self .CONFIDENCE_THRESHOLD_DEEP_MATCH ,
92
+ "depth_confidence" : - 1 ,
93
+ "width_confidence" : - 1 ,
94
+ },
95
+ )
96
+ .to (self ._device )
97
+ .eval ()
98
+ )
99
+ self ._extractor = DISK .from_pretrained ("depth" ).to (self ._device )
89
100
90
101
# Initialize ORB detector and brute force matcher for VO
91
102
# (smooth relative position with drift)
@@ -193,18 +204,16 @@ def _get_pose(
193
204
r_inv = r .T
194
205
camera_optical_position_in_world = - r_inv @ t
195
206
196
- # tf_.visualize_camera_position(
197
- # ref.copy(),
198
- # camera_optical_position_in_world,
199
- # f"Camera {'principal point' if shallow_inference else 'position'} "
200
- # f"in {'previous' if shallow_inference else 'world'} frame, "
201
- # f"{'shallow' if shallow_inference else 'deep'} inference",
202
- # )
203
-
204
- header = msg .query .header
207
+ tf_ .visualize_camera_position (
208
+ ref .copy (),
209
+ camera_optical_position_in_world ,
210
+ f"Camera { 'principal point' if shallow_inference else 'position' } "
211
+ f"in { 'previous' if shallow_inference else 'world' } frame, "
212
+ f"{ 'shallow' if shallow_inference else 'deep' } inference" ,
213
+ )
205
214
206
215
pose = tf_ .create_pose_msg (
207
- header .stamp ,
216
+ msg . query . header .stamp ,
208
217
cast (FrameID , "earth" ),
209
218
r_inv ,
210
219
camera_optical_position_in_world ,
@@ -348,24 +357,37 @@ def _process(
348
357
:return: Tuple of matched query image keypoints, and matched reference image
349
358
keypoints
350
359
"""
351
- if not shallow_inference : #
352
- if torch .cuda .is_available ():
353
- qry_tensor = torch .Tensor (qry [None , None ]).cuda () / 255.0
354
- ref_tensor = torch .Tensor (ref [None , None ]).cuda () / 255.0
355
- else :
356
- self .get_logger ().warning ("CUDA not available - using CPU." )
357
- qry_tensor = torch .Tensor (qry [None , None ]) / 255.0
358
- ref_tensor = torch .Tensor (ref [None , None ]) / 255.0
359
-
360
- with torch .no_grad ():
361
- results = self ._model ({"image0" : qry_tensor , "image1" : ref_tensor })
362
-
363
- conf = results ["confidence" ].cpu ().numpy ()
364
- good = conf > self .CONFIDENCE_THRESHOLD_DEEP_MATCH
365
- mkp_qry = results ["keypoints0" ].cpu ().numpy ()[good , :]
366
- mkp_ref = results ["keypoints1" ].cpu ().numpy ()[good , :]
360
+ if not shallow_inference :
361
+ qry_tensor = torch .Tensor (qry [None , None ]).to (self ._device ) / 255.0
362
+ ref_tensor = torch .Tensor (ref [None , None ]).to (self ._device ) / 255.0
363
+ qry_tensor = qry_tensor .expand (- 1 , 3 , - 1 , - 1 )
364
+ ref_tensor = ref_tensor .expand (- 1 , 3 , - 1 , - 1 )
365
+
366
+ with torch .inference_mode ():
367
+ input = torch .cat ([qry_tensor , ref_tensor ], dim = 0 )
368
+ # limit number of features to run faster, None means no limit i.e.
369
+ # slow but accurate
370
+ max_keypoints = 1024 # 4096 # None
371
+ feat_qry , feat_ref = self ._extractor (
372
+ input , max_keypoints , pad_if_not_divisible = True
373
+ )
374
+ kp_qry , desc_qry = feat_qry .keypoints , feat_qry .descriptors
375
+ kp_ref , desc_ref = feat_ref .keypoints , feat_ref .descriptors
376
+ lafs_qry = laf_from_center_scale_ori (
377
+ kp_qry [None ], torch .ones (1 , len (kp_qry ), 1 , 1 , device = self ._device )
378
+ )
379
+ lafs_ref = laf_from_center_scale_ori (
380
+ kp_ref [None ], torch .ones (1 , len (kp_ref ), 1 , 1 , device = self ._device )
381
+ )
382
+ dists , match_indices = self ._matcher (
383
+ desc_qry , desc_ref , lafs_qry , lafs_ref
384
+ )
385
+
386
+ mkp_qry = kp_qry [match_indices [:, 0 ]].cpu ().numpy ()
387
+ mkp_ref = kp_ref [match_indices [:, 1 ]].cpu ().numpy ()
367
388
368
389
return mkp_qry , mkp_ref
390
+
369
391
else :
370
392
# find the keypoints and descriptors with ORB
371
393
kp_qry , desc_qry = self ._orb .detectAndCompute (qry , None )
0 commit comments