SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
CVTrackedFeatures.cpp
Go to the documentation of this file.
1 /**
2  * \file CVTrackedFeatures.cpp
3  * \date Spring 2017
4  * \remarks Please use clangformat to format the code. See more code style on
5  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
6  * \authors Pascal Zingg, Timon Tschanz, Marcus Hudritsch, Michael Goettlicher
7  * \copyright http://opensource.org/licenses/GPL-3.0
8 */
9 
10 /*
11 The OpenCV library version 3.4 or above with extra module must be present.
12 If the application captures the live video stream with OpenCV you have
13 to define in addition the constant APP_USES_CVCAPTURE.
14 All classes that use OpenCV begin with CV.
15 See also the class docs for CVCapture, CVCalibration and CVTracked
16 for a good top down information.
17 */
18 
19 #include <CVImage.h>
20 #include <CVFeatureManager.h>
21 #include <CVTrackedFeatures.h>
22 #include <Utils.h>
23 #include <SLFileStorage.h>
24 
25 #if defined(SL_OS_WINDOWS)
26 # include <direct.h>
27 #endif
28 
29 //-----------------------------------------------------------------------------
30 // Globals for benchmarking
32 float sum_matches = 0;
37 double translationError = 0;
38 double rotationError = 0;
40 
41 //-----------------------------------------------------------------------------
43 {
44  // To match the binary features, we are matching each descriptor in reference with each
45  // descriptor in the current frame. The smaller the hamming distance the better the match
46  // Hamming distance <-> XOR sum
47  _matcher = cv::BFMatcher::create(cv::BFMatcher::BRUTEFORCE_HAMMING, false);
48 
49  // Initialize some member variables on startup to prevent uncontrolled behaviour
50  _currentFrame.foundPose = false;
51  _prevFrame.foundPose = false;
54  _forceRelocation = false;
55  _frameCount = 0;
56 
57  loadMarker(std::move(markerFilename));
58 
59 // Create directory for debug output if flag is set
60 #ifdef DEBUG_OUTPUT_PATH
61 # if defined(SL_OS_LINUX) || defined(SL_OS_MACOS) || defined(SL_OS_MACIOS)
62  mkdir(DEBUG_OUTPUT_PATH, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
63 # elif defined(SL_OS_WINDOWS)
64  _mkdir(DEBUG_OUTPUT_PATH);
65 # else
66 # undef SAVE_SNAPSHOTS_OUTPUT
67 # endif
68 #endif
69 }
70 //-----------------------------------------------------------------------------
71 //! Show statistics if program terminates
73 {
74 #if DO_FEATURE_BENCHMARKING
75  Utils::log("");
76  Utils::log("");
77  Utils::log("------------------------------------------------------------------");
78  Utils::log("CVTrackedFeatures statistics");
79  Utils::log("------------------------------------------------------------------");
80  Utils::log("Avg calculation time per frame : %f ms", _trackingTimesMS().average());
81  Utils::log("");
82  Utils::log("Settings for Pose estimation: ------------------------------------");
83  Utils::log("Features : %d", nFeatures);
84  Utils::log("Minimal ratio for 2 best matches : %f", minRatio);
85  Utils::log("RANSAC iterations : %d", iterations);
86  Utils::log("RANSAC mean reprojection error : %f", reprojection_error);
87  Utils::log("RANSAC confidence : %d", confidence);
88  Utils::log("Repose frequency : Each %d point", reposeFrequency);
89  Utils::log("Initial patch size for Pose optimization : %d pixels", initialPatchSize);
90  Utils::log("Maximal patch size for Pose optimization : %d pixels", maxPatchSize);
91  Utils::log("");
92  Utils::log("Pose information: ------------------------------------------------");
93  Utils::log("Avg allmatches to inliers proposition : %f", sum_allmatches_to_inliers / _frameCount);
94  Utils::log("Avg reprojection error (only if POSE) : %f", sum_reprojection_error / frames_with_pose);
95  Utils::log("Pose found : %d of %d frames", frames_with_pose, _frameCount);
96  Utils::log("Avg matches : %f", sum_matches / frames_with_pose);
97  Utils::log("Avg inlier matches : %f", sum_inlier_matches / frames_with_pose);
98  Utils::log("Avg more matches with Pose optimization : %f", sum_poseopt_difference / frames_with_pose);
99 
100 // Only used for testing with slight movements
101 // Utils::log("Avg Rotation error : %f deg", rotationError / frames_with_pose);
102 // Utils::log("Avg Translation error : %f px", translationError / frames_with_pose);
103 #endif // DO_FEATURE_BENCHMARKING
104 }
105 //-----------------------------------------------------------------------------
106 //! Loads the marker image form the filesystem
107 void CVTrackedFeatures::loadMarker(string markerFilename)
108 {
109  // Load the file directly
110  if (!SLFileStorage::exists(markerFilename, IOK_image))
111  {
112  string msg = "CVTrackedFeatures::loadMarker: File not found: " +
113  markerFilename;
114  Utils::exitMsg("SLProject",
115  msg.c_str(),
116  __LINE__,
117  __FILE__);
118  }
119 
120  CVImage img(markerFilename);
121 
122 #ifndef SL_EMSCRIPTEN
123  cvtColor(img.cvMat(), _marker.imageGray, cv::COLOR_RGB2GRAY);
124 #else
125  cvtColor(img.cvMat(), _marker.imageGray, cv::COLOR_RGBA2GRAY);
126 #endif
127 }
128 //-----------------------------------------------------------------------------
129 /*! Prepares the reference tracker:
130 1. Detect and describe the keypoints on the reference image
131 2. Set up 3D points with predefined scaling
132 3. Perform optional drawing operations on image
133 */
135 {
136  assert(!_marker.imageGray.empty() && "Grayscale image is empty!");
137 
138  // Clear previous initializations
139  _marker.keypoints2D.clear();
140  _marker.keypoints3D.clear();
141  _marker.descriptors.release();
142 
143  // Detect and compute features in marker image
147  // Scaling factor for the 3D point.
148  // Width of image is A4 size in image, 297mm is the real A4 height
149  float pixelPerMM = (float)_marker.imageGray.cols / 297.0f;
150 
151  // Calculate 3D-Points based on the detected features
152  for (auto& keypoint : _marker.keypoints2D)
153  {
154  // 2D location in image
155  CVPoint2f refImageKeypoint = keypoint.pt;
156 
157  // CVPoint scaling
158  refImageKeypoint /= pixelPerMM;
159 
160  // Here we can use Z=0 because the tracker is planar
161  _marker.keypoints3D.push_back(cv::Point3f(refImageKeypoint.x,
162  refImageKeypoint.y,
163  0.0f));
164  }
165 
166 // Draw points and indices which should be reprojected later.
167 // Only a few (defined with reposeFrequency)
168 // points are used for the reprojection.
169 #if defined(DEBUG_OUTPUT_PATH) || DRAW_REPROJECTION_POINTS
171  cvtColor(_marker.imageDrawing, _marker.imageDrawing, cv::COLOR_GRAY2BGR);
172 
173  for (size_t i = 0; i < _marker.keypoints3D.size(); i++)
174  {
175  if (i % reposeFrequency)
176  continue;
177 
178  CVPoint2f originalModelPoint = _marker.keypoints2D[i].pt;
179 
180  circle(_marker.imageDrawing,
181  originalModelPoint,
182  1,
183  CV_RGB(255, 0, 0),
184  1,
185  FILLED);
186 
187  putText(_marker.imageDrawing,
188  to_string(i),
189  CVPoint2f(originalModelPoint.x - 1,
190  originalModelPoint.y - 1),
191  FONT_HERSHEY_SIMPLEX,
192  0.25,
193  CV_RGB(255, 0, 0),
194  1);
195  }
196 #endif
197 }
198 //-----------------------------------------------------------------------------
199 //! Setter of the feature detector & descriptor type
201 {
203 
204  _currentFrame.foundPose = false;
205  _prevFrame.foundPose = false;
207 
208  // Set the frame counter to 0 to reinitialize in track
209  _frameCount = 0;
210 }
211 //-----------------------------------------------------------------------------
212 /*! The main part of this tracker is to calculate a correct Pose.
213 @param imageGray Current grayscale frame
214 @param image Current RGB frame
215 @param calib Calibration information
216 @return So far always false
217 */
219  CVMat image,
220  CVCalibration* calib)
221 {
222  assert(!image.empty() && "Image is empty");
223  assert(!calib->cameraMat().empty() && "Calibration is empty");
224  assert(!_marker.imageGray.empty());
225 
226  // Initialize reference points if program just started
227  if (_frameCount == 0)
228  {
229  _calib = calib;
231  }
232 
233  // Copy image matrix into current frame data
234  _currentFrame.image = image;
235  _currentFrame.imageGray = imageGray;
236 
237  // Determine if relocation or feature tracking should be performed
238  bool relocationNeeded = _forceRelocation ||
240  _prevFrame.inlierMatches.size() < 100 ||
242 
243  // If relocation condition meets, calculate the Pose with feature detection, otherwise
244  // track the previous determined features
245  if (relocationNeeded)
246  relocate();
247  else
248  tracking();
249 
251  {
254  }
255 
256  // Perform OpenCV drawning if flags are set (see CVTrackedFeatures.h)
258 
259  // Prepare next frame and transfer necessary data
261 
262  _frameCount++;
263 
264  return _currentFrame.foundPose;
265 }
266 //-----------------------------------------------------------------------------
267 /*! If relocation should be done, the following steps are necessary:
268 1. Detect keypoints
269 2. Describe keypoints (Binary descriptors)
270 3. Match keypoints in current frame and the reference tracker
271 4. Try to calculate new Pose with Perspective-n-Point algorithm
272 */
274 {
275  _isTracking = false;
279 
280  // Zero time keeping on the tracking branch
282 }
283 
284 //-----------------------------------------------------------------------------
285 /*! To track the already detected keypoints after a sucessful pose estimation,
286 we track the features with optical flow
287 */
289 {
290  _isTracking = true;
292 
293  // Zero time keeping on the relocation branch
296 }
297 
298 //-----------------------------------------------------------------------------
299 /*! Visualizes the following parts of the whole Pose estimation:
300 - Keypoints
301 - Inlier matches
302 - Optical Flow (Small arrows that show how keypoints moved between frames)
303 - Reprojection with the calculated Pose
304 */
306 {
307  if (drawDetection)
308  {
309  for (auto& inlierPoint : _currentFrame.inlierPoints2D)
310  circle(_currentFrame.image,
311  inlierPoint,
312  3,
313  cv::Scalar(0, 0, 255));
314  }
315 
316 #if DRAW_REPROJECTION_POINTS
317  CVMat imgReprojection = _currentFrame.image;
318 #elif defined(SAVE_SNAPSHOTS_OUTPUT)
319  CVMat imgReprojection;
320  _currentFrame.image.copyTo(imgReprojection);
321 #endif
322 
323 #if DRAW_REPROJECTION_POINTS || defined(DEBUG_OUTPUT_PATH)
324  if (!_currentFrame.inlierMatches.empty())
325  {
326  CVVPoint2f projectedPoints(_marker.keypoints3D.size());
327 
328  cv::projectPoints(_marker.keypoints3D,
331  _calib->cameraMat(),
332  _calib->distortion(),
333  projectedPoints);
334 
335  for (size_t i = 0; i < _marker.keypoints3D.size(); i++)
336  {
337  if (i % reposeFrequency) continue;
338 
339  CVPoint2f projectedModelPoint = projectedPoints[i];
340  CVPoint2f keypointForPose = _currentFrame.keypoints[_currentFrame.inlierMatches.back().queryIdx].pt;
341 
342  // draw all projected map features and the original keypoint on video stream
343  circle(imgReprojection,
344  projectedModelPoint,
345  2,
346  CV_RGB(255, 0, 0),
347  1,
348  FILLED);
349 
350  circle(imgReprojection,
351  keypointForPose,
352  5,
353  CV_RGB(0, 0, 255),
354  1,
355  FILLED);
356 
357  // draw the point index and reprojection error
358  putText(imgReprojection,
359  to_string(i),
360  CVPoint2f(projectedModelPoint.x - 2, projectedModelPoint.y - 5),
361  FONT_HERSHEY_SIMPLEX,
362  0.3,
363  CV_RGB(255, 0, 0),
364  1.0);
365  }
366  }
367 #endif
368 
369 #if defined(DEBUG_OUTPUT_PATH)
370  // Draw reprojection
371  CVMat imgOut;
372  drawMatches(imgReprojection,
373  CVVKeyPoint(),
375  CVVKeyPoint(),
376  CVVDMatch(),
377  imgOut,
378  CV_RGB(255, 0, 0),
379  CV_RGB(255, 0, 0));
380 
381  imwrite(DEBUG_OUTPUT_PATH + to_string(_frameCount) + "_reprojection.png",
382  imgOut);
383 
384  // Draw keypoints
385  if (!_currentFrame.keypoints.empty())
386  {
387  CVMat imgKeypoints;
388  drawKeypoints(_currentFrame.imageGray,
390  imgKeypoints);
391 
392  imwrite(DEBUG_OUTPUT_PATH + to_string(_frameCount) + "_keypoints.png",
393  imgKeypoints);
394  }
395 
396  for (size_t i = 0; i < _currentFrame.inlierPoints2D.size(); i++)
397  circle(_currentFrame.image,
399  2,
400  Scalar(0, 255, 0));
401 
402  // Draw matches
403  if (!_currentFrame.inlierMatches.empty())
404  {
405  CVMat imgMatches;
406  drawMatches(_currentFrame.imageGray,
411  imgMatches,
412  CV_RGB(255, 0, 0),
413  CV_RGB(255, 0, 0));
414 
415  imwrite(DEBUG_OUTPUT_PATH + to_string(_frameCount) + "_matching.png",
416  imgMatches);
417  }
418 
419  // Draw optical flow
420  if (_isTracking)
421  {
422  CVMat optFlow, rgb;
423  _currentFrame.imageGray.copyTo(optFlow);
424  cvtColor(optFlow, rgb, CV_GRAY2BGR);
425  for (size_t i = 0; i < _currentFrame.inlierPoints2D.size(); i++)
426  cv::arrowedLine(rgb,
429  Scalar(0, 255, 0),
430  1,
431  LINE_8,
432  0,
433  0.2);
434 
435  imwrite(DEBUG_OUTPUT_PATH + to_string(_frameCount) + "-optflow.png", rgb);
436  }
437 #endif
438 }
439 //-----------------------------------------------------------------------------
440 /*! Copies the current frame data to the previous frame data struct for the
441 next frame handling.
442 TODO: more elegant way to do this whole copy action
443 */
445 {
450 
455 
456  if (!_currentFrame.inlierMatches.empty())
458 
459  _currentFrame.keypoints.clear();
460  _currentFrame.matches.clear();
465 
467 
468  if (_prevFrame.foundPose)
469  {
472  }
473  else
474  {
475  _currentFrame.rvec = CVMat::zeros(3, 1, CV_64FC1);
476  _currentFrame.tvec = CVMat::zeros(3, 1, CV_64FC1);
477  }
478 }
479 //-----------------------------------------------------------------------------
480 /*! Get keypoints and descriptors in one step. This is a more efficient way
481 since we have to build the scaling pyramide only once. If we detect and
482 describe seperatly, it will lead in two scaling pyramids and is therefore less
483 meaningful.
484 */
486 {
487  float startMS = _timer.elapsedTimeInMilliSec();
488 
492 
494 }
495 //-----------------------------------------------------------------------------
496 /*! Get matching features with the defined feature matcher. Since we are using
497 the k-next-neighbour matcher, we check if the best and second best match are
498 not too identical with the so called ratio test.
499 @return Vector of found matches
500 */
502 {
503  float startMS = _timer.elapsedTimeInMilliSec();
504 
505  int k = 2;
506  CVVVDMatch matches;
507  _matcher->knnMatch(_currentFrame.descriptors, _marker.descriptors, matches, k);
508 
509  // Perform ratio test which determines if k matches from the knn matcher
510  // are not too similar. If the ratio of the the distance of the two
511  // matches is toward 1, the matches are near identically.
512  CVVDMatch goodMatches;
513  for (auto& match : matches)
514  {
515  const cv::DMatch& match1 = match[0];
516  const cv::DMatch& match2 = match[1];
517  if (match2.distance == 0.0f ||
518  (match1.distance / match2.distance) < minRatio)
519  goodMatches.push_back(match1);
520  }
521 
523  return goodMatches;
524 }
525 //-----------------------------------------------------------------------------
526 /*! This method does the most important work of the whole pipeline:
527 
528 RANSAC: We execute first RANSAC to eliminate wrong feature correspondences
529 (outliers) and only use the correct ones (inliers) for PnP solving
530 (https://en.wikipedia.org/wiki/Perspective-n-Point).\n
531 \n
532 Methods of solvePnP:
533 - P3P: If we have 3 Points given, we have the minimal form of the PnP problem.
534  We can treat the points as a triangle definition ABC. We have 3 corner points
535  and 3 angles. Because we get many soulutions for the equation, there will be a
536  fourth point which removes the ambiguity. Therefore the OpenCV implementation
537  requires 4 points to use this method.
538 - EPNP: This method is used if there are n >= 4 points. The reference points are
539  expressed as 4 virtual control points. The coordinates of these points are the
540  unknowns for the equtation.
541 - ITERATIVE: Calculates pose using the DLT (Direct Linear Transform) method.
542  If there is a homography will be much easier and no DLT will be used. Otherwise
543  we are using the DLT and make a Levenberg-Marquardt optimization. The latter
544  helps to decrease the reprojection error which is the sum of the squared
545  distances between the image and object points.\n
546 \n
547 Overall Steps:
548 1. Call RANSAC with EPNP: The RANdom Sample Consensus algorithm is called to
549 remove "wrong" point correspondences which makes the solvePnP more robust.
550 The so called inliers are used for calculation, wrong correspondences (outliers)
551 will be ignored. Therefore the method below will first run a solvePnP with the
552 EPNP method and returns the reprojection error. EPNP works like the following:
553  - Choose the 4 control pints: C0 as centroid of reference points, \n
554  C1, C2 and C3 from PCA of the reference points
555  - Compute barycentric coordinates with the control points
556  - Derivate the image reference points with the above
557 2. Optimize inlier matches
558 3. Call PnP ITERATIVE: General problem: We have a calibrated cam and sets of
559 corresponding 2D/3D points. We will calculate the rotation and translation in
560 respect to world coordinates.
561  - If for no extrinsic guess, begin with computation
562  - If planarity is detected, find homography, otherwise use DLT method
563  - After sucessful determination of a pose, optimize it with \n
564  Levenberg-Marquardt (iterative part)
565 
566 @return True if the pose was found.
567  */
569 {
570  // solvePnP crashes if less than 5 points are given
571  if (_currentFrame.matches.size() < 10) return false;
572 
573  float startMS = _timer.elapsedTimeInMilliSec();
574 
575  // Find 2D/3D correspondences
576  // At the moment we are using only the two correspondences like this:
577  // KeypointsOriginal <-> KeypointsActualscene
578  // Train index --> "CVPoint" in the model
579  // Query index --> "CVPoint" in the actual frame
580 
581  if (_currentFrame.matches.size() < 10)
582  return false;
583 
584  CVVPoint3f modelPoints(_currentFrame.matches.size());
585  CVVPoint2f framePoints(_currentFrame.matches.size());
586 
587  for (size_t i = 0; i < _currentFrame.matches.size(); i++)
588  {
589  modelPoints[i] = _marker.keypoints3D[(uint)_currentFrame.matches[i].trainIdx];
590  framePoints[i] = _currentFrame.keypoints[(uint)_currentFrame.matches[i].queryIdx].pt;
591  }
592 
593  vector<uchar> inliersMask(modelPoints.size());
594 
595  //////////////////////
596  // 1. RANSAC with EPnP
597  //////////////////////
598 
599  bool foundPose = cv::solvePnPRansac(modelPoints,
600  framePoints,
601  _calib->cameraMat(),
602  _calib->distortion(),
606  iterations,
608  confidence,
609  inliersMask,
610  cv::SOLVEPNP_EPNP);
611 
612  // Get matches with help of inlier indices
613  for (size_t idx : inliersMask)
614  {
616  _currentFrame.inlierPoints2D.push_back(framePoints[idx]);
617  _currentFrame.inlierPoints3D.push_back(modelPoints[idx]);
618  }
619 
620  // Pose optimization
621  if (foundPose)
622  {
623  // float matchesBefore = (float)_currentFrame.inlierMatches.size();
624 
625  /////////////////////
626  // 2. Optimze Matches
627  /////////////////////
628 
629  optimizeMatches();
630 
631  ///////////////////////
632  // 3. solvePnP Iterativ
633  ///////////////////////
634 
635  foundPose = cv::solvePnP(_currentFrame.inlierPoints3D,
637  _calib->cameraMat(),
638  _calib->distortion(),
641  true,
642  cv::SOLVEPNP_ITERATIVE);
643 
644 #if DO_FEATURE_BENCHMARKING
648  _currentFrame.matches.size();
650  matchesBefore;
651 #endif
652  }
653 
655 
656  return foundPose;
657 }
658 //-----------------------------------------------------------------------------
659 /*! To get more matches with the calculated pose, we reproject the reference
660 points to our current frame. Within a predefined patch, we try to rematch not
661 matched features with the reprojected point. If not possible, we increase the
662 patch size until we found a match for the point or we reach a threshold.
663 */
665 {
666 #if DO_FEATURE_BENCHMARKING
667  float reprojectionError = 0;
668 #endif
669 
670  // 1. Reproject the model points with the calculated POSE
671  CVVPoint2f projectedPoints(_marker.keypoints3D.size());
672  cv::projectPoints(_marker.keypoints3D,
675  _calib->cameraMat(),
676  _calib->distortion(),
677  projectedPoints);
678 
679  CVVKeyPoint bboxFrameKeypoints;
680  vector<size_t> frameIndicesInsideRect;
681 
682  for (size_t i = 0; i < _marker.keypoints3D.size(); i++)
683  {
684  // only every reposeFrequency
685  if (i % reposeFrequency)
686  continue;
687 
688  // Check if this point has a match inside matches, continue if so
689  int alreadyMatched = 0;
690  // todo: this is bad, because for every marker keypoint we have to iterate all inlierMatches!
691  // better: iterate inlierMatches once at the beginning and mark all marker keypoints as inliers or not!
692  for (size_t j = 0; j < _currentFrame.inlierMatches.size(); j++)
693  {
694  if (_currentFrame.inlierMatches[(uint)j].trainIdx == (int)i)
695  alreadyMatched++;
696  }
697 
698  if (alreadyMatched > 0) continue;
699 
700  // Get the corresponding projected point of the actual (i) modelpoint
701  CVPoint2f projectedModelPoint = projectedPoints[i];
702  CVVDMatch newMatches;
703 
704  int patchSize = initialPatchSize;
705 
706  // Adaptive patch size
707  while (newMatches.empty() && patchSize <= maxPatchSize)
708  {
709  // Increase matches by even number
710  patchSize += 2;
711  newMatches.clear();
712  bboxFrameKeypoints.clear();
713  frameIndicesInsideRect.clear();
714 
715  // 2. Select only before calculated Keypoints within patch
716  // with projected "positioning" keypoint as center
717  // OpenCV: Top-left origin
718  int xTopLeft = (int)(projectedModelPoint.x - (float)patchSize / 2.0f);
719  int yTopLeft = (int)(projectedModelPoint.y - (float)patchSize / 2.0f);
720  int xDownRight = xTopLeft + patchSize;
721  int yDownRight = yTopLeft + patchSize;
722 
723  for (size_t j = 0; j < _currentFrame.keypoints.size(); j++)
724  { // bbox check
725  if (_currentFrame.keypoints[j].pt.x > xTopLeft &&
726  _currentFrame.keypoints[j].pt.x < xDownRight &&
727  _currentFrame.keypoints[j].pt.y > yTopLeft &&
728  _currentFrame.keypoints[j].pt.y < yDownRight)
729  {
730  bboxFrameKeypoints.push_back(_currentFrame.keypoints[j]);
731  frameIndicesInsideRect.push_back(j);
732  }
733  }
734 
735  // 3. Match the descriptors of the key points inside
736  // the rectangle around the projected map point
737  // with the descriptor of the projected map point.
738 
739  // This is our descriptor for the model point i
740  CVMat modelPointDescriptor = _marker.descriptors.row((int)i);
741 
742  // We extract the descriptors which belong to the key points
743  // inside the rectangle around the projected map point
744  CVMat bboxPointsDescriptors;
745  for (size_t j : frameIndicesInsideRect)
746  bboxPointsDescriptors.push_back(_currentFrame.descriptors.row((int)j));
747 
748  // 4. Match the frame key points inside the rectangle with the projected model point
749  _matcher->match(bboxPointsDescriptors, modelPointDescriptor, newMatches);
750  }
751 
752  if (!newMatches.empty())
753  {
754  for (size_t j = 0; j < frameIndicesInsideRect.size(); j++)
755  {
756  newMatches[j].trainIdx = (int)i;
757  newMatches[j].queryIdx = (int)frameIndicesInsideRect[j];
758  }
759 
760  // 5. Only add the best new match to matches vector
761  CVDMatch bestNewMatch;
762  bestNewMatch.distance = 0;
763 
764  for (CVDMatch newMatch : newMatches)
765  if (bestNewMatch.distance < newMatch.distance)
766  bestNewMatch = newMatch;
767 
768  // 6. Only add the best new match to matches vector
769  _currentFrame.inlierMatches.push_back(bestNewMatch);
770  }
771 
772  // Get the keypoint which was used for pose estimation
773  CVPoint2f keypointForPose = _currentFrame.keypoints[(uint)_currentFrame.inlierMatches.back().queryIdx].pt;
774 
775 #if DO_FEATURE_BENCHMARKING
776  reprojectionError += (float)norm(CVMat(projectedModelPoint),
777  CVMat(keypointForPose));
778 #endif
779 
780 #if DRAW_PATCHES
781  // draw green rectangle around every map point
782  rectangle(_currentFrame.image,
783  Point2f(projectedModelPoint.x - (float)patchSize / 2.0f,
784  projectedModelPoint.y - (float)patchSize / 2.0f),
785  Point2f(projectedModelPoint.x + (float)patchSize / 2.0f,
786  projectedModelPoint.y + (float)patchSize / 2.0f),
787  CV_RGB(0, 255, 0));
788 
789  // draw key points, that lie inside this rectangle
790  for (const auto& kPt : bboxFrameKeypoints)
791  circle(_currentFrame.image,
792  kPt.pt,
793  1,
794  CV_RGB(0, 0, 255),
795  1,
796  FILLED);
797 #endif
798  }
799 
800 #if DO_FEATURE_BENCHMARKING
801  sum_reprojection_error += reprojectionError / _marker.keypoints3D.size();
802 #endif
803 
804 #if DO_FEATURE_BENCHMARKING
805  CVMat prevRmat, currRmat;
806  if (_prevFrame.foundPose)
807  {
808  Rodrigues(_prevFrame.rvec, prevRmat);
809  Rodrigues(_currentFrame.rvec, currRmat);
810  double rotationError_rad = acos((trace(prevRmat * currRmat).val[0] - 1.0) / 2.0);
811  rotationError += rotationError_rad * 180 / 3.14;
813  }
814 #endif
815 
816 #if DRAW_REPROJECTION_POINTS
817  // Draw the projection error for the current frame
818  putText(_currentFrame.image,
819  "Reprojection error: " + to_string(reprojectionError / _marker.keypoints3D.size()),
820  Point2f(20, 20),
821  FONT_HERSHEY_SIMPLEX,
822  0.5,
823  CV_RGB(255, 0, 0),
824  2.0);
825 #endif
826 
827  // Optimize POSE
828  vector<cv::Point3f> modelPoints = vector<cv::Point3f>(_currentFrame.inlierMatches.size());
829  vector<cv::Point2f> framePoints = vector<cv::Point2f>(_currentFrame.inlierMatches.size());
830  for (size_t i = 0; i < _currentFrame.inlierMatches.size(); i++)
831  {
832  modelPoints[i] = _marker.keypoints3D[(uint)_currentFrame.inlierMatches[i].trainIdx];
833  framePoints[i] = _currentFrame.keypoints[(uint)_currentFrame.inlierMatches[i].queryIdx].pt;
834  }
835 
836  if (modelPoints.empty()) return;
837  _currentFrame.inlierPoints3D = modelPoints;
838  _currentFrame.inlierPoints2D = framePoints;
839 }
840 //-----------------------------------------------------------------------------
841 /*! Tracks the features with Optical Flow (Lucas Kanade). This will only try to
842 predict the new location of keypoints. If they were found, we perform a
843 solvePnP to get the new Pose from feature tracking. The method performs tests
844 if the Pose is good enough (not too much difference between previous and new
845 Pose).
846 @param rvec Rotation vector (will be used for extrinsic guess)
847 @param tvec Translation vector (will be used for extrinsic guess)
848 @return True if Pose found, false otherwise
849 */
851 {
852  if (_prevFrame.inlierPoints2D.size() < 4) return false;
853 
854  float startMS = _timer.elapsedTimeInMilliSec();
855 
856  vector<uchar> status;
857  vector<float> err;
858  CVSize winSize(15, 15);
859 
860  cv::TermCriteria criteria(cv::TermCriteria::COUNT | cv::TermCriteria::EPS,
861  10, // terminate after this many iterations, or
862  0.03); // when the search window moves by less than this
863 
864  // Find closest possible feature points based on optical flow
865  CVVPoint2f pred2DPoints(_prevFrame.inlierPoints2D.size());
866 
867  // todo: do not relate optical flow to previous frame! better to original marker image, otherwise we will drift
868  cv::calcOpticalFlowPyrLK(
869  _prevFrame.imageGray, // Previous frame
870  _currentFrame.imageGray, // Current frame
871  _prevFrame.inlierPoints2D, // Previous and current keypoints coordinates.The latter will be
872  pred2DPoints, // expanded if more good coordinates are detected during OptFlow
873  status, // Output vector for keypoint correspondences (1 = match found)
874  err, // Error size for each flow
875  winSize, // Search window for each pyramid level
876  3, // Max levels of pyramid creation
877  criteria, // Configuration from above
878  0, // Additional flags
879  0.001); // Minimal Eigen threshold
880 
881  // Only use points which are not wrong in any way during the optical flow calculation
882  CVVPoint2f frame2DPoints;
883  CVVPoint3f model3DPoints;
884  for (size_t i = 0; i < status.size(); i++)
885  {
886  if (status[i])
887  {
888  frame2DPoints.push_back(pred2DPoints[i]);
889  // Original code from Zingg/Tschanz got zero size vector
890  // model3DPoints.push_back(_currentFrameFrame.inlierPoints3D[i]);
891  model3DPoints.push_back(_prevFrame.inlierPoints3D[i]);
892  }
893  }
894 
896 
897  _currentFrame.inlierPoints2D = frame2DPoints;
898  _currentFrame.inlierPoints3D = model3DPoints;
899 
900  if (_currentFrame.inlierPoints2D.size() < _prevFrame.inlierPoints2D.size() * 0.75)
901  return false;
902 
903  /////////////////////
904  // Pose Estimation //
905  /////////////////////
906 
907  startMS = _timer.elapsedTimeInMilliSec();
908 
909  bool foundPose = cv::solvePnP(model3DPoints,
910  frame2DPoints,
911  _calib->cameraMat(),
912  _calib->distortion(),
913  rvec,
914  tvec,
915  true);
916  bool poseValid = true;
917 
918  if (foundPose)
919  {
920  for (int i = 0; i < tvec.cols; i++)
921  {
922  if (abs(tvec.at<double>(i, 0) - tvec.at<double>(i, 0)) > abs(tvec.at<double>(i, 0)) * 0.2)
923  {
924  cout << "translation too large" << endl;
925  poseValid = false;
926  }
927  }
928  for (int i = 0; i < rvec.cols; i++)
929  {
930  if (abs(rvec.at<double>(i, 0) - rvec.at<double>(i, 0)) > 0.174533)
931  {
932  cout << "rotation too large" << endl;
933  poseValid = false;
934  }
935  }
936  }
937 
938  if (foundPose && poseValid)
939  {
940  rvec.copyTo(_currentFrame.rvec);
941  tvec.copyTo(_currentFrame.tvec);
942  }
943 
945 
946  return foundPose && poseValid;
947 }
948 //-----------------------------------------------------------------------------
CVDetectDescribeType
Feature detector-decriptor types.
double sum_reprojection_error
float sum_inlier_matches
float sum_allmatches_to_inliers
float sum_poseopt_difference
double translationError
double rotationError
int frames_since_posefound
int frames_with_pose
float sum_matches
const int reposeFrequency
const float reprojection_error
const int nFeatures
const int iterations
const int maxPatchSize
const int initialPatchSize
const double confidence
const float minRatio
cv::Point2f CVPoint2f
Definition: CVTypedefs.h:43
vector< cv::Point3f > CVVPoint3f
Definition: CVTypedefs.h:79
cv::DMatch CVDMatch
Definition: CVTypedefs.h:62
vector< vector< cv::DMatch > > CVVVDMatch
Definition: CVTypedefs.h:101
cv::Size CVSize
Definition: CVTypedefs.h:55
cv::Mat CVMat
Definition: CVTypedefs.h:38
vector< cv::Point2f > CVVPoint2f
Definition: CVTypedefs.h:77
vector< cv::DMatch > CVVDMatch
Definition: CVTypedefs.h:89
vector< cv::KeyPoint > CVVKeyPoint
Definition: CVTypedefs.h:88
@ IOK_image
Definition: SLFileStorage.h:40
Live video camera calibration class with OpenCV an OpenCV calibration.
Definition: CVCalibration.h:71
const CVMat & cameraMat() const
const CVMat & distortion() const
void createDetectorDescriptor(CVDetectDescribeType detectDescribeType)
Creates a detector and decriptor to the passed type.
void detectAndDescribe(CVInputArray image, CVVKeyPoint &keypoints, CVOutputArray descriptors, CVInputArray mask=cv::noArray())
OpenCV image class with the same interface as the former SLImage class.
Definition: CVImage.h:64
CVMat cvMat() const
Definition: CVImage.h:122
bool _isTracking
True if tracking.
void loadMarker(string markerFilename)
Loads the marker image form the filesystem.
CVVDMatch getFeatureMatches()
SLFrameData _prevFrame
The previous video frame data.
CVTrackedFeatures(string markerFilename)
CVFeatureManager _featureManager
Feature detector-descriptor wrapper instance.
~CVTrackedFeatures()
Show statistics if program terminates.
bool _forceRelocation
Force relocation every frame (no opt. flow tracking)
SLFeatureMarker2D _marker
2D marker data
cv::Ptr< cv::DescriptorMatcher > _matcher
Descriptor matching algorithm.
CVCalibration * _calib
Current calibration in use.
void drawDebugInformation(bool drawDetection)
CVDetectDescribeType type()
bool track(CVMat imageGray, CVMat image, CVCalibration *calib) final
int _frameCount
NO. of frames since process start.
bool trackWithOptFlow(CVMat rvec, CVMat tvec)
SLFrameData _currentFrame
The current video frame data.
static AvgFloat optFlowTimesMS
Averaged time for video feature optical flow tracking in ms.
Definition: CVTracked.h:87
CVMatx44f _objectViewMat
view transformation matrix
Definition: CVTracked.h:93
static AvgFloat detectTimesMS
Averaged time for video feature detection & description in ms.
Definition: CVTracked.h:83
bool _drawDetection
Flag if detection should be drawn into image.
Definition: CVTracked.h:92
static cv::Matx44f createGLMatrix(const CVMat &tVec, const CVMat &rVec)
Create an OpenGL 4x4 matrix from an OpenCV translation & rotation vector.
Definition: CVTracked.cpp:46
HighResTimer _timer
High resolution timer.
Definition: CVTracked.h:94
bool drawDetection()
Definition: CVTracked.h:64
static AvgFloat matchTimesMS
Averaged time for video feature matching in ms.
Definition: CVTracked.h:86
static AvgFloat poseTimesMS
Averaged time for video feature pose estimation in ms.
Definition: CVTracked.h:88
float elapsedTimeInMilliSec()
Definition: HighResTimer.h:38
void set(T value)
Sets the current value in the value array and builds the average.
Definition: Averaged.h:53
bool exists(std::string path, SLIOStreamKind kind)
Checks whether a given file exists.
T abs(T a)
Definition: Utils.h:249
void exitMsg(const char *tag, const char *msg, const int line, const char *file)
Terminates the application with a message. No leak checking.
Definition: Utils.cpp:1135
void log(const char *tag, const char *format,...)
logs a formatted string platform independently
Definition: Utils.cpp:1103
CVMat imageGray
Grayscale image of the marker.
CVVPoint3f keypoints3D
3D feature points in mm
CVVKeyPoint keypoints2D
2D keypoints in pixels
CVMat descriptors
Descriptors of the 2D keypoints.
CVVDMatch inlierMatches
matches that lead to correct transform
bool useExtrinsicGuess
flag if extrinsic gues should be used
bool foundPose
True if pose was found.
CVMat rvec
Rotation of the camera pose.
CVMat imageGray
Reference to grayscale video frame.
float reprojectionError
Reprojection error of the pose.
CVMat tvec
Translation of the camera pose.
CVVPoint2f inlierPoints2D
Inlier 2D points after RANSAC.
CVVDMatch matches
matches between video decriptors and marker descriptors
CVMat image
Reference to color video frame.
CVVKeyPoint keypoints
2D keypoints detected in video frame
CVVPoint3f inlierPoints3D
Inlier 3D points after RANSAC on the marker.
CVMat descriptors
Descriptors of keypoints.