OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
BeaconBasedPoseEstimator.cpp
Go to the documentation of this file.
1 
11 // Copyright 2015 Sensics, Inc.
12 //
13 // Licensed under the Apache License, Version 2.0 (the "License");
14 // you may not use this file except in compliance with the License.
15 // You may obtain a copy of the License at
16 //
17 // http://www.apache.org/licenses/LICENSE-2.0
18 //
19 // Unless required by applicable law or agreed to in writing, software
20 // distributed under the License is distributed on an "AS IS" BASIS,
21 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 // See the License for the specific language governing permissions and
23 // limitations under the License.
24 
25 // Internal Includes
27 #include "ImagePointMeasurement.h" // for projectPoint
28 #include <cvToEigen.h>
29 
30 // Library/third-party includes
31 #include <opencv2/calib3d/calib3d.hpp>
32 #include <opencv2/core/affine.hpp>
34 #include <osvr/Util/EigenInterop.h>
35 
36 // Standard includes
37 #include <iostream>
38 #include <stdexcept>
39 
40 namespace osvr {
41 namespace vbtracker {
43  static const double LINEAR_SCALE_FACTOR = 1000.;
44 
45  static const auto DEFAULT_MEASUREMENT_VARIANCE = 3.0;
46 
47  // The total number of frames that we can have dodgy Kalman tracking for
48  // before RANSAC takes over again.
49  static const std::size_t MAX_PROBATION_FRAMES = 10;
50 
51  static const std::size_t MAX_FRAMES_WITHOUT_MEASUREMENTS = 50;
52 
53  static const std::size_t MAX_FRAMES_WITHOUT_ID_BLOBS = 10;
54 
56  CameraParameters const &camParams, size_t requiredInliers,
57  size_t permittedOutliers, ConfigParams const &params)
58  : m_params(params), m_camParams(camParams) {
59  m_gotPose = false;
60  m_requiredInliers = requiredInliers;
61  m_permittedOutliers = permittedOutliers;
62  }
63 
65  const Point3Vector &beacons, Vec3Vector const &emissionDirection,
66  std::vector<double> const &variance,
67  BeaconIDPredicate const &autocalibrationFixedPredicate,
68  double beaconAutocalibErrorScale) {
69  // Our existing pose won't match anymore.
70  m_gotPose = false;
71  m_beacons.clear();
72  m_updateBeaconCentroid(beacons);
73  Eigen::Matrix3d beaconError =
74  Eigen::Vector3d::Constant(m_params.initialBeaconError)
75  .asDiagonal() *
76  beaconAutocalibErrorScale;
77  auto bNum = size_t{0};
78  for (auto &beacon : beacons) {
79  auto isFixed = autocalibrationFixedPredicate(bNum + 1);
80  m_beaconFixed.push_back(isFixed);
81  m_beacons.emplace_back(new BeaconState{
82  cvToVector(beacon).cast<double>() - m_centroid,
83  isFixed ? Eigen::Matrix3d::Zero() : beaconError});
84  bNum++;
85  }
86  if (1 == variance.size()) {
89  m_beaconMeasurementVariance.resize(m_beacons.size(), variance[0]);
90  } else {
91  m_beaconMeasurementVariance = variance;
92  }
93  // ensure it's the right size
94  m_beaconMeasurementVariance.resize(m_beacons.size(),
95  DEFAULT_MEASUREMENT_VARIANCE);
96 
97  m_beaconEmissionDirection = emissionDirection;
98  if (m_beaconEmissionDirection.size() != m_beacons.size()) {
99  throw std::runtime_error("Beacon emission direction array size did "
100  "not match number of beacons!");
101  }
102  // Prep the debug data.
103  m_updateBeaconDebugInfoArray();
104  return true;
105  }
106 
107  void BeaconBasedPoseEstimator::m_updateBeaconCentroid(
108  const Point3Vector &beacons) {
109  if (m_params.offsetToCentroid) {
110  Eigen::Vector3d beaconSum = Eigen::Vector3d::Zero();
111  auto bNum = size_t{0};
112  for (auto &beacon : beacons) {
113  beaconSum += cvToVector(beacon).cast<double>();
114  bNum++;
115  }
116  m_centroid = beaconSum / bNum;
117  if (m_params.debug) {
118  std::cout << "Beacon centroid: " << m_centroid.transpose()
119  << std::endl;
120  }
121  } else {
122  m_centroid = Eigen::Vector3d::Map(m_params.manualBeaconOffset);
123  }
124  }
125 
126  void BeaconBasedPoseEstimator::m_updateBeaconDebugInfoArray() {
127  m_beaconDebugData.resize(m_beacons.size());
128  }
129 
130  bool BeaconBasedPoseEstimator::SetCameraParameters(
131  CameraParameters const &camParams) {
132  // Our existing pose won't match anymore.
133  m_gotPose = false;
134 
135  m_camParams = camParams;
136  return true;
137  }
138 
139  OSVR_PoseState BeaconBasedPoseEstimator::GetState() const {
140  OSVR_PoseState ret;
141  util::eigen_interop::map(ret).rotation() = m_state.getQuaternion();
142  util::eigen_interop::map(ret).translation() =
143  m_convertInternalPositionRepToExternal(m_state.position());
144  return ret;
145  }
146 
147  Eigen::Vector3d BeaconBasedPoseEstimator::GetLinearVelocity() const {
148  return m_state.velocity() / LINEAR_SCALE_FACTOR;
149  }
150 
151  Eigen::Vector3d BeaconBasedPoseEstimator::GetAngularVelocity() const {
152  return m_state.angularVelocity();
153  }
154 
156  LedGroup &leds, OSVR_TimeValue const &tv, OSVR_PoseState &outPose) {
157  auto ret = m_estimatePoseFromLeds(leds, tv, outPose);
158  m_gotPose = ret;
159  return ret;
160  }
161  Eigen::Vector3d
162  BeaconBasedPoseEstimator::m_convertInternalPositionRepToExternal(
163  Eigen::Vector3d const &pos) const {
164  return (pos + m_centroid) / LINEAR_SCALE_FACTOR;
165  }
166  bool BeaconBasedPoseEstimator::m_estimatePoseFromLeds(
167  LedGroup &leds, OSVR_TimeValue const &tv, OSVR_PoseState &outPose) {
168  if (m_params.streamBeaconDebugInfo) {
171  for (auto &data : m_beaconDebugData) {
172  data.reset();
173  }
174  }
175 
177  auto didReset = m_forceRansacIfKalmanNeedsReset(leds);
178  if (didReset && m_params.debug) {
179  std::cout << "Video-based tracker: lost fix, in-flight reset"
180  << std::endl;
181  }
182 
183  bool usedKalman = false;
184  bool result = false;
185  if (!m_gotPose || !m_gotPrev || !m_permitKalman) {
186  // Must use the direct approach
187  result = m_pnpransacEstimator(leds);
188  } else {
189  auto dt = osvrTimeValueDurationSeconds(&tv, &m_prev);
190  // Can use kalman approach
191  result = m_kalmanAutocalibEstimator(leds, dt);
192  usedKalman = true;
193  }
194 
195  if (!result) {
196  return false;
197  }
198  m_prev = tv;
201  m_gotPrev = true;
202 
203  //==============================================================
204  // Put into OSVR format.
205  outPose = GetState();
206  if (usedKalman) {
207  auto currentTime = util::time::getNow();
208  auto dt2 = osvrTimeValueDurationSeconds(&currentTime, &tv);
209  outPose = GetPredictedState(dt2 + m_params.additionalPrediction);
210  }
211  return true;
212  }
213 
214  bool BeaconBasedPoseEstimator::m_forceRansacIfKalmanNeedsReset(
215  LedGroup const &leds) {
216  auto needsReset = (m_framesInProbation > MAX_PROBATION_FRAMES) ||
217  (m_framesWithoutUtilizedMeasurements >
218  MAX_FRAMES_WITHOUT_MEASUREMENTS);
220  if (!needsReset &&
221  m_framesWithoutIdentifiedBlobs > MAX_FRAMES_WITHOUT_ID_BLOBS) {
222  // In this case, we force ransac once we get blobs once again.
223  auto const beaconsSize = m_beacons.size();
224  for (auto const &led : leds) {
225  if (!led.identified()) {
226  continue;
227  }
228  auto id = led.getID();
229  if (id < beaconsSize) {
230  needsReset = true;
231  break;
232  }
233  }
234  }
235  if (needsReset) {
236  m_framesInProbation = 0;
237  m_framesWithoutUtilizedMeasurements = 0;
238  m_framesWithoutIdentifiedBlobs = 0;
239 
245  using StateVec = kalman::types::DimVector<State>;
246  m_state.setStateVector(StateVec::Zero());
247 
250  m_gotPose = false;
251  }
252  return needsReset;
253  }
254 
255  bool BeaconBasedPoseEstimator::m_pnpransacEstimator(LedGroup &leds) {
256  // We need to get a pair of matched vectors of points: 2D locations
257  // with in the image and 3D locations in model space. There needs to
258  // be a correspondence between the points in these vectors, such that
259  // the ith element in one matches the ith element in the other. We
260  // make these by looking up the locations of LEDs with known identifiers
261  // and pushing both onto the vectors at the same time.
262 
263  std::vector<cv::Point3f> objectPoints;
264  std::vector<cv::Point2f> imagePoints;
265  auto const beaconsSize = m_beacons.size();
266  for (auto const &led : leds) {
267  if (!led.identified()) {
268  continue;
269  }
270  auto id = led.getID();
271  if (id < beaconsSize) {
272  m_beaconDebugData[id].variance = -1;
273  m_beaconDebugData[id].measurement = led.getLocation();
274  imagePoints.push_back(led.getLocation());
275  objectPoints.push_back(
276  vec3dToCVPoint3f(m_beacons[id]->stateVector()));
277  }
278  }
279 
280  // Make sure we have enough points to do our estimation.
281  if (objectPoints.size() < m_permittedOutliers + m_requiredInliers) {
282  return false;
283  }
284 
285  // Produce an estimate of the translation and rotation needed to take
286  // points from model space into camera space. We allow for at most
287  // m_permittedOutliers outliers. Even in simulation data, we sometimes
288  // find duplicate IDs for LEDs, indicating that we are getting
289  // mis-identified ones sometimes.
290  // We tried using the previous guess to reduce the amount of computation
291  // being done, but this got us stuck in infinite locations. We seem to
292  // do okay without using it, so leaving it out.
293  const float MaxReprojectionErrorForInlier = 87.0f;
294  bool usePreviousGuess = false;
295  int iterationsCount = 5;
296  cv::Mat inlierIndices;
297 
298 #if CV_MAJOR_VERSION == 2
299  cv::solvePnPRansac(
300  objectPoints, imagePoints, m_camParams.cameraMatrix,
301  m_camParams.distortionParameters, m_rvec, m_tvec, usePreviousGuess,
302  iterationsCount, MaxReprojectionErrorForInlier,
303  static_cast<int>(objectPoints.size() - m_permittedOutliers),
304  inlierIndices);
305 #elif CV_MAJOR_VERSION == 3
306  // parameter added to the OpenCV 3.0 interface in place of the number of
307  // inliers
310  double confidence = 0.99;
311  auto ransacResult = cv::solvePnPRansac(
312  objectPoints, imagePoints, m_camParams.cameraMatrix,
313  m_camParams.distortionParameters, m_rvec, m_tvec, usePreviousGuess,
314  iterationsCount, 8.0f, confidence, inlierIndices);
315  if (!ransacResult) {
316  return false;
317  }
318 #else
319 #error "Unrecognized OpenCV version!"
320 #endif
321 
322  //==========================================================================
323  // Make sure we got all the inliers we needed. Otherwise, reject this
324  // pose.
325  if (inlierIndices.rows < m_requiredInliers) {
326  return false;
327  }
328 
329  //==========================================================================
330  // Reproject the inliers into the image and make sure they are actually
331  // close to the expected location; otherwise, we have a bad pose.
332  const double pixelReprojectionErrorForSingleAxisMax = 4;
333  if (inlierIndices.rows > 0) {
334  std::vector<cv::Point3f> inlierObjectPoints;
335  std::vector<cv::Point2f> inlierImagePoints;
336  for (int i = 0; i < inlierIndices.rows; i++) {
337  inlierObjectPoints.push_back(objectPoints[i]);
338  inlierImagePoints.push_back(imagePoints[i]);
339  }
340  std::vector<cv::Point2f> reprojectedPoints;
341  cv::projectPoints(
342  inlierObjectPoints, m_rvec, m_tvec, m_camParams.cameraMatrix,
343  m_camParams.distortionParameters, reprojectedPoints);
344 
345  for (size_t i = 0; i < reprojectedPoints.size(); i++) {
346  if (reprojectedPoints[i].x - inlierImagePoints[i].x >
347  pixelReprojectionErrorForSingleAxisMax) {
348  return false;
349  }
350  if (reprojectedPoints[i].y - inlierImagePoints[i].y >
351  pixelReprojectionErrorForSingleAxisMax) {
352  return false;
353  }
354  }
355  }
356 
357  //==========================================================================
358  // Convert this into an OSVR representation of the transformation that
359  // gives the pose of the HDK origin in the camera coordinate system,
360  // switching units to meters and encoding the angle in a unit
361  // quaternion.
362  // The matrix described by rvec and tvec takes points in model space
363  // (the space where the LEDs are defined, which is in mm away from an
364  // implied origin) into a coordinate system where the center is at the
365  // camera's origin, with X to the right, Y down, and Z in the direction
366  // that the camera is facing (but still in the original units of mm):
367  // |Xc| |r11 r12 r13 t1| |Xm|
368  // |Yc| = |r21 r22 r23 t2|*|Ym|
369  // |Zc| |r31 r32 r33 t3| |Zm|
370  // |1 |
371  // That is, it rotates into the camera coordinate system and then adds
372  // the translation, which is in the camera coordinate system.
373  // This is the transformation we want, since it reports the sensor's
374  // position and orientation in camera space, except that we want to
375  // convert
376  // the units into meters and the orientation into a Quaternion.
377  // NOTE: This is a right-handed coordinate system with X pointing
378  // towards the right from the camera center of projection, Y pointing
379  // down, and Z pointing along the camera viewing direction.
380 
381  m_gotMeasurement = true;
382 
383  if (m_tvec.at<double>(2) < 0) {
384 // -z means the wrong side of the pinhole
386 #if 0
387  std::cout << "On the wrong side of the looking glass:" << m_tvec
388  << std::endl;
389 #endif
390  // So, we invert translation, and apply 180 rotation (to rotation)
391  // about z.
392  m_tvec *= -1;
393 
395  auto rotMat = cv::Affine3d::Mat3::eye();
396  rotMat(0, 0) = -1;
397  rotMat(1, 1) = -1;
399  m_rvec =
400  cv::Mat(cv::Affine3d(m_rvec) * (cv::Affine3d(rotMat)).rvec());
401  }
402 
403  m_resetState(cvToVector3d(m_tvec), cvRotVecToQuat(m_rvec));
404  return true;
405  }
406 
408  std::vector<cv::Point2f> &out) {
409  // Make sure we have a pose. Otherwise, we can't do anything.
410  if (!m_gotPose) {
411  return false;
412  }
413  // Convert our beacon-states into OpenCV-friendly structures.
414  Point3Vector beacons;
415  for (auto const &beacon : m_beacons) {
416  beacons.push_back(vec3dToCVPoint3f(beacon->stateVector()));
417  }
418  // Do the OpenCV projection.
419  cv::projectPoints(beacons, m_rvec, m_tvec, m_camParams.cameraMatrix,
420  m_camParams.distortionParameters, out);
421  return true;
422  }
423 
425  m_permitKalman = permitKalman;
426  }
427 
428  Eigen::Vector3d
429  BeaconBasedPoseEstimator::getBeaconAutocalibPosition(std::size_t i) const {
430  return m_beacons.at(i)->stateVector();
431  }
432 
433  Eigen::Vector3d
434  BeaconBasedPoseEstimator::getBeaconAutocalibVariance(std::size_t i) const {
435  return m_beacons.at(i)->errorCovariance().diagonal();
436  }
437 
438 #if 0
439  static const double InitialStateError[] = {.01, .01, .1, 1., 1., .1,
440  10., 10., 10., 10., 10., 10.};
441 #endif
442  static const double InitialStateError[] = {0, 0, 0, 0, 0, 0,
443  10., 10., 10., 10., 10., 10.};
444  void
445  BeaconBasedPoseEstimator::m_resetState(Eigen::Vector3d const &xlate,
446  Eigen::Quaterniond const &quat) {
447  // Note that here, units are millimeters and radians, and x and z are
448  // the lateral translation dimensions, with z being distance from camera
449  using StateVec = kalman::types::DimVector<State>;
450 
451  m_state.position() = xlate;
452 
453  m_state.setQuaternion(quat);
454  m_state.setErrorCovariance(StateVec(InitialStateError).asDiagonal());
455 
458  m_model.setNoiseAutocorrelation(
459  kalman::types::Vector<6>(m_params.processNoiseAutocorrelation));
460 
461  if (m_params.debug && m_permitKalman) {
462  std::cout << "Video-based tracker: Beacon entering run state: pos:"
463  << m_state.position().transpose() << "\n orientation: "
464  << m_state.getQuaternion().coeffs().transpose()
465  << std::endl;
466  }
467  }
468 
469  void BeaconBasedPoseEstimator::dumpBeaconLocationsToStream(
470  std::ostream &os) const {
471  Eigen::IOFormat ourFormat(Eigen::StreamPrecision, 0, ",");
472  for (auto const &beacon : m_beacons) {
473  os << (beacon->stateVector() + m_centroid)
474  .transpose()
475  .format(ourFormat)
476  << std::endl;
477  }
478  }
479 
480 } // namespace vbtracker
481 } // namespace osvr
OSVR_EXTERN_C_END double osvrTimeValueDurationSeconds(const OSVR_TimeValue *tvA, const OSVR_TimeValue *tvB)
Compute the difference between the two time values, returning the duration as a double-precision floa...
Definition: TimeValueC.h:185
void setQuaternion(Eigen::Quaterniond const &quaternion)
Intended for startup use.
Definition: PoseState.h:189
void getNow(TimeValue &tv)
Set the given TimeValue to the current time.
Definition: TimeValue.h:51
void setDamping(double posDamping, double oriDamping)
Set the damping - must be in (0, 1)
OSVR_Quaternion rotation
Orientation as a unit quaternion.
Definition: Pose3C.h:58
Header wrapping include of and for warning quieting.
bool SetBeacons(const Point3Vector &beacons, Vec3Vector const &emissionDirection, std::vector< double > const &variance, BeaconIDPredicate const &autocalibrationFixedPredicate, double beaconAutocalibErrorScale=1)
Header file for class that tracks and identifies LEDs.
General configuration parameters.
Definition: ConfigParams.h:82
BeaconBasedPoseEstimator(CameraParameters const &camParams, size_t requiredInliers=4, size_t permittedOutliers=2, ConfigParams const &params=ConfigParams{})
Constructor that expects its beacons to be set later. It is told the camera matrix and distortion coe...
Header.
bool debug
Whether to show the debug windows and debug messages.
Definition: ConfigParams.h:137
A structure defining a 3D (6DOF) rigid body pose: translation and rotation.
Definition: Pose3C.h:54
Header for interoperation between the Eigen math library, the internal mini math library, and VRPN's quatlib.
double additionalPrediction
Seconds beyond the current time to predict, using the Kalman state.
Definition: ConfigParams.h:119
Standardized, portable parallel to struct timeval for representing both absolute times and time inter...
Definition: TimeValueC.h:81
void setStateVector(StateVector const &state)
set xhat
Definition: PoseState.h:175
bool ProjectBeaconsToImage(std::vector< cv::Point2f > &outPose)
Project the beacons into image space given the most-recent estimation of pose.
EIGEN_MAKE_ALIGNED_OPERATOR_NEW bool EstimatePoseFromLeds(LedGroup &leds, OSVR_TimeValue const &tv, OSVR_PoseState &out)
Produce an estimate of the pose of the model-space origin in camera space, where the origin is at the...