OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
PoseEstimator_SCAATKalman.cpp
Go to the documentation of this file.
1 
11 // Copyright 2015, 2016 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 #include <iostream>
27 #include <util/Stride.h>
28 static ::util::Stride debugStride{401};
29 
30 #if 0
31 template <typename T>
32 inline void dumpKalmanDebugOuput(const char name[], const char expr[],
33  T const &value) {
34  if (debugStride) {
35  std::cout << "\n(Kalman Debug Output) " << name << " [" << expr
36  << "]:\n" << value << std::endl;
37  }
38 }
39 #define OSVR_KALMAN_DEBUG_OUTPUT(Name, Value) \
40  dumpKalmanDebugOuput(Name, #Value, Value)
41 #endif
42 
43 // Internal Includes
44 #include "ImagePointMeasurement.h"
45 #include "LED.h"
46 #include "PinholeCameraFlip.h"
48 #include "UsefulQuaternions.h"
49 #include "cvToEigen.h"
50 
51 // Library/third-party includes
56 
57 #include <util/Stride.h>
58 
59 // Standard includes
60 #include <algorithm>
61 #include <iostream>
62 #include <iterator> // back_inserter
63 #include <random>
64 
65 #undef OSVR_DEBUG_VELOCITY
66 #undef OSVR_VARIANCE_PENALTY_FOR_FIXED_BEACONS
67 #undef OSVR_CHECK_BOUNDING_BOXES
68 #undef OSVR_TRY_LIMITING_ANGULAR_VELOCITY_CHANGE
69 #undef OSVR_DEBUG_EMISSION_DIRECTION
70 
71 namespace osvr {
72 namespace vbtracker {
73  static const auto DIM_BEACON_CUTOFF_TO_SKIP_BRIGHTS = 4;
74 
79  static const auto MISIDENTIFIED_BEACON_CUTOFF = 3;
80 
83  static const auto SQUARED_MAX_RESIDUAL_FACTOR_FOR_ID_REJECT = 4;
84  SCAATKalmanPoseEstimator::SCAATKalmanPoseEstimator(
85  ConfigParams const &params)
86  : m_shouldSkipBright(params.shouldSkipBrightLeds),
87  m_maxResidual(params.maxResidual),
88  m_maxSquaredResidual(params.maxResidual * params.maxResidual),
89  m_maxZComponent(params.maxZComponent),
90  m_highResidualVariancePenalty(params.highResidualVariancePenalty),
91  m_beaconProcessNoise(params.beaconProcessNoise),
92  m_noveltyPenaltyBase(params.tuning.noveltyPenaltyBase),
93  m_noBeaconLinearVelocityDecayCoefficient(
94  params.noBeaconLinearVelocityDecayCoefficient),
95  m_brightLedVariancePenalty(params.brightLedVariancePenalty),
96  m_measurementVarianceScaleFactor(
97  params.measurementVarianceScaleFactor),
98  m_distanceMeasVarianceBase(params.tuning.distanceMeasVarianceBase),
99  m_distanceMeasVarianceIntercept(
100  params.tuning.distanceMeasVarianceIntercept),
101  m_extraVerbose(params.extraVerbose),
102  m_randEngine(std::random_device()()) {
103  std::tie(m_minBoxRatio, m_maxBoxRatio) =
104  std::minmax({params.boundingBoxFilterRatio,
105  1.f / params.boundingBoxFilterRatio});
106 
107  const auto maxSquaredResidual = params.maxResidual * params.maxResidual;
108  }
109 
110  inline double xyDistanceFromMetersToPixels(double xyDistance,
111  double depthInMeters,
112  CameraModel const &cam) {
113  return (xyDistance / depthInMeters) * cam.focalLength;
114  }
115  inline double squaredXyDistanceFromMetersToPixels(double xyDistance,
116  double depthInMeters,
117  CameraModel const &cam) {
118  return (xyDistance * xyDistance / (depthInMeters * depthInMeters)) *
119  cam.focalLength * cam.focalLength;
120  }
121 
123  operator()(EstimatorInOutParams const &p, LedPtrList const &leds,
124  osvr::util::time::TimeValue const &frameTime, double videoDt) {
125  bool gotMeasurement = false;
126  double varianceFactor = 1;
127 
128  const auto inBoundsID = leds.size();
129  // Default to using all the measurements we can
130  auto skipBright = false;
131 
132  auto skipAll = false;
133  auto numBeaconsToUse = std::size_t{0};
134  {
135  auto inBoundsBright = std::size_t{0};
136  auto inBoundsRound = std::size_t{0};
137 
139  for (auto const &ledPtr : leds) {
140  auto &led = *ledPtr;
141  if (led.isBright()) {
142  inBoundsBright++;
143  }
144 
145  auto boundingBoxRatioResult = inBoundingBoxRatioRange(led);
146  if (TriBool::True == boundingBoxRatioResult) {
147  inBoundsRound++;
148  }
149  }
150 
152  if (inBoundsID - inBoundsBright >
153  DIM_BEACON_CUTOFF_TO_SKIP_BRIGHTS &&
154  m_shouldSkipBright) {
155  skipBright = true;
156  }
157  if (0 == inBoundsID) {
158  m_framesWithoutIdentifiedBlobs++;
159  } else {
160  m_framesWithoutIdentifiedBlobs = 0;
161  }
162  numBeaconsToUse =
163  skipBright ? (inBoundsID - inBoundsBright) : inBoundsID;
164  if (m_lastUsableBeaconsSeen > 0 && numBeaconsToUse == 1) {
165  // If we're losing sight of beacons and we can only see one,
166  // it's likely to give us nasty velocity, so in that case we
167  // skip it too.
168  skipAll = true;
169  // Set this here, because goodLeds will be empty below.
170  m_lastUsableBeaconsSeen = numBeaconsToUse;
171  }
172  }
173 
174  if (p.startingTime != frameTime) {
176  auto dt = util::time::duration(frameTime, p.startingTime);
177  // auto dt = osvrTimeValueDurationSeconds(&frameTime,
178  // &p.startingTime);
179  kalman::predict(p.state, p.processModel, dt);
180  p.state.externalizeRotation();
181  }
182 
183  auto numBad = std::size_t{0};
184  auto numGood = std::size_t{0};
185  auto goodLeds = filterLeds(leds, skipBright, skipAll, numBad, p);
186 
187  if (!skipAll) {
188  // if we were in skipAll mode, we set this above.
189  m_lastUsableBeaconsSeen = goodLeds.size();
190  }
191 
192  if (goodLeds.empty() && p.startingTime != frameTime) {
195  auto dt = util::time::duration(frameTime, p.startingTime);
196  auto atten = std::pow(m_noBeaconLinearVelocityDecayCoefficient, dt);
197  p.state.velocity() *= atten;
198  }
199 
201  std::shuffle(begin(goodLeds), end(goodLeds), m_randEngine);
202 
203 #if 0
204  static ::util::Stride varianceStride{ 809 };
205  if (++varianceStride) {
206  std::cout << p.state.errorCovariance().diagonal().transpose()
207  << std::endl;
208  }
209 #endif
210 
211  CameraModel cam;
212  cam.focalLength = p.camParams.focalLength();
213  cam.principalPoint = p.camParams.eiPrincipalPoint();
214  ImagePointMeasurement meas{cam, p.targetToBody};
215 
217 
218  for (auto &ledPtr : goodLeds) {
219  auto &led = *ledPtr;
220 
221  auto id = led.getID();
222  auto index = asIndex(id);
223 
224  auto &debug = p.beaconDebug[index];
225 
226  auto localVarianceFactor = varianceFactor;
227  auto newIdentificationVariancePenalty =
228  std::pow(m_noveltyPenaltyBase, led.novelty());
229 
232  if (p.beaconFixed[index]) {
233  beaconProcess.setNoiseAutocorrelation(0);
234 #ifdef OSVR_VARIANCE_PENALTY_FOR_FIXED_BEACONS
235  localVarianceFactor *= 2;
239 #endif
240  } else {
241  beaconProcess.setNoiseAutocorrelation(m_beaconProcessNoise);
242  kalman::predict(*(p.beacons[index]), beaconProcess, videoDt);
243  }
244 
247  meas.setMeasurement(
248  cvToVector(led.getLocationForTracking()).cast<double>());
249 
250  auto state =
251  kalman::makeAugmentedState(p.state, *(p.beacons[index]));
252  meas.updateFromState(state);
253 
263  Eigen::Vector2d residual = meas.getResidual(state);
264  auto squaredResidual = residual.squaredNorm();
265  // Compute what the squared, pixel-space residual would be for the
266  // configured, max-tolerable residual in meters at the beacon depth
267  // before applying the penalty
268  auto depth = meas.getBeaconInCameraSpace().z();
269  auto maxSquaredResidual =
270  squaredXyDistanceFromMetersToPixels(m_maxResidual, depth, cam);
271  if (squaredResidual > maxSquaredResidual) {
272  // OK, it's bad, but is it really bad?
273 
276  numBad++;
277 
278  // Let's see if it's really bad and thus likely actually some
279  // other object that we've mis-recognized as a beacon, like a
280  // lighthouse base station.
281  if (squaredResidual >
282  squaredXyDistanceFromMetersToPixels(
283  SQUARED_MAX_RESIDUAL_FACTOR_FOR_ID_REJECT *
284  m_maxResidual,
285  depth, cam)) {
286  // Yeah, it's really bad, throw it out!
287  markAsPossiblyMisidentified(led);
288  continue;
289  }
290 
291  // OK, it's just probably a low-quality measurement but
292  // not a measurement of something else.
293  localVarianceFactor *= m_highResidualVariancePenalty;
294  } else {
295  // It's reasonable!
296  numGood++;
297  }
298 
301  markAsUsed(led);
302 
303  debug.residual.x = residual.x();
304  debug.residual.y = residual.y();
305 #if 0
306  auto effectiveVariance =
309  localVarianceFactor * m_measurementVarianceScaleFactor *
310  newIdentificationVariancePenalty *
311  (led.isBright() ? m_brightLedVariancePenalty : 1.) *
312  p.beaconMeasurementVariance[index] / led.getMeasurement().area;
313 #else
314  auto effectiveVariance = localVarianceFactor *
320  m_measurementVarianceScaleFactor *
321  newIdentificationVariancePenalty *
322  p.beaconMeasurementVariance[index] *
323  getVarianceFromBeaconDepth(depth);
324 #endif
325  debug.variance = effectiveVariance;
326  meas.setVariance(effectiveVariance);
327 
329  auto model = kalman::makeAugmentedProcessModel(p.processModel,
330  beaconProcess);
331 
332  auto correction = kalman::beginCorrection(state, model, meas);
333  if (!correction.stateCorrectionFinite) {
334  std::cout << "Non-finite state correction processing beacon "
335  << led.getOneBasedID().value() << std::endl;
336  continue;
337  }
338 #ifdef OSVR_TRY_LIMITING_ANGULAR_VELOCITY_CHANGE
339 
346  static const auto MaxAngVelChangeFromOneBeacon = 3 * M_PI;
347  static const auto MaxAnglVelChangeSquared =
348  MaxAngVelChangeFromOneBeacon * MaxAngVelChangeFromOneBeacon;
349  auto angVelChangeSquared =
350  correction.stateCorrection.segment<3>(9).squaredNorm();
351  if (angVelChangeSquared > MaxAnglVelChangeSquared) {
352  std::cout << "Got too high of a angular velocity correction "
353  "from report from "
354  << led.getOneBasedID().value() << ": magnitude "
355  << std::sqrt(angVelChangeSquared) << std::endl;
356  continue;
357  }
358 #endif
359  correction.finishCorrection();
360 
361  gotMeasurement = true;
362  }
363 
364  handlePossiblyMisidentifiedLeds();
365 
366  if (gotMeasurement) {
367  // Re-symmetrize error covariance.
369  0.5 * p.state.errorCovariance() +
370  0.5 * p.state.errorCovariance().transpose();
371  p.state.errorCovariance() = cov;
372 
373 #ifdef OSVR_DEBUG_VELOCITY
374  {
375  static ::util::Stride s(77);
376  if (++s) {
377  if (p.state.velocity().squaredNorm() > 0.01) {
378  // fast enough to say something about
379  std::cout
380  << "Velocity: " << p.state.velocity().transpose()
381  << "\n";
382  }
383  double angSpeed = p.state.angularVelocity().norm();
384  if (angSpeed > 0.1) {
385  std::cout << "AngVel: " << angSpeed << " about "
386  << (p.state.angularVelocity() / angSpeed)
387  .transpose()
388  << "\n";
389  }
390  }
391  }
392 #endif
393  }
394 
396  bool incrementProbation = false;
397  if (0 == m_framesInProbation) {
398  // Let's try to keep a 3:2 ratio of good to bad when not "in
399  // probation"
400  incrementProbation = (numBad * 3 > numGood * 2);
401 
402  } else {
403  // Already in trouble, add a bit of hysteresis and raising the bar
404  // so we don't hop out easily.
405  incrementProbation = numBad * 2 > numGood;
406  if (!incrementProbation) {
407  // OK, we're good again
408  m_framesInProbation = 0;
409  }
410  }
411  if (incrementProbation) {
412  m_framesInProbation++;
413  }
414 
416  if (gotMeasurement) {
417  m_framesWithoutUtilizedMeasurements = 0;
418  } else {
419  if (inBoundsID > 0) {
422  m_framesWithoutUtilizedMeasurements++;
423  }
424  }
425 
426  if (gotMeasurement && p.state.position().z() < 0) {
427  if (m_extraVerbose) {
428  std::cout << "Kalman detected wrong side of the looking glass! "
429  "We can't be behind the camera and be seen, but "
430  "our position is apparently "
431  << p.state.position().transpose() << std::endl;
432  }
436  Eigen::Quaterniond quat = p.state.getQuaternion();
437  pinholeCameraFlipPose(p.state.position(), quat);
438  p.state.setQuaternion(quat);
439 
440  pinholeCameraFlipVelocities(p.state.velocity(),
441  p.state.angularVelocity());
442 #if 0
443  p.state.position() *= -1;
445  p.state.velocity() *= -1;
446 
448  p.state.angularVelocity() =
449  get180aboutZ() * p.state.angularVelocity();
450  p.state.setQuaternion(get180aboutZ() * p.state.getQuaternion());
451 #endif
452  }
453  return true;
454  }
455 
457  LedPtrList const &leds, const bool skipBright, const bool skipAll,
458  std::size_t &numBad, EstimatorInOutParams const &p) {
459  LedPtrList ret;
460 
463  const Eigen::Matrix3d rotate =
464  p.state.getCombinedQuaternion().toRotationMatrix();
466  // Eigen::RowVector3d zRotate = rotate.row(2);
467 
468  std::copy_if(
469  begin(leds), end(leds), std::back_inserter(ret), [&](Led *ledPtr) {
470  auto &led = *ledPtr;
471 
472  auto id = led.getID();
473  auto index = asIndex(id);
474 
475  auto &debug = p.beaconDebug[index];
476  debug.seen = true;
477  debug.measurement = led.getLocationForTracking();
478  if (skipAll) {
479  return false;
480  }
481 
482  // Angle of emission checking
483  // If we transform the emission vector into camera space, an LED
484  // pointed right at the camera will be -Z. Anything with a 0 or
485  // positive z component is clearly out, and realistically,
486  // anything with a z component over -0.5 is probably fairly
487  // oblique. We don't want to use such beacons since they can
488  // easily introduce substantial error.
489  double zComponent =
490  (rotate * cvToVector(p.beaconEmissionDirection[index])).z();
491 #if OSVR_DEBUG_EMISSION_DIRECTION
492 
495  // static const auto beaconInspected = OneBasedBeaconId(32);
497  // static const auto beaconInspected = OneBasedBeaconId(10);
499  static const auto beaconInspected = OneBasedBeaconId(5);
501 
502  if (makeOneBased(id) == beaconInspected) {
503  static ::util::Stride s(20);
504  if (s) {
505  auto altZ =
506  zRotate *
507  cvToVector(p.beaconEmissionDirection[index]);
508  std::cout
509  << "Beacon " << beaconInspected.value()
510  << ": alternate z component is " << altZ
511  << " and beacon emission direction is "
512  << (rotate *
513  cvToVector(p.beaconEmissionDirection[index]))
514  .transpose()
515  << std::endl;
516  }
517  s.advance();
518  }
519 #endif
520  if (zComponent > 0.) {
521  if (m_extraVerbose) {
522  std::cout
523  << "Rejecting an LED at "
524  << led.getLocationForTracking() << " claiming ID "
525  << led.getOneBasedID().value()
526  << " thus emission vec "
527  << (rotate *
528  cvToVector(p.beaconEmissionDirection[index]))
529  .transpose()
530  << std::endl;
531  }
534  markAsPossiblyMisidentified(led);
535 
539  numBad++;
540  return false;
541  }
542  if (zComponent > m_maxZComponent) {
545  if (m_extraVerbose) {
546  std::cout << "Skipping " << led.getOneBasedID().value()
547  << " with z component " << zComponent
548  << std::endl;
549  }
550  return false;
551  }
552 
553  if (skipBright && led.isBright()) {
554  return false;
555  }
556 
557 #ifdef OSVR_CHECK_BOUNDING_BOXES
558  auto boundingBoxRatioResult = inBoundingBoxRatioRange(led);
563  if (TriBool::False == boundingBoxRatioResult) {
565  numBad++;
566  return false;
567  }
568 #endif
569  return true;
570  });
571  return ret;
572  }
573 
574  SCAATKalmanPoseEstimator::TriBool
575  SCAATKalmanPoseEstimator::inBoundingBoxRatioRange(Led const &led) {
576  if (led.getMeasurement().knowBoundingBox()) {
577  auto boundingBoxRatio =
578  led.getMeasurement().boundingBoxSize().height /
579  led.getMeasurement().boundingBoxSize().width;
580  if (boundingBoxRatio > m_minBoxRatio &&
581  boundingBoxRatio < m_maxBoxRatio) {
582  return TriBool::True;
583  }
584  return TriBool::False;
585  }
586  return TriBool::Unknown;
587  }
588 
589  void SCAATKalmanPoseEstimator::markAsPossiblyMisidentified(Led &led) {
590  m_possiblyMisidentified.push_back(&led);
591  }
592 
593  void SCAATKalmanPoseEstimator::markAsUsed(Led &led) {
594  led.markAsUsed();
595  m_ledsUsed++;
596  }
597 
598  double SCAATKalmanPoseEstimator::getVarianceFromBeaconDepth(double depth) {
599  return std::pow(m_distanceMeasVarianceBase, depth * 100.) *
600  m_distanceMeasVarianceIntercept;
601  }
602 
603  void SCAATKalmanPoseEstimator::handlePossiblyMisidentifiedLeds() {
604  m_ledsConsideredMisidentifiedLastFrame = m_possiblyMisidentified.size();
605  if (m_ledsConsideredMisidentifiedLastFrame > m_ledsUsed &&
606  m_ledsConsideredMisidentifiedLastFrame >
607  MISIDENTIFIED_BEACON_CUTOFF) {
608  std::cout << "Think our model is wrong: considered "
609  << m_ledsConsideredMisidentifiedLastFrame
610  << " beacons misidentified, while only used "
611  << m_ledsUsed << std::endl;
612  m_misIDConsideredOurFault = true;
613  } else {
614  m_misIDConsideredOurFault = false;
615  for (auto &ledPtr : m_possiblyMisidentified) {
616  ledPtr->markMisidentified();
617  }
618  }
619 
620  // Get ready for the next frame.
621  m_ledsUsedLastFrame = m_ledsUsed;
622  m_ledsUsed = 0;
623  m_possiblyMisidentified.clear();
624  }
625 
626  // The total number of frames that we can have dodgy Kalman tracking for
627  // before RANSAC takes over again.
628  static const std::size_t MAX_PROBATION_FRAMES = 10;
629 
630  // static const std::size_t MAX_FRAMES_WITHOUT_MEASUREMENTS = 100;
631 
632  static const std::size_t MAX_FRAMES_WITHOUT_ID_BLOBS = 25;
633 
634  SCAATKalmanPoseEstimator::TrackingHealth
636  // Reset immediately if we have a large number of mis-identified beacons
637  // that we decided were a model error, not a recognition error.
638  if (m_misIDConsideredOurFault) {
639  return TrackingHealth::NeedsHardResetNow;
640  }
641 
642  auto needsReset = (m_framesInProbation > MAX_PROBATION_FRAMES);
643 #if 0
644  ||
645  (m_framesWithoutUtilizedMeasurements >
646  MAX_FRAMES_WITHOUT_MEASUREMENTS);
647 #endif
648  if (needsReset) {
649  return TrackingHealth::NeedsHardResetNow;
650  }
652  if (m_framesWithoutIdentifiedBlobs > MAX_FRAMES_WITHOUT_ID_BLOBS) {
653  // In this case, we force ransac once we get blobs once again.
654  return TrackingHealth::SoftResetWhenBeaconsSeen;
655  }
657  return TrackingHealth::Functioning;
658  }
659 } // namespace vbtracker
660 } // namespace osvr
Helper class to keep track of the state of a blob over time. This is used to help determine the ident...
Definition: LED.h:47
DeducedAugmentedProcessModel< ModelA, ModelB > makeAugmentedProcessModel(ModelA &a, ModelB &b)
void setQuaternion(Eigen::Quaterniond const &quaternion)
Intended for startup use.
Definition: PoseState.h:189
STL namespace.
Header wrapping include of and for warning quieting.
Eigen::Quaterniond getCombinedQuaternion() const
Definition: PoseState.h:238
StateSquareMatrix const & errorCovariance() const
P.
Definition: PoseState.h:183
double duration(TimeValue const &a, TimeValue const &b)
Get a double containing seconds between the time points.
Definition: TimeValue.h:62
SquareMatrix< Dimension< T >::value > DimSquareMatrix
A square matrix, n x n, where n is the dimension of T.
Header file for class that tracks and identifies LEDs.
CorrectionInProgress< StateType, MeasurementType > beginCorrection(StateType &state, ProcessModelType &processModel, MeasurementType &meas)
Header.
ZeroBasedBeaconId getID() const
Tells which LED I am.
Definition: LED.h:89
cv::Size2f boundingBoxSize() const
bool operator()(EstimatorInOutParams const &p, LedPtrList const &leds, osvr::util::time::TimeValue const &frameTime, double videoDt)
Standardized, portable parallel to struct timeval for representing both absolute times and time inter...
Definition: TimeValueC.h:81
LedPtrList filterLeds(LedPtrList const &leds, const bool skipBright, const bool skipAll, std::size_t &numBad, EstimatorInOutParams const &p)
DeducedAugmentedState< StateA, StateB > makeAugmentedState(StateA &a, StateB &b)
Factory function, akin to std::tie(), to make an augmented state.
osvr::util::time::TimeValue const & startingTime
Time that the state is coming in at.