OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
PoseEstimator_RANSAC.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 // Internal Includes
26 #include "PoseEstimator_RANSAC.h"
27 #include "CameraParameters.h"
28 #include "LED.h"
29 #include "PinholeCameraFlip.h"
30 #include "UsefulQuaternions.h"
31 #include "cvToEigen.h"
32 
33 // Library/third-party includes
34 #include <opencv2/calib3d/calib3d.hpp>
35 #include <opencv2/core/affine.hpp>
36 #include <opencv2/core/core.hpp>
37 
38 // Standard includes
39 #include <algorithm>
40 #include <iostream>
41 
45 #undef OSVR_UVBI_TEST_RANSAC_REPROJECTION
46 
47 static const float MAX_REPROJECTION_ERROR = 4.f;
48 
49 namespace osvr {
50 namespace vbtracker {
52  operator()(CameraParameters const &camParams, LedPtrList const &leds,
53  BeaconStateVec const &beacons,
54  std::vector<BeaconData> &beaconDebug, Eigen::Vector3d &outXlate,
55  Eigen::Quaterniond &outQuat, int skipBrightsCutoff,
56  std::size_t iterations) {
57 
58  bool skipBrights = false;
59 
60  if (skipBrightsCutoff > 0) {
61  int nonBrights =
62  std::count_if(begin(leds), end(leds),
63  [](Led *ledPtr) { return !ledPtr->isBright(); });
64  if (nonBrights >= skipBrightsCutoff) {
65  skipBrights = true;
66  // std::cout << "will be skipping brights!" << std::endl;
67  }
68  }
69 
70  // We need to get a pair of matched vectors of points: 2D locations
71  // with in the image and 3D locations in model space. There needs to
72  // be a correspondence between the points in these vectors, such that
73  // the ith element in one matches the ith element in the other. We
74  // make these by looking up the locations of LEDs with known identifiers
75  // and pushing both onto the vectors at the same time.
76 
77  std::vector<cv::Point3f> objectPoints;
78  std::vector<cv::Point2f> imagePoints;
79  std::vector<ZeroBasedBeaconId> beaconIds;
80  for (auto const &led : leds) {
81  if (skipBrights && led->isBright()) {
82  continue;
83  }
84  auto id = makeZeroBased(led->getID());
85  auto index = asIndex(id);
86  beaconDebug[index].variance = -1;
87  beaconDebug[index].measurement = led->getLocationForTracking();
88  beaconIds.push_back(id);
89 
92  imagePoints.push_back(led->getLocationForTracking());
93  objectPoints.push_back(
94  vec3dToCVPoint3f(beacons[index]->stateVector()));
95  }
96 
97  // Make sure we have enough points to do our estimation.
98  if (objectPoints.size() < m_permittedOutliers + m_requiredInliers) {
99  return false;
100  }
101 
102  // Produce an estimate of the translation and rotation needed to take
103  // points from model space into camera space. We allow for at most
104  // m_permittedOutliers outliers. Even in simulation data, we sometimes
105  // find duplicate IDs for LEDs, indicating that we are getting
106  // mis-identified ones sometimes.
107  // We tried using the previous guess to reduce the amount of computation
108  // being done, but this got us stuck in infinite locations. We seem to
109  // do okay without using it, so leaving it out.
110  // @todo Make number of iterations into a parameter.
111  bool usePreviousGuess = false;
112  cv::Mat inlierIndices;
113 
114  cv::Mat rvec;
115  cv::Mat tvec;
116 #if CV_MAJOR_VERSION == 2
117  cv::solvePnPRansac(
118  objectPoints, imagePoints, camParams.cameraMatrix,
119  camParams.distortionParameters, rvec, tvec, usePreviousGuess,
120  iterations, MAX_REPROJECTION_ERROR,
121  static_cast<int>(objectPoints.size() - m_permittedOutliers),
122  inlierIndices);
123 #elif CV_MAJOR_VERSION == 3
124  // parameter added to the OpenCV 3.0 interface in place of the number of
125  // inliers
128  double confidence = 0.99;
129  auto ransacResult = cv::solvePnPRansac(
130  objectPoints, imagePoints, camParams.cameraMatrix,
131  camParams.distortionParameters, rvec, tvec, usePreviousGuess,
132  iterations, MAX_REPROJECTION_ERROR, confidence, inlierIndices);
133  if (!ransacResult) {
134  return false;
135  }
136 #else
137 #error "Unrecognized OpenCV version!"
138 #endif
139 
140  //==========================================================================
141  // Make sure we got all the inliers we needed. Otherwise, reject this
142  // pose.
143  if (inlierIndices.rows < static_cast<int>(m_requiredInliers)) {
144  return false;
145  }
146 
147  if (inlierIndices.rows > 0) {
148 
149 #ifdef OSVR_UVBI_TEST_RANSAC_REPROJECTION
150  //==========================================================================
151  // Reproject the inliers into the image and make sure they are
152  // actually
153  // close to the expected location; otherwise, we have a bad pose.
154  const double pixelReprojectionErrorForSingleAxisMax = 4;
155  std::vector<cv::Point3f> inlierObjectPoints;
156  std::vector<cv::Point2f> inlierImagePoints;
157  for (int i = 0; i < inlierIndices.rows; i++) {
158  inlierObjectPoints.push_back(objectPoints[i]);
159  inlierImagePoints.push_back(imagePoints[i]);
160  }
161  std::vector<cv::Point2f> reprojectedPoints;
162  cv::projectPoints(
163  inlierObjectPoints, rvec, tvec, camParams.cameraMatrix,
164  camParams.distortionParameters, reprojectedPoints);
165 
166  for (size_t i = 0; i < reprojectedPoints.size(); i++) {
167  if (reprojectedPoints[i].x - inlierImagePoints[i].x >
168  pixelReprojectionErrorForSingleAxisMax) {
169  std::cout << "Reject on reprojected beacon id "
170  << makeOneBased(inlierBeaconIds[i]).value()
171  << " x axis." << std::endl;
172  return false;
173  }
174  if (reprojectedPoints[i].y - inlierImagePoints[i].y >
175  pixelReprojectionErrorForSingleAxisMax) {
176  std::cout << "Reject on reprojected beacon id "
177  << makeOneBased(inlierBeaconIds[i]).value()
178  << " y axis." << std::endl;
179  return false;
180  }
181  }
182 #endif
183 
185  std::vector<ZeroBasedBeaconId> inlierBeaconIds;
186  for (int i = 0; i < inlierIndices.rows; i++) {
187  inlierBeaconIds.push_back(beaconIds[i]);
188  }
189 
192 
193  // Need a custom comparator for the ID type.
194  auto idComparator = [](ZeroBasedBeaconId const &lhs,
195  ZeroBasedBeaconId const &rhs) {
196  return lhs.value() < rhs.value();
197  };
198  std::sort(begin(inlierBeaconIds), end(inlierBeaconIds),
199  idComparator);
200  // This lambda wraps binary_search to do what it says: check to see
201  // if a given beacon ID is in the list of inlier beacons.
202  auto isAnInlierBeacon = [&inlierBeaconIds, &idComparator](
203  ZeroBasedBeaconId const &needle) {
204  return std::binary_search(begin(inlierBeaconIds),
205  end(inlierBeaconIds), needle,
206  idComparator);
207  };
208  for (auto &led : leds) {
209  if (isAnInlierBeacon(led->getID())) {
210  led->markAsUsed();
211  }
212  }
213  }
214 
215  //==========================================================================
216  // Convert this into an OSVR representation of the transformation that
217  // gives the pose of the HDK origin in the camera coordinate system,
218  // switching units to meters and encoding the angle in a unit
219  // quaternion.
220  // The matrix described by rvec and tvec takes points in model space
221  // (the space where the LEDs are defined, which is in mm away from an
222  // implied origin) into a coordinate system where the center is at the
223  // camera's origin, with X to the right, Y down, and Z in the direction
224  // that the camera is facing (but still in the original units of mm):
225  // |Xc| |r11 r12 r13 t1| |Xm|
226  // |Yc| = |r21 r22 r23 t2|*|Ym|
227  // |Zc| |r31 r32 r33 t3| |Zm|
228  // |1 |
229  // That is, it rotates into the camera coordinate system and then adds
230  // the translation, which is in the camera coordinate system.
231  // This is the transformation we want, since it reports the sensor's
232  // position and orientation in camera space, except that we want to
233  // convert
234  // the units into meters and the orientation into a Quaternion.
235  // NOTE: This is a right-handed coordinate system with X pointing
236  // towards the right from the camera center of projection, Y pointing
237  // down, and Z pointing along the camera viewing direction, if the input
238  // points are not inverted.
239 
241  Eigen::Affine3d xform = Eigen::Affine3d(cv::Affine3d(rvec, tvec));
242  Eigen::Vector3d xlate(xform.translation());
243  Eigen::Quaterniond quat(xform.rotation());
244  if (!xlate.array().allFinite()) {
245  std::cout << "[UnifiedTracker] Computed a non-finite position with "
246  "RANSAC."
247  << std::endl;
248  return false;
249  }
250  if (!quat.coeffs().array().allFinite()) {
251  std::cout << "[UnifiedTracker] Computed a non-finite orientation "
252  "with RANSAC."
253  << std::endl;
254  return false;
255  }
256 
257  // -z means the wrong side of the pinhole
259  if (xlate.z() < 0) {
260 
261 #if 0
262  std::cout << "On the wrong side of the looking glass:"
263  << xlate.transpose() << std::endl;
264 #endif
265  pinholeCameraFlipPose(xlate, quat);
266  }
267 
268  if (!xlate.array().allFinite()) {
269  std::cout << "[UnifiedTracker] Computed a non-finite position with "
270  "RANSAC post-pinhole-flip."
271  << std::endl;
272  return false;
273  }
274  if (!quat.coeffs().array().allFinite()) {
275  std::cout << "[UnifiedTracker] Computed a non-finite orientation "
276  "with RANSAC post-pinhole-flip."
277  << std::endl;
278  return false;
279  }
280  if (quat.w() < 0) {
281  // arbitrarily choose w to be positive.
282  quat = Eigen::Quaterniond(-quat.coeffs());
283  }
284  outXlate = xlate;
285  outQuat = quat;
286  return true;
287  }
288 
290  static const double InitialPositionStateError = 0.;
292  static const double InitialOrientationStateError = 0.5;
293  static const double InitialVelocityStateError = 0.;
294  static const double InitialAngVelStateError = 0.;
295  static const double InitialStateError[] = {
296  InitialPositionStateError, InitialPositionStateError,
297  InitialPositionStateError, InitialOrientationStateError,
298  InitialOrientationStateError, InitialOrientationStateError,
299  InitialVelocityStateError, InitialVelocityStateError,
300  InitialVelocityStateError, InitialAngVelStateError,
301  InitialAngVelStateError, InitialAngVelStateError};
303  LedPtrList const &leds) {
304  Eigen::Vector3d xlate;
305  Eigen::Quaterniond quat;
307  {
308  auto ret = (*this)(p.camParams, leds, p.beacons, p.beaconDebug,
309  xlate, quat);
310  if (!ret) {
311  return false;
312  }
313  }
316  p.state.position() = xlate;
317  p.state.setQuaternion(quat);
319 #if 1
320  p.state.incrementalOrientation() = Eigen::Vector3d::Zero();
321  p.state.velocity() = Eigen::Vector3d::Zero();
322  p.state.angularVelocity() = Eigen::Vector3d::Zero();
323 #endif
324  using StateVec = kalman::types::DimVector<BodyState>;
325  using StateSquareMatrix = kalman::types::DimSquareMatrix<BodyState>;
326 
327  StateSquareMatrix covariance = StateVec(InitialStateError).asDiagonal();
329  /*
330  covariance.bottomRightCorner<3, 3>() =
331  p.state.errorCovariance().bottomRightCorner<3, 3>();
332  */
333  p.state.setErrorCovariance(covariance);
334  return true;
335  }
336 
337 } // namespace vbtracker
338 } // 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
void setQuaternion(Eigen::Quaterniond const &quaternion)
Intended for startup use.
Definition: PoseState.h:189
Vector< Dimension< T >::value > DimVector
A vector of length = dimension of T.
bool isBright() const
Returns the most-recent boolean "bright" state according to the LED identifier. Note that the value i...
Definition: LED.h:143
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.
Header.
bool operator()(CameraParameters const &camParams, LedPtrList const &leds, BeaconStateVec const &beacons, std::vector< BeaconData > &beaconDebug, Eigen::Vector3d &outXlate, Eigen::Quaterniond &outQuat, int skipBrightsCutoff=-1, std::size_t iterations=5)