SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
CVTrackedFeatures Class Reference

CVTrackedFeatures is the main part of the AR Christoffelturm scene. More...

#include <CVTrackedFeatures.h>

Inheritance diagram for CVTrackedFeatures:
[legend]

Classes

struct  SLFeatureMarker2D
 Data of a 2D marker image. More...
 
struct  SLFrameData
 Feature date for a video frame. More...
 

Public Member Functions

 CVTrackedFeatures (string markerFilename)
 
 ~CVTrackedFeatures ()
 Show statistics if program terminates. More...
 
bool track (CVMat imageGray, CVMat image, CVCalibration *calib) final
 
bool forceRelocation ()
 
CVDetectDescribeType type ()
 
void forceRelocation (bool fR)
 
void type (CVDetectDescribeType ddType)
 Setter of the feature detector & descriptor type. More...
 
- Public Member Functions inherited from CVTracked
 CVTracked ()
 
virtual ~CVTracked ()=default
 
void drawDetection (bool draw)
 
bool isVisible ()
 
bool drawDetection ()
 
CVMatx44f objectViewMat ()
 

Private Member Functions

void loadMarker (string markerFilename)
 Loads the marker image form the filesystem. More...
 
void initFeaturesOnMarker ()
 
void relocate ()
 
void tracking ()
 
void drawDebugInformation (bool drawDetection)
 
void transferFrameData ()
 
void detectKeypointsAndDescriptors ()
 
CVVDMatch getFeatureMatches ()
 
bool calculatePose ()
 
void optimizeMatches ()
 
bool trackWithOptFlow (CVMat rvec, CVMat tvec)
 

Private Attributes

cv::Ptr< cv::DescriptorMatcher > _matcher
 Descriptor matching algorithm. More...
 
CVCalibration_calib
 Current calibration in use. More...
 
int _frameCount
 NO. of frames since process start. More...
 
bool _isTracking
 True if tracking. More...
 
SLFeatureMarker2D _marker
 2D marker data More...
 
SLFrameData _currentFrame
 The current video frame data. More...
 
SLFrameData _prevFrame
 The previous video frame data. More...
 
bool _forceRelocation
 Force relocation every frame (no opt. flow tracking) More...
 
CVFeatureManager _featureManager
 Feature detector-descriptor wrapper instance. More...
 

Additional Inherited Members

- Static Public Member Functions inherited from CVTracked
static cv::Matx44f createGLMatrix (const CVMat &tVec, const CVMat &rVec)
 Create an OpenGL 4x4 matrix from an OpenCV translation & rotation vector. More...
 
static void createRvecTvec (const CVMatx44f &glMat, CVMat &tVec, CVMat &rVec)
 Creates the OpenCV rvec & tvec vectors from an column major OpenGL 4x4 matrix. More...
 
static CVMatx44f calcObjectMatrix (const CVMatx44f &cameraObjectMat, const CVMatx44f &objectViewMat)
 
static CVVec3f averageVector (vector< CVVec3f > vectors, vector< float > weights)
 
static SLQuat4f averageQuaternion (vector< SLQuat4f > quaternions, vector< float > weights)
 
static void resetTimes ()
 Resets all static variables. More...
 
- Static Public Attributes inherited from CVTracked
static AvgFloat trackingTimesMS
 Averaged time for video tracking in ms. More...
 
static AvgFloat detectTimesMS
 Averaged time for video feature detection & description in ms. More...
 
static AvgFloat detect1TimesMS
 Averaged time for video feature detection subpart 1 in ms. More...
 
static AvgFloat detect2TimesMS
 Averaged time for video feature detection subpart 2 in ms. More...
 
static AvgFloat matchTimesMS
 Averaged time for video feature matching in ms. More...
 
static AvgFloat optFlowTimesMS
 Averaged time for video feature optical flow tracking in ms. More...
 
static AvgFloat poseTimesMS
 Averaged time for video feature pose estimation in ms. More...
 
- Protected Attributes inherited from CVTracked
bool _isVisible
 Flag if marker is visible. More...
 
bool _drawDetection
 Flag if detection should be drawn into image. More...
 
CVMatx44f _objectViewMat
 view transformation matrix More...
 
HighResTimer _timer
 High resolution timer. More...
 

Detailed Description

CVTrackedFeatures is the main part of the AR Christoffelturm scene.

The implementation tries to find a valid pose based on feature points in realtime. The feature matching algorithm checks the points of the current camera frame with against a reference. There are two important parts of this procedure: The relocalisation, which will be called if we have to find the pose with no hint where the camera could be. The other one is called feature tracking: If a pose was found, the implementation tries to track them and update the pose respectively.

