Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
RBXFeatFeatureTracker.py
35import numpy as np
36from enum import Enum
37import torch
38
39from visp.core import ColVector, Matrix, ArrayInt2D
40from visp.core import CameraParameters, HomogeneousMatrix, PixelMeterConversion, Robust
41from visp.core import ImageGray, ImageRGBa
42from visp.core import Color
43from visp.core import Display
44
45from visp.rbt import RBFeatureTracker, RBFeatureTrackerInput, RBFeatureTrackerFactory
46from visp.rbt import TemporalWeighting
47
48from visp.python.rbt import TrackedDescriptorMap
49from visp.python.rbt.xfeat import XFeatTrackingBackend
50
51class RBXFeatFeatureTracker(RBFeatureTracker):
52 """A trackable feature implementation for the RBT that relies on XFeat for keypoint extraction.
53 XFeat is used to extract keypoints (2D) along with their descriptors in every frame.
54 At the end of every tracking iteration, Novel keypoints are added in a map (see visp.python.rbt.TrackedDescriptorMap)
55 Using the 2D keypoint location, the newly computed 3D camera pose and the render information
56
57 This feature relies on the XFeat tracker backend,
58 which should be customized to set the number of keypoints to retrieve each frame along with the minimum similarity score for the matching step.
59
60 The error that is computed is either the reprojection error (2D, in normalized image plane coordinates, similar to an IBVS)
61 or in 3D (in metric space) if the option is chosen and an RGB-D camera is used.
62
63 An example JSON configuration of the tracker is:
64 {
65 "type": "xfeat",
66 "weight": 1,
67 "use_3d": false,
68 "display": true,
69 "numPoints": 4096,
70 "reprojectionThreshold": 5,
71 "minDistNewPoint": 0.0,
72 "maxDepthErrorVisible": 0.02,
73 "maxDepthErrorCandidate": 0.02
74 }
75
76 See the TrackedDescriptorMap class for some of the settings description.
77
78 """
79
80 class DisplayType(Enum):
81 SIMPLE = 'simple'
82 SIMPLE_MODEL_AND_PROJ = 'simpleModelAndProj'
83 ERROR = 'error'
84 WEIGHT_AND_ERROR = 'weightAndError'
85
86
87 def __init__(self, backend: XFeatTrackingBackend):
88 RBFeatureTracker.__init__(self)
89
90 self.backend = backend
91 self.object_map = TrackedDescriptorMap(num_points=2048,
92 reprojection_threshold=5.0,
93 min_dist_new_point=1e-4,
94 max_depth_error_visible=1e-2)
96 self.iter = 0
97 self.use_3d = False
98 self.last_cMo = None
99 self.robust = Robust()
101 self.display_type = RBXFeatFeatureTracker.DisplayType.SIMPLE
102
103
104 def requiresRGB(self) -> bool:
105 return True
106 def requiresDepth(self) -> bool:
107 return self.use_3d
109 return True
110
111 def load_settings(self, dict_rep):
112 import json
113 self.setTrackerWeight(TemporalWeighting.parseTemporalWeightingRawJson(json.dumps(dict_rep['weight'])))
114 self.use_3d = dict_rep.get('use3d', self.use_3d)
115 self.setFeaturesShouldBeDisplayed(dict_rep.get('display', False))
116 self.object_map.parse_settings(dict_rep)
117
118 def onTrackingIterStart(self, frame: RBFeatureTrackerInput, cMo: HomogeneousMatrix):
119 self.cov.resize(6, 6, 0)
120 self.LTL.resize(6, 6, 0)
121 self.LTR.resize(6, 0)
122 self.numFeatures = 0
123 # Update map
124 if self.current_representation is not None:
125 self.removed_indices = self.object_map.update(frame, cMo,
126 frame.renders.depth,
127 frame.depth,
130 self.current_representation.keypoints,
131 self.current_representation.descriptors)
132
133
134 def extractFeatures(self, frame: RBFeatureTrackerInput, previousFrame: RBFeatureTrackerInput, cMo: HomogeneousMatrix):
135 self.frame = frame
136 self.backend.process_frame(frame, self.iter)
137
138 def trackFeatures(self, frame: RBFeatureTrackerInput, previousFrame: RBFeatureTrackerInput, cMo: HomogeneousMatrix):
139
140
141 self.current_representation = self.backend.get_current_object_data()
142 self.idx_curr_obj_matched, self.idx_object_map_matched = None, None
143
144 with torch.no_grad():
145 if self.object_map.has_points() and self.current_representation is not None:
146 h, w = frame.I.getRows(), frame.I.getCols()
147 depth_map = frame.renders.depth
148 import time
149 visible_indices = np.ascontiguousarray(self.object_map.point_map.getVisiblePoints(h, w, frame.cam, frame.renders.cMo, depth_map))
150 if len(visible_indices) > 0:
151 visible_object_descriptors = self.object_map.descriptors[visible_indices]
152 self.idx_curr_obj_matched, self.idx_object_map_matched = self.backend.match(self.current_representation.descriptors, visible_object_descriptors)
153 # else:
154 # self.idx_curr_obj_matched, self.idx_object_map_matched, self.matching_weight = self.backend.match_unidirectional(self.current_representation.descriptors, visible_object_descriptors)
155 # self.matching_weight = self.matching_weight.cpu().numpy()
156 self.idx_curr_obj_matched = self.idx_curr_obj_matched.cpu().numpy()
157 self.idx_object_map_matched = visible_indices[self.idx_object_map_matched.cpu().numpy()]
158
159 self.numFeatures = 0
160 if self.idx_curr_obj_matched is not None:
161
162 # If we're using 3D points, retain only those with a valid depth
163 if self.use_3d:
164 valid_indices = []
165 self.Zs = []
166 for i, kp in enumerate(self.current_representation.keypoints[self.idx_curr_obj_matched]):
167 Z = frame.depth[int(kp[1]), int(kp[0])]
168 if Z > 0.0:
169 valid_indices.append(i)
170 self.Zs.append(Z)
171 self.Zs = np.ascontiguousarray(self.Zs)
172 self.idx_curr_obj_matched = self.idx_curr_obj_matched[valid_indices]
173 self.idx_object_map_matched = self.idx_object_map_matched[valid_indices]
174
175 self.numFeatures = len(self.idx_curr_obj_matched) * (2 if not self.use_3d else 3)
176
177
178 def initVVS(self, frame: RBFeatureTrackerInput, _previousFrame: RBFeatureTrackerInput, _cMo: HomogeneousMatrix):
179 self.L.resize(self.numFeatures, 6, False, False)
180 self.error.resize(self.numFeatures, False)
181 self.weighted_error.resize(self.numFeatures, False)
182 self.weights.resize(self.numFeatures, False)
183 self.covWeightDiag.resize(self.numFeatures, False)
184
185 if self.idx_object_map_matched is not None:
186 self.visp_indices_matched = ArrayInt2D.view(self.idx_object_map_matched[..., None].astype(np.int32))
187 current_px_matched = self.current_representation.keypoints[self.idx_curr_obj_matched]
188 xoc, yoc = PixelMeterConversion.convertPoints(frame.cam, current_px_matched[:, 0], current_px_matched[:, 1])
189 if not self.use_3d:
190 self.current_xy_obj = Matrix.view(np.ascontiguousarray(np.concatenate((xoc[..., None], yoc[..., None]), axis=-1), dtype=np.float64))
191 else:
192 xyz = np.concatenate([arr[:, None] for arr in (xoc * self.Zs, yoc * self.Zs, self.Zs)], axis=-1, dtype=np.float64)
193 self.observations = Matrix.view(xyz)
194 else:
195 self.visp_indices_matched = ArrayInt2D()
196
197 self.error_per_point = ColVector(self.numFeatures // (2 if not self.use_3d else 3))
198 self.weight_per_point = ColVector(self.error_per_point.getCols())
199
200 def computeVVSIter(self, frame: RBFeatureTrackerInput, cMo: HomogeneousMatrix, iteration: int):
201
202 if self.numFeatures < 8 * (3 if self.use_3d else 2):
203 self.numFeatures = 0
204 return
205 # Error and Jacobian computation
206 if not self.use_3d:
207 self.object_map.point_map.computeReprojectionErrorAndJacobian(self.visp_indices_matched, cMo, self.current_xy_obj, self.L, self.error)
208 else:
209 self.object_map.point_map.compute3DErrorAndJacobian(self.visp_indices_matched, cMo, self.observations, self.L, self.error)
210
211 # Compute Min MAD for M-estimator
212 threshold = 0.0
213 if not self.use_3d:
214 # Compute min threshold as relative to the bounding box (object size in the iamge)
215 bb = frame.renders.boundingBox
216 rect_diagonal = np.sqrt(bb.getHeight() ** 2 + bb.getWidth() ** 2)
217 cam = frame.cam
218 threshold = (rect_diagonal / np.sqrt(cam.get_px() ** 2 + cam.get_py() ** 2)) * 0.001
219 else:
220 diff = frame.renders.zFar - frame.renders.zNear
221 threshold = diff * 0.001
222
223 # M-estimator weighting performed relative to the euclidean distance between points, not the individual component error
224 self.robust.setMinMedianAbsoluteDeviation(threshold)
225 error_per_point = np.linalg.norm(self.error.numpy().reshape((-1, 2 if not self.use_3d else 3)), axis=-1)
226 self.error_per_point[:] = error_per_point
227
228 self.robust.MEstimator(Robust.TUKEY, self.error_per_point, self.weight_per_point)
229
230 # Compute optimizer quantities
231 self.weights[:] = np.repeat(self.weight_per_point, 2 if not self.use_3d else 3)
232 # else:
233 # self.weights[:] = np.repeat(self.weight_per_point.numpy() * self.matching_weight, 2 if not self.use_3d else 3)
234 self.updateOptimizerTerms(cMo)
235
236 def onTrackingIterEnd(self, cMo: HomogeneousMatrix):
237
238 # Update displayed points before removing them from map
239 if self.idx_curr_obj_matched is not None and self.numFeatures > 0 and self.idx_object_map_matched is not None:
240 cX, xs, uvs = Matrix(), Matrix(), Matrix()
241 map_indices = ArrayInt2D.view(np.ascontiguousarray(self.idx_object_map_matched[:, None]).astype(np.int32))
242 self.object_map.point_map.project(self.frame.cam, map_indices, cMo, cX, xs, uvs)
243 self.pixel_pos_visible = uvs.numpy().copy()
244
245
246 self.frame = None
247 self.iter += 1
248
249 def reset(self):
250 self.object_map.reset()
251 self.current_representation = None
252 self.current_xy_obj = None
253 self.numFeatures = 0
254 self.iter = 0
255 self.frame = None
256 self.idx_curr_obj_matched = None
257 self.idx_object_map_matched = None
258
259 def display(self, cam: CameraParameters, I: ImageGray, IRGB: ImageRGBa, I_depth: ImageGray):
260 if self.idx_curr_obj_matched is None or self.numFeatures == 0:
261 return
262 ps = self.current_representation.keypoints[self.idx_curr_obj_matched]
263 uvs = self.pixel_pos_visible
264 error = np.linalg.norm(uvs - ps, axis=1)
265
266 max_error_display = np.maximum(np.max(error), self.object_map.point_map.getOutlierReprojectionErrorThreshold())
267 ps = np.rint(ps).astype(np.int32)
268 uvs = np.rint(uvs).astype(np.int32)
269 if self.display_type == RBXFeatFeatureTracker.DisplayType.SIMPLE:
270 Display.displayCrosses(IRGB, ps[:, 1], ps[:, 0], 8, Color.green, 1)
271 elif self.display_type == RBXFeatFeatureTracker.DisplayType.SIMPLE_MODEL_AND_PROJ:
272 Display.displayCrosses(IRGB, ps[:, 1], ps[:, 0], 8, Color.green, 1)
273 Display.displayCrosses(IRGB, uvs[:, 1], np.rint(uvs[:, 0]).astype(np.int32), 8, Color.red, 1)
274
275 elif self.display_type == RBXFeatFeatureTracker.DisplayType.WEIGHT_AND_ERROR:
276 weights = (np.sum(self.weights.numpy().reshape((len(error), 2)), axis=-1) / 2.0)
277 c = Color()
278 for p in range(len(error)):
279 c.setColor(
280 np.rint((error[p] / max_error_display) * 255).astype(np.uint8),
281 np.rint(weights[p] * 255.0).astype(np.uint8),
282 0, 255
283 )
284 Display.displayCircleStatic(IRGB, ps[p, 1], ps[p, 0], 2, c, True, 1)
285 else:
286 raise RuntimeError('Display time not implemented')
287
288# def create_instance():
289# return XFeatFeatureTracker(None)
290
291# TrackerPyBase.register_type('xfeat', create_instance)
display(self, CameraParameters cam, ImageGray I, ImageRGBa IRGB, ImageGray I_depth)
extractFeatures(self, RBFeatureTrackerInput frame, RBFeatureTrackerInput previousFrame, HomogeneousMatrix cMo)
initVVS(self, RBFeatureTrackerInput frame, RBFeatureTrackerInput _previousFrame, HomogeneousMatrix _cMo)
trackFeatures(self, RBFeatureTrackerInput frame, RBFeatureTrackerInput previousFrame, HomogeneousMatrix cMo)
computeVVSIter(self, RBFeatureTrackerInput frame, HomogeneousMatrix cMo, int iteration)
onTrackingIterStart(self, RBFeatureTrackerInput frame, HomogeneousMatrix cMo)