TrackDescriptor.cpp
Go to the documentation of this file.
1 /*
2  * OpenVINS: An Open Platform for Visual-Inertial Research
3  * Copyright (C) 2018-2023 Patrick Geneva
4  * Copyright (C) 2018-2023 Guoquan Huang
5  * Copyright (C) 2018-2023 OpenVINS Contributors
6  * Copyright (C) 2018-2019 Kevin Eckenhoff
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "TrackDescriptor.h"
23 
24 #include <opencv2/features2d.hpp>
25 
26 #include "Grider_FAST.h"
27 #include "cam/CamBase.h"
28 #include "feat/Feature.h"
29 #include "feat/FeatureDatabase.h"
30 
31 using namespace ov_core;
32 
34 
35  // Error check that we have all the data
36  if (message.sensor_ids.empty() || message.sensor_ids.size() != message.images.size() || message.images.size() != message.masks.size()) {
37  PRINT_ERROR(RED "[ERROR]: MESSAGE DATA SIZES DO NOT MATCH OR EMPTY!!!\n" RESET);
38  PRINT_ERROR(RED "[ERROR]: - message.sensor_ids.size() = %zu\n" RESET, message.sensor_ids.size());
39  PRINT_ERROR(RED "[ERROR]: - message.images.size() = %zu\n" RESET, message.images.size());
40  PRINT_ERROR(RED "[ERROR]: - message.masks.size() = %zu\n" RESET, message.masks.size());
41  std::exit(EXIT_FAILURE);
42  }
43 
44  // Either call our stereo or monocular version
45  // If we are doing binocular tracking, then we should parallize our tracking
46  size_t num_images = message.images.size();
47  if (num_images == 1) {
48  feed_monocular(message, 0);
49  } else if (num_images == 2 && use_stereo) {
50  feed_stereo(message, 0, 1);
51  } else if (!use_stereo) {
52  parallel_for_(cv::Range(0, (int)num_images), LambdaBody([&](const cv::Range &range) {
53  for (int i = range.start; i < range.end; i++) {
54  feed_monocular(message, i);
55  }
56  }));
57  } else {
58  PRINT_ERROR(RED "[ERROR]: invalid number of images passed %zu, we only support mono or stereo tracking", num_images);
59  std::exit(EXIT_FAILURE);
60  }
61 }
62 
63 void TrackDescriptor::feed_monocular(const CameraData &message, size_t msg_id) {
64 
65  // Start timing
66  rT1 = boost::posix_time::microsec_clock::local_time();
67 
68  // Lock this data feed for this camera
69  size_t cam_id = message.sensor_ids.at(msg_id);
70  std::lock_guard<std::mutex> lck(mtx_feeds.at(cam_id));
71 
72  // Histogram equalize
73  cv::Mat img, mask;
74  if (histogram_method == HistogramMethod::HISTOGRAM) {
75  cv::equalizeHist(message.images.at(msg_id), img);
76  } else if (histogram_method == HistogramMethod::CLAHE) {
77  double eq_clip_limit = 10.0;
78  cv::Size eq_win_size = cv::Size(8, 8);
79  cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(eq_clip_limit, eq_win_size);
80  clahe->apply(message.images.at(msg_id), img);
81  } else {
82  img = message.images.at(msg_id);
83  }
84  mask = message.masks.at(msg_id);
85 
86  // If we are the first frame (or have lost tracking), initialize our descriptors
87  if (pts_last.find(cam_id) == pts_last.end() || pts_last[cam_id].empty()) {
88  std::vector<cv::KeyPoint> good_left;
89  std::vector<size_t> good_ids_left;
90  cv::Mat good_desc_left;
91  perform_detection_monocular(img, mask, good_left, good_desc_left, good_ids_left);
92  std::lock_guard<std::mutex> lckv(mtx_last_vars);
93  img_last[cam_id] = img;
94  img_mask_last[cam_id] = mask;
95  pts_last[cam_id] = good_left;
96  ids_last[cam_id] = good_ids_left;
97  desc_last[cam_id] = good_desc_left;
98  return;
99  }
100 
101  // Our new keypoints and descriptor for the new image
102  std::vector<cv::KeyPoint> pts_new;
103  cv::Mat desc_new;
104  std::vector<size_t> ids_new;
105 
106  // First, extract new descriptors for this new image
107  perform_detection_monocular(img, mask, pts_new, desc_new, ids_new);
108  rT2 = boost::posix_time::microsec_clock::local_time();
109 
110  // Our matches temporally
111  std::vector<cv::DMatch> matches_ll;
112 
113  // Lets match temporally
114  robust_match(pts_last[cam_id], pts_new, desc_last[cam_id], desc_new, cam_id, cam_id, matches_ll);
115  rT3 = boost::posix_time::microsec_clock::local_time();
116 
117  // Get our "good tracks"
118  std::vector<cv::KeyPoint> good_left;
119  std::vector<size_t> good_ids_left;
120  cv::Mat good_desc_left;
121 
122  // Count how many we have tracked from the last time
123  int num_tracklast = 0;
124 
125  // Loop through all current left to right points
126  // We want to see if any of theses have matches to the previous frame
127  // If we have a match new->old then we want to use that ID instead of the new one
128  for (size_t i = 0; i < pts_new.size(); i++) {
129 
130  // Loop through all left matches, and find the old "train" id
131  int idll = -1;
132  for (size_t j = 0; j < matches_ll.size(); j++) {
133  if (matches_ll[j].trainIdx == (int)i) {
134  idll = matches_ll[j].queryIdx;
135  }
136  }
137 
138  // Then lets replace the current ID with the old ID if found
139  // Else just append the current feature and its unique ID
140  good_left.push_back(pts_new[i]);
141  good_desc_left.push_back(desc_new.row((int)i));
142  if (idll != -1) {
143  good_ids_left.push_back(ids_last[cam_id][idll]);
144  num_tracklast++;
145  } else {
146  good_ids_left.push_back(ids_new[i]);
147  }
148  }
149  rT4 = boost::posix_time::microsec_clock::local_time();
150 
151  // Update our feature database, with theses new observations
152  for (size_t i = 0; i < good_left.size(); i++) {
153  cv::Point2f npt_l = camera_calib.at(cam_id)->undistort_cv(good_left.at(i).pt);
154  database->update_feature(good_ids_left.at(i), message.timestamp, cam_id, good_left.at(i).pt.x, good_left.at(i).pt.y, npt_l.x, npt_l.y);
155  }
156 
157  // Debug info
158  // PRINT_DEBUG("LtoL = %d | good = %d | fromlast = %d\n",(int)matches_ll.size(),(int)good_left.size(),num_tracklast);
159 
160  // Move forward in time
161  {
162  std::lock_guard<std::mutex> lckv(mtx_last_vars);
163  img_last[cam_id] = img;
164  img_mask_last[cam_id] = mask;
165  pts_last[cam_id] = good_left;
166  ids_last[cam_id] = good_ids_left;
167  desc_last[cam_id] = good_desc_left;
168  }
169  rT5 = boost::posix_time::microsec_clock::local_time();
170 
171  // Our timing information
172  PRINT_ALL("[TIME-DESC]: %.4f seconds for detection\n", (rT2 - rT1).total_microseconds() * 1e-6);
173  PRINT_ALL("[TIME-DESC]: %.4f seconds for matching\n", (rT3 - rT2).total_microseconds() * 1e-6);
174  PRINT_ALL("[TIME-DESC]: %.4f seconds for merging\n", (rT4 - rT3).total_microseconds() * 1e-6);
175  PRINT_ALL("[TIME-DESC]: %.4f seconds for feature DB update (%d features)\n", (rT5 - rT4).total_microseconds() * 1e-6,
176  (int)good_left.size());
177  PRINT_ALL("[TIME-DESC]: %.4f seconds for total\n", (rT5 - rT1).total_microseconds() * 1e-6);
178 }
179 
180 void TrackDescriptor::feed_stereo(const CameraData &message, size_t msg_id_left, size_t msg_id_right) {
181 
182  // Start timing
183  rT1 = boost::posix_time::microsec_clock::local_time();
184 
185  // Lock this data feed for this camera
186  size_t cam_id_left = message.sensor_ids.at(msg_id_left);
187  size_t cam_id_right = message.sensor_ids.at(msg_id_right);
188  std::lock_guard<std::mutex> lck1(mtx_feeds.at(cam_id_left));
189  std::lock_guard<std::mutex> lck2(mtx_feeds.at(cam_id_right));
190 
191  // Histogram equalize images
192  cv::Mat img_left, img_right, mask_left, mask_right;
193  if (histogram_method == HistogramMethod::HISTOGRAM) {
194  cv::equalizeHist(message.images.at(msg_id_left), img_left);
195  cv::equalizeHist(message.images.at(msg_id_right), img_right);
196  } else if (histogram_method == HistogramMethod::CLAHE) {
197  double eq_clip_limit = 10.0;
198  cv::Size eq_win_size = cv::Size(8, 8);
199  cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(eq_clip_limit, eq_win_size);
200  clahe->apply(message.images.at(msg_id_left), img_left);
201  clahe->apply(message.images.at(msg_id_right), img_right);
202  } else {
203  img_left = message.images.at(msg_id_left);
204  img_right = message.images.at(msg_id_right);
205  }
206  mask_left = message.masks.at(msg_id_left);
207  mask_right = message.masks.at(msg_id_right);
208 
209  // If we are the first frame (or have lost tracking), initialize our descriptors
210  if (pts_last[cam_id_left].empty() || pts_last[cam_id_right].empty()) {
211  std::vector<cv::KeyPoint> good_left, good_right;
212  std::vector<size_t> good_ids_left, good_ids_right;
213  cv::Mat good_desc_left, good_desc_right;
214  perform_detection_stereo(img_left, img_right, mask_left, mask_right, good_left, good_right, good_desc_left, good_desc_right,
215  cam_id_left, cam_id_right, good_ids_left, good_ids_right);
216  std::lock_guard<std::mutex> lckv(mtx_last_vars);
217  img_last[cam_id_left] = img_left;
218  img_last[cam_id_right] = img_right;
219  img_mask_last[cam_id_left] = mask_left;
220  img_mask_last[cam_id_right] = mask_right;
221  pts_last[cam_id_left] = good_left;
222  pts_last[cam_id_right] = good_right;
223  ids_last[cam_id_left] = good_ids_left;
224  ids_last[cam_id_right] = good_ids_right;
225  desc_last[cam_id_left] = good_desc_left;
226  desc_last[cam_id_right] = good_desc_right;
227  return;
228  }
229 
230  // Our new keypoints and descriptor for the new image
231  std::vector<cv::KeyPoint> pts_left_new, pts_right_new;
232  cv::Mat desc_left_new, desc_right_new;
233  std::vector<size_t> ids_left_new, ids_right_new;
234 
235  // First, extract new descriptors for this new image
236  perform_detection_stereo(img_left, img_right, mask_left, mask_right, pts_left_new, pts_right_new, desc_left_new, desc_right_new,
237  cam_id_left, cam_id_right, ids_left_new, ids_right_new);
238  rT2 = boost::posix_time::microsec_clock::local_time();
239 
240  // Our matches temporally
241  std::vector<cv::DMatch> matches_ll, matches_rr;
242  parallel_for_(cv::Range(0, 2), LambdaBody([&](const cv::Range &range) {
243  for (int i = range.start; i < range.end; i++) {
244  bool is_left = (i == 0);
245  robust_match(pts_last[is_left ? cam_id_left : cam_id_right], is_left ? pts_left_new : pts_right_new,
246  desc_last[is_left ? cam_id_left : cam_id_right], is_left ? desc_left_new : desc_right_new,
247  is_left ? cam_id_left : cam_id_right, is_left ? cam_id_left : cam_id_right,
248  is_left ? matches_ll : matches_rr);
249  }
250  }));
251  rT3 = boost::posix_time::microsec_clock::local_time();
252 
253  // Get our "good tracks"
254  std::vector<cv::KeyPoint> good_left, good_right;
255  std::vector<size_t> good_ids_left, good_ids_right;
256  cv::Mat good_desc_left, good_desc_right;
257 
258  // Points must be of equal size
259  assert(pts_last[cam_id_left].size() == pts_last[cam_id_right].size());
260  assert(pts_left_new.size() == pts_right_new.size());
261 
262  // Count how many we have tracked from the last time
263  int num_tracklast = 0;
264 
265  // Loop through all current left to right points
266  // We want to see if any of theses have matches to the previous frame
267  // If we have a match new->old then we want to use that ID instead of the new one
268  for (size_t i = 0; i < pts_left_new.size(); i++) {
269 
270  // Loop through all left matches, and find the old "train" id
271  int idll = -1;
272  for (size_t j = 0; j < matches_ll.size(); j++) {
273  if (matches_ll[j].trainIdx == (int)i) {
274  idll = matches_ll[j].queryIdx;
275  }
276  }
277 
278  // Loop through all left matches, and find the old "train" id
279  int idrr = -1;
280  for (size_t j = 0; j < matches_rr.size(); j++) {
281  if (matches_rr[j].trainIdx == (int)i) {
282  idrr = matches_rr[j].queryIdx;
283  }
284  }
285 
286  // If we found a good stereo track from left to left, and right to right
287  // Then lets replace the current ID with the old ID
288  // We also check that we are linked to the same past ID value
289  if (idll != -1 && idrr != -1 && ids_last[cam_id_left][idll] == ids_last[cam_id_right][idrr]) {
290  good_left.push_back(pts_left_new[i]);
291  good_right.push_back(pts_right_new[i]);
292  good_desc_left.push_back(desc_left_new.row((int)i));
293  good_desc_right.push_back(desc_right_new.row((int)i));
294  good_ids_left.push_back(ids_last[cam_id_left][idll]);
295  good_ids_right.push_back(ids_last[cam_id_right][idrr]);
296  num_tracklast++;
297  } else {
298  // Else just append the current feature and its unique ID
299  good_left.push_back(pts_left_new[i]);
300  good_right.push_back(pts_right_new[i]);
301  good_desc_left.push_back(desc_left_new.row((int)i));
302  good_desc_right.push_back(desc_right_new.row((int)i));
303  good_ids_left.push_back(ids_left_new[i]);
304  good_ids_right.push_back(ids_left_new[i]);
305  }
306  }
307  rT4 = boost::posix_time::microsec_clock::local_time();
308 
309  //===================================================================================
310  //===================================================================================
311 
312  // Update our feature database, with theses new observations
313  for (size_t i = 0; i < good_left.size(); i++) {
314  // Assert that our IDs are the same
315  assert(good_ids_left.at(i) == good_ids_right.at(i));
316  // Try to undistort the point
317  cv::Point2f npt_l = camera_calib.at(cam_id_left)->undistort_cv(good_left.at(i).pt);
318  cv::Point2f npt_r = camera_calib.at(cam_id_right)->undistort_cv(good_right.at(i).pt);
319  // Append to the database
320  database->update_feature(good_ids_left.at(i), message.timestamp, cam_id_left, good_left.at(i).pt.x, good_left.at(i).pt.y, npt_l.x,
321  npt_l.y);
322  database->update_feature(good_ids_left.at(i), message.timestamp, cam_id_right, good_right.at(i).pt.x, good_right.at(i).pt.y, npt_r.x,
323  npt_r.y);
324  }
325 
326  // Debug info
327  // PRINT_DEBUG("LtoL = %d | RtoR = %d | LtoR = %d | good = %d | fromlast = %d\n", (int)matches_ll.size(),
328  // (int)matches_rr.size(),(int)ids_left_new.size(),(int)good_left.size(),num_tracklast);
329 
330  // Move forward in time
331  {
332  std::lock_guard<std::mutex> lckv(mtx_last_vars);
333  img_last[cam_id_left] = img_left;
334  img_last[cam_id_right] = img_right;
335  img_mask_last[cam_id_left] = mask_left;
336  img_mask_last[cam_id_right] = mask_right;
337  pts_last[cam_id_left] = good_left;
338  pts_last[cam_id_right] = good_right;
339  ids_last[cam_id_left] = good_ids_left;
340  ids_last[cam_id_right] = good_ids_right;
341  desc_last[cam_id_left] = good_desc_left;
342  desc_last[cam_id_right] = good_desc_right;
343  }
344  rT5 = boost::posix_time::microsec_clock::local_time();
345 
346  // Our timing information
347  PRINT_ALL("[TIME-DESC]: %.4f seconds for detection\n", (rT2 - rT1).total_microseconds() * 1e-6);
348  PRINT_ALL("[TIME-DESC]: %.4f seconds for matching\n", (rT3 - rT2).total_microseconds() * 1e-6);
349  PRINT_ALL("[TIME-DESC]: %.4f seconds for merging\n", (rT4 - rT3).total_microseconds() * 1e-6);
350  PRINT_ALL("[TIME-DESC]: %.4f seconds for feature DB update (%d features)\n", (rT5 - rT4).total_microseconds() * 1e-6,
351  (int)good_left.size());
352  PRINT_ALL("[TIME-DESC]: %.4f seconds for total\n", (rT5 - rT1).total_microseconds() * 1e-6);
353 }
354 
355 void TrackDescriptor::perform_detection_monocular(const cv::Mat &img0, const cv::Mat &mask0, std::vector<cv::KeyPoint> &pts0,
356  cv::Mat &desc0, std::vector<size_t> &ids0) {
357 
358  // Assert that we need features
359  assert(pts0.empty());
360 
361  // Extract our features (use FAST with griding)
362  std::vector<cv::KeyPoint> pts0_ext;
363  Grider_FAST::perform_griding(img0, mask0, pts0_ext, num_features, grid_x, grid_y, threshold, true);
364 
365  // For all new points, extract their descriptors
366  cv::Mat desc0_ext;
367  this->orb0->compute(img0, pts0_ext, desc0_ext);
368 
369  // Create a 2D occupancy grid for this current image
370  // Note that we scale this down, so that each grid point is equal to a set of pixels
371  // This means that we will reject points that less then grid_px_size points away then existing features
372  cv::Size size((int)((float)img0.cols / (float)min_px_dist), (int)((float)img0.rows / (float)min_px_dist));
373  cv::Mat grid_2d = cv::Mat::zeros(size, CV_8UC1);
374 
375  // For all good matches, lets append to our returned vectors
376  // NOTE: if we multi-thread this atomic can cause some randomness due to multiple thread detecting features
377  // NOTE: this is due to the fact that we select update features based on feat id
378  // NOTE: thus the order will matter since we try to select oldest (smallest id) to update with
379  // NOTE: not sure how to remove... maybe a better way?
380  for (size_t i = 0; i < pts0_ext.size(); i++) {
381  // Get current left keypoint, check that it is in bounds
382  cv::KeyPoint kpt = pts0_ext.at(i);
383  int x = (int)kpt.pt.x;
384  int y = (int)kpt.pt.y;
385  int x_grid = (int)(kpt.pt.x / (float)min_px_dist);
386  int y_grid = (int)(kpt.pt.y / (float)min_px_dist);
387  if (x_grid < 0 || x_grid >= size.width || y_grid < 0 || y_grid >= size.height || x < 0 || x >= img0.cols || y < 0 || y >= img0.rows) {
388  continue;
389  }
390  // Check if this keypoint is near another point
391  if (grid_2d.at<uint8_t>(y_grid, x_grid) > 127)
392  continue;
393  // Else we are good, append our keypoints and descriptors
394  pts0.push_back(pts0_ext.at(i));
395  desc0.push_back(desc0_ext.row((int)i));
396  // Set our IDs to be unique IDs here, will later replace with corrected ones, after temporal matching
397  size_t temp = ++currid;
398  ids0.push_back(temp);
399  grid_2d.at<uint8_t>(y_grid, x_grid) = 255;
400  }
401 }
402 
403 void TrackDescriptor::perform_detection_stereo(const cv::Mat &img0, const cv::Mat &img1, const cv::Mat &mask0, const cv::Mat &mask1,
404  std::vector<cv::KeyPoint> &pts0, std::vector<cv::KeyPoint> &pts1, cv::Mat &desc0,
405  cv::Mat &desc1, size_t cam_id0, size_t cam_id1, std::vector<size_t> &ids0,
406  std::vector<size_t> &ids1) {
407 
408  // Assert that we need features
409  assert(pts0.empty());
410  assert(pts1.empty());
411 
412  // Extract our features (use FAST with griding), and their descriptors
413  std::vector<cv::KeyPoint> pts0_ext, pts1_ext;
414  cv::Mat desc0_ext, desc1_ext;
415  parallel_for_(cv::Range(0, 2), LambdaBody([&](const cv::Range &range) {
416  for (int i = range.start; i < range.end; i++) {
417  bool is_left = (i == 0);
418  Grider_FAST::perform_griding(is_left ? img0 : img1, is_left ? mask0 : mask1, is_left ? pts0_ext : pts1_ext,
419  num_features, grid_x, grid_y, threshold, true);
420  (is_left ? orb0 : orb1)->compute(is_left ? img0 : img1, is_left ? pts0_ext : pts1_ext, is_left ? desc0_ext : desc1_ext);
421  }
422  }));
423 
424  // Do matching from the left to the right image
425  std::vector<cv::DMatch> matches;
426  robust_match(pts0_ext, pts1_ext, desc0_ext, desc1_ext, cam_id0, cam_id1, matches);
427 
428  // Create a 2D occupancy grid for this current image
429  // Note that we scale this down, so that each grid point is equal to a set of pixels
430  // This means that we will reject points that less then grid_px_size points away then existing features
431  cv::Size size0((int)((float)img0.cols / (float)min_px_dist), (int)((float)img0.rows / (float)min_px_dist));
432  cv::Mat grid_2d_0 = cv::Mat::zeros(size0, CV_8UC1);
433  cv::Size size1((int)((float)img1.cols / (float)min_px_dist), (int)((float)img1.rows / (float)min_px_dist));
434  cv::Mat grid_2d_1 = cv::Mat::zeros(size1, CV_8UC1);
435 
436  // For all good matches, lets append to our returned vectors
437  for (size_t i = 0; i < matches.size(); i++) {
438 
439  // Get our ids
440  int index_pt0 = matches.at(i).queryIdx;
441  int index_pt1 = matches.at(i).trainIdx;
442 
443  // Get current left/right keypoint, check that it is in bounds
444  cv::KeyPoint kpt0 = pts0_ext.at(index_pt0);
445  cv::KeyPoint kpt1 = pts1_ext.at(index_pt1);
446  int x0 = (int)kpt0.pt.x;
447  int y0 = (int)kpt0.pt.y;
448  int x0_grid = (int)(kpt0.pt.x / (float)min_px_dist);
449  int y0_grid = (int)(kpt0.pt.y / (float)min_px_dist);
450  if (x0_grid < 0 || x0_grid >= size0.width || y0_grid < 0 || y0_grid >= size0.height || x0 < 0 || x0 >= img0.cols || y0 < 0 ||
451  y0 >= img0.rows) {
452  continue;
453  }
454  int x1 = (int)kpt1.pt.x;
455  int y1 = (int)kpt1.pt.y;
456  int x1_grid = (int)(kpt1.pt.x / (float)min_px_dist);
457  int y1_grid = (int)(kpt1.pt.y / (float)min_px_dist);
458  if (x1_grid < 0 || x1_grid >= size1.width || y1_grid < 0 || y1_grid >= size1.height || x1 < 0 || x1 >= img0.cols || y1 < 0 ||
459  y1 >= img0.rows) {
460  continue;
461  }
462 
463  // Check if this keypoint is near another point
464  if (grid_2d_0.at<uint8_t>(y0_grid, x0_grid) > 127 || grid_2d_1.at<uint8_t>(y1_grid, x1_grid) > 127)
465  continue;
466 
467  // Append our keypoints and descriptors
468  pts0.push_back(pts0_ext[index_pt0]);
469  pts1.push_back(pts1_ext[index_pt1]);
470  desc0.push_back(desc0_ext.row(index_pt0));
471  desc1.push_back(desc1_ext.row(index_pt1));
472 
473  // Set our IDs to be unique IDs here, will later replace with corrected ones, after temporal matching
474  size_t temp = ++currid;
475  ids0.push_back(temp);
476  ids1.push_back(temp);
477  }
478 }
479 
480 void TrackDescriptor::robust_match(const std::vector<cv::KeyPoint> &pts0, const std::vector<cv::KeyPoint> &pts1, const cv::Mat &desc0,
481  const cv::Mat &desc1, size_t id0, size_t id1, std::vector<cv::DMatch> &matches) {
482 
483  // Our 1to2 and 2to1 match vectors
484  std::vector<std::vector<cv::DMatch>> matches0to1, matches1to0;
485 
486  // Match descriptors (return 2 nearest neighbours)
487  matcher->knnMatch(desc0, desc1, matches0to1, 2);
488  matcher->knnMatch(desc1, desc0, matches1to0, 2);
489 
490  // Do a ratio test for both matches
491  robust_ratio_test(matches0to1);
492  robust_ratio_test(matches1to0);
493 
494  // Finally do a symmetry test
495  std::vector<cv::DMatch> matches_good;
496  robust_symmetry_test(matches0to1, matches1to0, matches_good);
497 
498  // Convert points into points for RANSAC
499  std::vector<cv::Point2f> pts0_rsc, pts1_rsc;
500  for (size_t i = 0; i < matches_good.size(); i++) {
501  // Get our ids
502  int index_pt0 = matches_good.at(i).queryIdx;
503  int index_pt1 = matches_good.at(i).trainIdx;
504  // Push back just the 2d point
505  pts0_rsc.push_back(pts0[index_pt0].pt);
506  pts1_rsc.push_back(pts1[index_pt1].pt);
507  }
508 
509  // If we don't have enough points for ransac just return empty
510  if (pts0_rsc.size() < 10)
511  return;
512 
513  // Normalize these points, so we can then do ransac
514  // We don't want to do ransac on distorted image uvs since the mapping is nonlinear
515  std::vector<cv::Point2f> pts0_n, pts1_n;
516  for (size_t i = 0; i < pts0_rsc.size(); i++) {
517  pts0_n.push_back(camera_calib.at(id0)->undistort_cv(pts0_rsc.at(i)));
518  pts1_n.push_back(camera_calib.at(id1)->undistort_cv(pts1_rsc.at(i)));
519  }
520 
521  // Do RANSAC outlier rejection (note since we normalized the max pixel error is now in the normalized cords)
522  std::vector<uchar> mask_rsc;
523  double max_focallength_img0 = std::max(camera_calib.at(id0)->get_K()(0, 0), camera_calib.at(id0)->get_K()(1, 1));
524  double max_focallength_img1 = std::max(camera_calib.at(id1)->get_K()(0, 0), camera_calib.at(id1)->get_K()(1, 1));
525  double max_focallength = std::max(max_focallength_img0, max_focallength_img1);
526  cv::findFundamentalMat(pts0_n, pts1_n, cv::FM_RANSAC, 1 / max_focallength, 0.999, mask_rsc);
527 
528  // Loop through all good matches, and only append ones that have passed RANSAC
529  for (size_t i = 0; i < matches_good.size(); i++) {
530  // Skip if bad ransac id
531  if (mask_rsc[i] != 1)
532  continue;
533  // Else, lets append this match to the return array!
534  matches.push_back(matches_good.at(i));
535  }
536 }
537 
538 void TrackDescriptor::robust_ratio_test(std::vector<std::vector<cv::DMatch>> &matches) {
539  // Loop through all matches
540  for (auto &match : matches) {
541  // If 2 NN has been identified, else remove this feature
542  if (match.size() > 1) {
543  // check distance ratio, remove it if the ratio is larger
544  if (match[0].distance / match[1].distance > knn_ratio) {
545  match.clear();
546  }
547  } else {
548  // does not have 2 neighbours, so remove it
549  match.clear();
550  }
551  }
552 }
553 
554 void TrackDescriptor::robust_symmetry_test(std::vector<std::vector<cv::DMatch>> &matches1, std::vector<std::vector<cv::DMatch>> &matches2,
555  std::vector<cv::DMatch> &good_matches) {
556  // for all matches image 1 -> image 2
557  for (auto &match1 : matches1) {
558  // ignore deleted matches
559  if (match1.empty() || match1.size() < 2)
560  continue;
561  // for all matches image 2 -> image 1
562  for (auto &match2 : matches2) {
563  // ignore deleted matches
564  if (match2.empty() || match2.size() < 2)
565  continue;
566  // Match symmetry test
567  if (match1[0].queryIdx == match2[0].trainIdx && match2[0].queryIdx == match1[0].trainIdx) {
568  // add symmetrical match
569  good_matches.emplace_back(cv::DMatch(match1[0].queryIdx, match1[0].trainIdx, match1[0].distance));
570  // next match in image 1 -> image 2
571  break;
572  }
573  }
574  }
575 }
FeatureDatabase.h
ov_core::TrackBase::mtx_feeds
std::vector< std::mutex > mtx_feeds
Mutexs for our last set of image storage (img_last, pts_last, and ids_last)
Definition: TrackBase.h:174
ov_core::TrackBase::currid
std::atomic< size_t > currid
Master ID for this tracker (atomic to allow for multi-threading)
Definition: TrackBase.h:192
ov_core::TrackBase::database
std::shared_ptr< FeatureDatabase > database
Database with all our current features.
Definition: TrackBase.h:159
ov_core::TrackDescriptor::robust_symmetry_test
void robust_symmetry_test(std::vector< std::vector< cv::DMatch >> &matches1, std::vector< std::vector< cv::DMatch >> &matches2, std::vector< cv::DMatch > &good_matches)
Definition: TrackDescriptor.cpp:554
ov_core::TrackBase::img_mask_last
std::map< size_t, cv::Mat > img_mask_last
Last set of images (use map so all trackers render in the same order)
Definition: TrackBase.h:183
ov_core::TrackBase::mtx_last_vars
std::mutex mtx_last_vars
Mutex for editing the *_last variables.
Definition: TrackBase.h:177
Grider_FAST.h
ov_core::CameraData::images
std::vector< cv::Mat > images
Raw image we have collected for each camera.
Definition: sensor_data.h:64
ov_core::TrackDescriptor::robust_ratio_test
void robust_ratio_test(std::vector< std::vector< cv::DMatch >> &matches)
Definition: TrackDescriptor.cpp:538
ov_core::TrackDescriptor::orb0
cv::Ptr< cv::ORB > orb0
Definition: TrackDescriptor.h:149
Feature.h
ov_core::TrackBase::use_stereo
bool use_stereo
If we should use binocular tracking or stereo tracking for multi-camera.
Definition: TrackBase.h:168
ov_core::CameraData
Struct for a collection of camera measurements.
Definition: sensor_data.h:55
ov_core::TrackBase::num_features
int num_features
Number of features we should try to track frame to frame.
Definition: TrackBase.h:165
ov_core::Grider_FAST::perform_griding
static void perform_griding(const cv::Mat &img, const cv::Mat &mask, std::vector< cv::KeyPoint > &pts, int num_features, int grid_x, int grid_y, int threshold, bool nonmaxSuppression)
This function will perform grid extraction using FAST.
Definition: Grider_FAST.h:73
ov_core::TrackDescriptor::robust_match
void robust_match(const std::vector< cv::KeyPoint > &pts0, const std::vector< cv::KeyPoint > &pts1, const cv::Mat &desc0, const cv::Mat &desc1, size_t id0, size_t id1, std::vector< cv::DMatch > &matches)
Find matches between two keypoint+descriptor sets.
Definition: TrackDescriptor.cpp:480
PRINT_ERROR
#define PRINT_ERROR(x...)
Definition: print.h:100
ov_core::TrackDescriptor::knn_ratio
double knn_ratio
Definition: TrackDescriptor.h:165
ov_core::TrackDescriptor::grid_y
int grid_y
Definition: TrackDescriptor.h:158
PRINT_ALL
#define PRINT_ALL(x...)
Definition: print.h:96
ov_core::CameraData::sensor_ids
std::vector< int > sensor_ids
Camera ids for each of the images collected.
Definition: sensor_data.h:61
ov_core::TrackDescriptor::rT4
boost::posix_time::ptime rT4
Definition: TrackDescriptor.h:146
ov_core::TrackBase::histogram_method
HistogramMethod histogram_method
What histogram equalization method we should pre-process images with?
Definition: TrackBase.h:171
ov_core::TrackDescriptor::feed_monocular
void feed_monocular(const CameraData &message, size_t msg_id)
Process a new monocular image.
Definition: TrackDescriptor.cpp:63
ov_core::CameraData::timestamp
double timestamp
Timestamp of the reading.
Definition: sensor_data.h:58
ov_core::TrackDescriptor::feed_stereo
void feed_stereo(const CameraData &message, size_t msg_id_left, size_t msg_id_right)
Process new stereo pair of images.
Definition: TrackDescriptor.cpp:180
ov_core::CameraData::masks
std::vector< cv::Mat > masks
Tracking masks for each camera we have.
Definition: sensor_data.h:67
ov_core::TrackBase::ids_last
std::unordered_map< size_t, std::vector< size_t > > ids_last
Set of IDs of each current feature in the database.
Definition: TrackBase.h:189
ov_core::LambdaBody
Definition: opencv_lambda_body.h:37
ov_core::TrackDescriptor::rT2
boost::posix_time::ptime rT2
Definition: TrackDescriptor.h:146
ov_core::TrackDescriptor::perform_detection_monocular
void perform_detection_monocular(const cv::Mat &img0, const cv::Mat &mask0, std::vector< cv::KeyPoint > &pts0, cv::Mat &desc0, std::vector< size_t > &ids0)
Detects new features in the current image.
Definition: TrackDescriptor.cpp:355
CamBase.h
RED
#define RED
Definition: colors.h:27
ov_core::TrackDescriptor::rT1
boost::posix_time::ptime rT1
Definition: TrackDescriptor.h:146
ov_core::TrackDescriptor::rT5
boost::posix_time::ptime rT5
Definition: TrackDescriptor.h:146
ov_core::TrackDescriptor::grid_x
int grid_x
Definition: TrackDescriptor.h:157
ov_core::TrackDescriptor::feed_new_camera
void feed_new_camera(const CameraData &message) override
Process a new image.
Definition: TrackDescriptor.cpp:33
ov_core::TrackDescriptor::threshold
int threshold
Definition: TrackDescriptor.h:156
ov_core::TrackDescriptor::perform_detection_stereo
void perform_detection_stereo(const cv::Mat &img0, const cv::Mat &img1, const cv::Mat &mask0, const cv::Mat &mask1, std::vector< cv::KeyPoint > &pts0, std::vector< cv::KeyPoint > &pts1, cv::Mat &desc0, cv::Mat &desc1, size_t cam_id0, size_t cam_id1, std::vector< size_t > &ids0, std::vector< size_t > &ids1)
Detects new features in the current stereo pair.
Definition: TrackDescriptor.cpp:403
TrackDescriptor.h
RESET
#define RESET
Definition: colors.h:25
ov_core::TrackDescriptor::desc_last
std::unordered_map< size_t, cv::Mat > desc_last
Definition: TrackDescriptor.h:168
ov_core::TrackDescriptor::matcher
cv::Ptr< cv::DescriptorMatcher > matcher
Definition: TrackDescriptor.h:153
ov_core::TrackBase::pts_last
std::unordered_map< size_t, std::vector< cv::KeyPoint > > pts_last
Last set of tracked points.
Definition: TrackBase.h:186
ov_core::TrackDescriptor::rT3
boost::posix_time::ptime rT3
Definition: TrackDescriptor.h:146
ov_core::TrackBase::img_last
std::map< size_t, cv::Mat > img_last
Last set of images (use map so all trackers render in the same order)
Definition: TrackBase.h:180
ov_core::TrackBase::camera_calib
std::unordered_map< size_t, std::shared_ptr< CamBase > > camera_calib
Camera object which has all calibration in it.
Definition: TrackBase.h:156
ov_core::TrackDescriptor::min_px_dist
int min_px_dist
Definition: TrackDescriptor.h:161
ov_core
Core algorithms for OpenVINS.
Definition: CamBase.h:30


ov_core
Author(s): Patrick Geneva , Kevin Eckenhoff , Guoquan Huang
autogenerated on Mon Jan 22 2024 03:08:17