Definition at line 66 of file CVTrackedFeatures.h.

Constructor & Destructor Documentation

◆ CVTrackedFeatures()

CVTrackedFeatures::CVTrackedFeatures ( string  markerFilename)
explicit

Definition at line 42 of file CVTrackedFeatures.cpp.

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 }
const int nFeatures
vector< cv::Point2f > CVVPoint2f
Definition: CVTypedefs.h:77
void loadMarker(string markerFilename)
Loads the marker image form the filesystem.
SLFrameData _prevFrame
The previous video frame data.
bool _forceRelocation
Force relocation every frame (no opt. flow tracking)
cv::Ptr< cv::DescriptorMatcher > _matcher
Descriptor matching algorithm.
int _frameCount
NO. of frames since process start.
SLFrameData _currentFrame
The current video frame data.
bool foundPose
True if pose was found.
float reprojectionError
Reprojection error of the pose.
CVVPoint2f inlierPoints2D
Inlier 2D points after RANSAC.

◆ ~CVTrackedFeatures()

CVTrackedFeatures::~CVTrackedFeatures ( )

Show statistics if program terminates.

Definition at line 72 of file CVTrackedFeatures.cpp.

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 }
double sum_reprojection_error
float sum_inlier_matches
float sum_allmatches_to_inliers
float sum_poseopt_difference
int frames_with_pose
float sum_matches
const int reposeFrequency
const float reprojection_error
const int iterations
const int maxPatchSize
const int initialPatchSize
const double confidence
const float minRatio
void log(const char *tag, const char *format,...)
logs a formatted string platform independently
Definition: Utils.cpp:1103

Member Function Documentation

◆ calculatePose()

bool CVTrackedFeatures::calculatePose ( )
private

This method does the most important work of the whole pipeline:

RANSAC: We execute first RANSAC to eliminate wrong feature correspondences (outliers) and only use the correct ones (inliers) for PnP solving (https://en.wikipedia.org/wiki/Perspective-n-Point).

Methods of solvePnP:

  • P3P: If we have 3 Points given, we have the minimal form of the PnP problem. We can treat the points as a triangle definition ABC. We have 3 corner points and 3 angles. Because we get many soulutions for the equation, there will be a fourth point which removes the ambiguity. Therefore the OpenCV implementation requires 4 points to use this method.
  • EPNP: This method is used if there are n >= 4 points. The reference points are expressed as 4 virtual control points. The coordinates of these points are the unknowns for the equtation.
  • ITERATIVE: Calculates pose using the DLT (Direct Linear Transform) method. If there is a homography will be much easier and no DLT will be used. Otherwise we are using the DLT and make a Levenberg-Marquardt optimization. The latter helps to decrease the reprojection error which is the sum of the squared distances between the image and object points.

    Overall Steps:
  1. Call RANSAC with EPNP: The RANdom Sample Consensus algorithm is called to remove "wrong" point correspondences which makes the solvePnP more robust. The so called inliers are used for calculation, wrong correspondences (outliers) will be ignored. Therefore the method below will first run a solvePnP with the EPNP method and returns the reprojection error. EPNP works like the following:
    • Choose the 4 control pints: C0 as centroid of reference points,
      C1, C2 and C3 from PCA of the reference points
    • Compute barycentric coordinates with the control points
    • Derivate the image reference points with the above
  2. Optimize inlier matches
  3. Call PnP ITERATIVE: General problem: We have a calibrated cam and sets of corresponding 2D/3D points. We will calculate the rotation and translation in respect to world coordinates.
    • If for no extrinsic guess, begin with computation
    • If planarity is detected, find homography, otherwise use DLT method
    • After sucessful determination of a pose, optimize it with
      Levenberg-Marquardt (iterative part)
Returns
True if the pose was found.

Definition at line 568 of file CVTrackedFeatures.cpp.

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 }
vector< cv::Point3f > CVVPoint3f
Definition: CVTypedefs.h:79
const CVMat & cameraMat() const
const CVMat & distortion() const
SLFeatureMarker2D _marker
2D marker data
CVCalibration * _calib
Current calibration in use.
HighResTimer _timer
High resolution timer.
Definition: CVTracked.h:94
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
CVVPoint3f keypoints3D
3D feature points in mm
CVVDMatch inlierMatches
matches that lead to correct transform
bool useExtrinsicGuess
flag if extrinsic gues should be used
CVMat rvec
Rotation of the camera pose.
CVMat tvec
Translation of the camera pose.
CVVDMatch matches
matches between video decriptors and marker descriptors
CVVKeyPoint keypoints
2D keypoints detected in video frame
CVVPoint3f inlierPoints3D
Inlier 3D points after RANSAC on the marker.

◆ detectKeypointsAndDescriptors()

void CVTrackedFeatures::detectKeypointsAndDescriptors ( )
private

Get keypoints and descriptors in one step. This is a more efficient way since we have to build the scaling pyramide only once. If we detect and describe seperatly, it will lead in two scaling pyramids and is therefore less meaningful.

Definition at line 485 of file CVTrackedFeatures.cpp.

486 {
487  float startMS = _timer.elapsedTimeInMilliSec();
488 
492 
494 }
void detectAndDescribe(CVInputArray image, CVVKeyPoint &keypoints, CVOutputArray descriptors, CVInputArray mask=cv::noArray())
CVFeatureManager _featureManager
Feature detector-descriptor wrapper instance.
static AvgFloat detectTimesMS
Averaged time for video feature detection & description in ms.
Definition: CVTracked.h:83
CVMat imageGray
Reference to grayscale video frame.
CVMat descriptors
Descriptors of keypoints.

◆ drawDebugInformation()

void CVTrackedFeatures::drawDebugInformation ( bool  drawDetection)
private

Visualizes the following parts of the whole Pose estimation:

  • Keypoints
  • Inlier matches
  • Optical Flow (Small arrows that show how keypoints moved between frames)
  • Reprojection with the calculated Pose

Definition at line 305 of file CVTrackedFeatures.cpp.

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 }
cv::Point2f CVPoint2f
Definition: CVTypedefs.h:43
cv::Mat CVMat
Definition: CVTypedefs.h:38
vector< cv::DMatch > CVVDMatch
Definition: CVTypedefs.h:89
vector< cv::KeyPoint > CVVKeyPoint
Definition: CVTypedefs.h:88
bool _isTracking
True if tracking.
bool drawDetection()
Definition: CVTracked.h:64
CVMat imageGray
Grayscale image of the marker.
CVVKeyPoint keypoints2D
2D keypoints in pixels
CVMat image
Reference to color video frame.

◆ forceRelocation() [1/2]

bool CVTrackedFeatures::forceRelocation ( )
inline

Definition at line 75 of file CVTrackedFeatures.h.

75 { return _forceRelocation; }

◆ forceRelocation() [2/2]

void CVTrackedFeatures::forceRelocation ( bool  fR)
inline

Definition at line 79 of file CVTrackedFeatures.h.

79 { _forceRelocation = fR; }

◆ getFeatureMatches()

CVVDMatch CVTrackedFeatures::getFeatureMatches ( )
private

Get matching features with the defined feature matcher. Since we are using the k-next-neighbour matcher, we check if the best and second best match are not too identical with the so called ratio test.

Returns
Vector of found matches

Definition at line 501 of file CVTrackedFeatures.cpp.

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 }
vector< vector< cv::DMatch > > CVVVDMatch
Definition: CVTypedefs.h:101
static AvgFloat matchTimesMS
Averaged time for video feature matching in ms.
Definition: CVTracked.h:86
CVMat descriptors
Descriptors of the 2D keypoints.

◆ initFeaturesOnMarker()

void CVTrackedFeatures::initFeaturesOnMarker ( )
private

Prepares the reference tracker:

  1. Detect and describe the keypoints on the reference image
  2. Set up 3D points with predefined scaling
  3. Perform optional drawing operations on image

Definition at line 134 of file CVTrackedFeatures.cpp.

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 }

◆ loadMarker()

void CVTrackedFeatures::loadMarker ( string  markerFilename)
private

Loads the marker image form the filesystem.

Definition at line 107 of file CVTrackedFeatures.cpp.

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 }
@ IOK_image
Definition: SLFileStorage.h:40
OpenCV image class with the same interface as the former SLImage class.
Definition: CVImage.h:64
bool exists(std::string path, SLIOStreamKind kind)
Checks whether a given file exists.
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

◆ optimizeMatches()

void CVTrackedFeatures::optimizeMatches ( )
private

To get more matches with the calculated pose, we reproject the reference points to our current frame. Within a predefined patch, we try to rematch not matched features with the reprojected point. If not possible, we increase the patch size until we found a match for the point or we reach a threshold.

Definition at line 664 of file CVTrackedFeatures.cpp.

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 }
double translationError
double rotationError
cv::DMatch CVDMatch
Definition: CVTypedefs.h:62

◆ relocate()

void CVTrackedFeatures::relocate ( )
private

If relocation should be done, the following steps are necessary:

  1. Detect keypoints
  2. Describe keypoints (Binary descriptors)
  3. Match keypoints in current frame and the reference tracker
  4. Try to calculate new Pose with Perspective-n-Point algorithm

Definition at line 273 of file CVTrackedFeatures.cpp.

274 {
275  _isTracking = false;
279 
280  // Zero time keeping on the tracking branch
282 }
CVVDMatch getFeatureMatches()
static AvgFloat optFlowTimesMS
Averaged time for video feature optical flow tracking in ms.
Definition: CVTracked.h:87

◆ track()

bool CVTrackedFeatures::track ( CVMat  imageGray,
CVMat  image,
CVCalibration calib 
)
finalvirtual

The main part of this tracker is to calculate a correct Pose.

Parameters
imageGrayCurrent grayscale frame
imageCurrent RGB frame
calibCalibration information
Returns
So far always false

Implements CVTracked.

Definition at line 218 of file CVTrackedFeatures.cpp.

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 }
int frames_since_posefound
void drawDebugInformation(bool drawDetection)
CVMatx44f _objectViewMat
view transformation matrix
Definition: CVTracked.h:93
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

◆ tracking()

void CVTrackedFeatures::tracking ( )
private

To track the already detected keypoints after a sucessful pose estimation, we track the features with optical flow

Definition at line 288 of file CVTrackedFeatures.cpp.

289 {
290  _isTracking = true;
292 
293  // Zero time keeping on the relocation branch
296 }
bool trackWithOptFlow(CVMat rvec, CVMat tvec)

◆ trackWithOptFlow()

bool CVTrackedFeatures::trackWithOptFlow ( CVMat  rvec,
CVMat  tvec 
)
private

Tracks the features with Optical Flow (Lucas Kanade). This will only try to predict the new location of keypoints. If they were found, we perform a solvePnP to get the new Pose from feature tracking. The method performs tests if the Pose is good enough (not too much difference between previous and new Pose).

Parameters
rvecRotation vector (will be used for extrinsic guess)
tvecTranslation vector (will be used for extrinsic guess)
Returns
True if Pose found, false otherwise

Definition at line 850 of file CVTrackedFeatures.cpp.

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 }
cv::Size CVSize
Definition: CVTypedefs.h:55
T abs(T a)
Definition: Utils.h:249

◆ transferFrameData()

void CVTrackedFeatures::transferFrameData ( )
private

Copies the current frame data to the previous frame data struct for the next frame handling. TODO: more elegant way to do this whole copy action

Definition at line 444 of file CVTrackedFeatures.cpp.

◆ type() [1/2]

CVDetectDescribeType CVTrackedFeatures::type ( )
inline

Definition at line 76 of file CVTrackedFeatures.h.

76 { return _featureManager.type(); }
CVDetectDescribeType type()

◆ type() [2/2]

void CVTrackedFeatures::type ( CVDetectDescribeType  ddType)

Setter of the feature detector & descriptor type.

Definition at line 200 of file CVTrackedFeatures.cpp.

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 }
void createDetectorDescriptor(CVDetectDescribeType detectDescribeType)
Creates a detector and decriptor to the passed type.

Member Data Documentation

◆ _calib

CVCalibration* CVTrackedFeatures::_calib
private

Current calibration in use.

Definition at line 96 of file CVTrackedFeatures.h.

◆ _currentFrame

SLFrameData CVTrackedFeatures::_currentFrame
private

The current video frame data.

Definition at line 129 of file CVTrackedFeatures.h.

◆ _featureManager

CVFeatureManager CVTrackedFeatures::_featureManager
private

Feature detector-descriptor wrapper instance.

Definition at line 132 of file CVTrackedFeatures.h.

◆ _forceRelocation

bool CVTrackedFeatures::_forceRelocation
private

Force relocation every frame (no opt. flow tracking)

Definition at line 131 of file CVTrackedFeatures.h.

◆ _frameCount

int CVTrackedFeatures::_frameCount
private

NO. of frames since process start.

Definition at line 97 of file CVTrackedFeatures.h.

◆ _isTracking

bool CVTrackedFeatures::_isTracking
private

True if tracking.

Definition at line 98 of file CVTrackedFeatures.h.

◆ _marker

SLFeatureMarker2D CVTrackedFeatures::_marker
private

2D marker data

Definition at line 128 of file CVTrackedFeatures.h.

◆ _matcher

cv::Ptr<cv::DescriptorMatcher> CVTrackedFeatures::_matcher
private

Descriptor matching algorithm.

Definition at line 95 of file CVTrackedFeatures.h.

◆ _prevFrame

SLFrameData CVTrackedFeatures::_prevFrame
private

The previous video frame data.

Definition at line 130 of file CVTrackedFeatures.h.


The documentation for this class was generated from the following files: