OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
VideoBasedTracker.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
26 #include "VideoBasedTracker.h"
27 #include <CameraDistortionModel.h>
28 #include <UndistortMeasurements.h>
29 #include <cvToEigen.h>
30 #include <osvr/Util/CSV.h>
32 
33 // Library/third-party includes
34 #include <opencv2/core/version.hpp>
35 #include <opencv2/highgui/highgui.hpp>
36 #include <opencv2/imgproc/imgproc.hpp>
37 
38 // Standard includes
39 #include <algorithm>
40 #include <fstream>
41 #include <iostream>
42 
43 namespace osvr {
44 namespace vbtracker {
45 
46  VideoBasedTracker::VideoBasedTracker(ConfigParams const &params)
47  : m_params(params), m_blobExtractor(params.blobParams) {}
48 
49  // This version requires YOU to add your beacons! You!
50  void VideoBasedTracker::addSensor(
51  LedIdentifierPtr &&identifier, CameraParameters const &camParams,
52  std::function<void(BeaconBasedPoseEstimator &)> const &beaconAdder,
53  size_t requiredInliers, size_t permittedOutliers) {
54  m_camParams = camParams;
55  m_identifiers.emplace_back(std::move(identifier));
56  m_estimators.emplace_back(new BeaconBasedPoseEstimator(
57  camParams.createUndistortedVariant(), requiredInliers,
58  permittedOutliers, m_params));
59  m_led_groups.emplace_back();
60  beaconAdder(*m_estimators.back());
61  m_assertInvariants();
62  }
63 
64  void VideoBasedTracker::addSensor(
65  LedIdentifierPtr &&identifier, CameraParameters const &camParams,
66  Point3Vector const &locations, Vec3Vector const &emissionDirection,
67  std::vector<double> const &variance,
68  BeaconIDPredicate const &autocalibrationFixedPredicate,
69  size_t requiredInliers, size_t permittedOutliers,
70  double beaconAutocalibErrorScale) {
71  addSensor(std::move(identifier), camParams,
72  [&](BeaconBasedPoseEstimator &estimator) {
73  estimator.SetBeacons(locations, emissionDirection,
74  variance,
75  autocalibrationFixedPredicate,
76  beaconAutocalibErrorScale);
77  },
78  requiredInliers, permittedOutliers);
79  }
80 
81  void VideoBasedTracker::dumpKeypointDebugData(
82  std::vector<cv::KeyPoint> const &keypoints) {
83  {
84  std::cout << "Dumping blob detection debug data, capture frame "
85  << m_debugFrame << std::endl;
86  cv::imwrite("debug_rawimage" + std::to_string(m_debugFrame) +
87  ".png",
88  m_frame);
89  cv::imwrite("debug_blobframe" + std::to_string(m_debugFrame) +
90  ".png",
91  m_imageWithBlobs);
92  cv::imwrite("debug_thresholded" + std::to_string(m_debugFrame) +
93  ".png",
94  m_thresholdImage);
95  }
96 
97  {
98  auto filename = std::string{"debug_data" +
99  std::to_string(m_debugFrame) + ".txt"};
100  std::ofstream datafile{filename.c_str()};
101  datafile << "MinThreshold: " << m_sbdParams.minThreshold
102  << std::endl;
103  datafile << "MaxThreshold: " << m_sbdParams.maxThreshold
104  << std::endl;
105  datafile << "ThresholdStep: " << m_sbdParams.thresholdStep
106  << std::endl;
107  datafile << "Thresholds:" << std::endl;
108  for (double thresh = m_sbdParams.minThreshold;
109  thresh < m_sbdParams.maxThreshold;
110  thresh += m_sbdParams.thresholdStep) {
111  datafile << thresh << std::endl;
112  }
113  }
114  {
115  using namespace osvr::util;
116  CSV kpcsv;
117  for (auto &keypoint : keypoints) {
118  kpcsv.row() << cell("x", keypoint.pt.x)
119  << cell("y", keypoint.pt.y)
120  << cell("size", keypoint.size);
121  }
122  auto filename = std::string{"debug_blobdetect" +
123  std::to_string(m_debugFrame) + ".csv"};
124  std::ofstream csvfile{filename.c_str()};
125  kpcsv.output(csvfile);
126  csvfile.close();
127  }
128  std::cout << "Data dump complete." << std::endl;
129  m_debugFrame++;
130  }
131 
132  bool VideoBasedTracker::processImage(cv::Mat frame, cv::Mat grayImage,
133  OSVR_TimeValue const &tv,
134  PoseHandler handler) {
135  m_assertInvariants();
136  bool done = false;
137  m_frame = frame;
138  m_imageGray = grayImage;
139  auto foundLeds = m_blobExtractor.extractBlobs(grayImage);
140 
142  auto undistortedLeds = undistortLeds(foundLeds, m_camParams);
143 
144  // We allow multiple sets of LEDs, each corresponding to a different
145  // sensor, to be located in the same image. We construct a new set
146  // of LEDs for each and try to find them. It is assumed that they all
147  // have unique ID patterns across all sensors.
148  for (size_t sensor = 0; sensor < m_identifiers.size(); sensor++) {
149 
150  osvrPose3SetIdentity(&m_pose);
151  auto ledsMeasurements = undistortedLeds;
152 
153  // Locate the closest blob from this frame to each LED found
154  // in the previous frame. If it is close enough to the nearest
155  // neighbor from last time, we assume that it is the same LED and
156  // update it. If not, we delete the LED from the list. Once we
157  // have matched a blob to an LED, we remove it from the list. If
158  // there are any blobs leftover, we create new LEDs from them.
159  // @todo: Include motion estimate based on Kalman filter along with
160  // model of the projection once we have one built. Note that this
161  // will require handling the lens distortion appropriately.
162  {
163  auto &myLeds = m_led_groups[sensor];
164  auto led = begin(myLeds);
165  auto e = end(myLeds);
166  while (led != end(myLeds)) {
167  led->resetUsed();
168  auto threshold = m_params.blobMoveThreshold *
169  led->getMeasurement().diameter;
170  auto nearest = led->nearest(ledsMeasurements, threshold);
171  if (nearest == end(ledsMeasurements)) {
172  // We have no blob corresponding to this LED, so we need
173  // to delete this LED.
174  led = myLeds.erase(led);
175  } else {
176  // Update the values in this LED and then go on to the
177  // next one. Remove this blob from the list of
178  // potential matches.
179  led->addMeasurement(*nearest,
180  m_params.blobsKeepIdentity);
181  ledsMeasurements.erase(nearest);
182  ++led;
183  }
184  }
185  // If we have any blobs that have not been associated with an
186  // LED, then we add a new LED for each of them.
187  // std::cout << "Had " << Leds.size() << " LEDs, " <<
188  // keyPoints.size() << " new ones available" << std::endl;
189  for (auto &remainingLed : ledsMeasurements) {
190  myLeds.emplace_back(m_identifiers[sensor].get(),
191  remainingLed);
192  }
193  }
194  //==================================================================
195  // Compute the pose of the HMD w.r.t. the camera frame of
196  // reference.
197  bool gotPose = false;
198  if (m_estimators[sensor]) {
199 
200  // Get an estimated pose, if we have enough data.
201  OSVR_PoseState pose;
202  if (m_estimators[sensor]->EstimatePoseFromLeds(
203  m_led_groups[sensor], tv, pose)) {
204  m_pose = pose;
205  handler(static_cast<unsigned>(sensor), pose);
206  gotPose = true;
207  }
208  }
209  if (m_params.debug) {
210  // Don't display the debugging info every frame, or we can't go
211  // fast enough.
212  static const auto RED = cv::Vec3b(0, 0, 255);
213  static const auto YELLOW = cv::Vec3b(0, 255, 255);
214  static const auto GREEN = cv::Vec3b(0, 255, 0);
215  static int count = 0;
216  if (++count == 11) {
217  // Fake the thresholded image to give an idea of what the
218  // blob detector is doing.
219  m_thresholdImage = m_blobExtractor.getDebugThresholdImage();
220 
221  // Draw detected blobs as blue circles.
222  m_imageWithBlobs = m_blobExtractor.getDebugBlobImage();
223 
224  // Draw the unidentified (flying?) blobs (UFBs?) on the
225  // status image
226  m_frame.copyTo(m_statusImage);
227  for (auto const &led : m_led_groups[sensor]) {
228  auto loc = led.getLocation();
229  if (!led.identified()) {
230  drawLedCircleOnStatusImage(led, false, RED);
231  } else if (!gotPose) {
232  // If identified, but we don't have a pose, draw
233  // them as yellow outlines.
234  drawLedCircleOnStatusImage(led, false, YELLOW);
235 
236  drawRecognizedLedIdOnStatusImage(led);
237  }
238  }
239 
240  // Label the keypoints with their IDs in the "blob" image
241  for (auto &led : m_led_groups[sensor]) {
242  // Print 1-based LED ID for actual LEDs
243  auto label = std::to_string(led.getOneBasedID());
244  cv::Point where = led.getLocation();
245  where.x += 1;
246  where.y += 1;
247  cv::putText(m_imageWithBlobs, label, where,
248  cv::FONT_HERSHEY_SIMPLEX, 0.5,
249  cv::Scalar(0, 0, 255));
250  }
251 
252  // If we have a transform, reproject all of the points from
253  // the model space (the LED locations) back into the image
254  // and display them on the blob image in green.
255  if (gotPose) {
256  std::vector<cv::Point2f> imagePoints;
257  m_estimators[sensor]->ProjectBeaconsToImage(
258  imagePoints);
259  const size_t n = imagePoints.size();
260  for (size_t i = 0; i < n; ++i) {
261  // Print 1-based LED IDs
262  auto label = std::to_string(i + 1);
263  auto where = imagePoints[i];
264  where.x += 1;
265  where.y += 1;
266  cv::putText(m_imageWithBlobs, label, where,
267  cv::FONT_HERSHEY_SIMPLEX, 0.5,
268  cv::Scalar(GREEN));
269  }
270 
271  // Now, also set up the status image, as long as we have
272  // a reprojection.
273  for (auto const &led : m_led_groups[sensor]) {
274  if (led.identified()) {
275  // Color-code identified beacons based on
276  // whether or not we used their data.
277  auto color =
278  led.wasUsedLastFrame() ? GREEN : YELLOW;
279 
280  // Draw a filled circle at the keypoint.
281  drawLedCircleOnStatusImage(led, true, color);
282  // identified -> id is non-negative
283  auto id = static_cast<size_t>(led.getID());
284  if (id < n) {
285  auto reprojection = imagePoints[id];
286 
287  drawRecognizedLedIdOnStatusImage(led);
288  cv::putText(
289  m_statusImage,
290  std::to_string(led.getOneBasedID()),
291  reprojection, cv::FONT_HERSHEY_SIMPLEX,
292  0.25, cv::Scalar(0, 0, 0));
293  }
294  }
295  }
296  // end of status image setup
297  }
298 
299  if (!m_debugHelpDisplayed) {
300  std::cout
301  << "\nVideo-based tracking debug windows help:\n";
302  std::cout
303  << " - press 's' to show the detected blobs and "
304  "the status of recognized beacons (default)\n"
305  << " - press 'b' to show the labeled blobs and "
306  "the reprojected beacons\n"
307  << " - press 'i' to show the raw input image\n"
308  << " - press 't' to show the blob-detecting "
309  "threshold image\n"
310  << " - press 'p' to dump the current "
311  "auto-calibrated beacon positions to a CSV "
312  "file\n"
313  << " - press 'q' to quit the debug windows "
314  "(tracker will continue operation)\n"
315  << std::endl;
316  m_debugHelpDisplayed = true;
317  }
318  // Pick which image to show and show it.
319  if (m_frame.data) {
320  std::ostringstream windowName;
321  windowName << "Sensor" << sensor;
322  cv::imshow(windowName.str(), *m_shownImage);
323  int key = cv::waitKey(1) & 0xff;
324  switch (key) {
325  case 's':
326  // Show the concise "status" image (default)
327  m_shownImage = &m_statusImage;
328  break;
329  case 'b':
330  // Show the blob/keypoints image
331  m_shownImage = &m_imageWithBlobs;
332  break;
333  case 'i':
334  // Show the input image.
335  m_shownImage = &m_frame;
336  break;
337 
338  case 't':
339  // Show the thresholded image
340  m_shownImage = &m_thresholdImage;
341  break;
342 
343 #if 0
344  case 'd':
345  dumpKeypointDebugData(foundKeyPoints);
346  break;
347 #endif
348  case 'p':
349  // Dump the beacon positions to file.
350  {
351  std::ofstream beaconfile("beacons.csv");
352  for (auto const &estimator : m_estimators) {
353  beaconfile << "----" << std::endl;
354  estimator->dumpBeaconLocationsToStream(
355  beaconfile);
356  }
357  beaconfile.close();
358  }
359  break;
360 
361  case 'q':
362  // Indicate we want to quit.
363  done = true;
364  // Also, if we can't "quit", at least hide the debug
365  // window.
366  m_params.debug = false;
367  cv::destroyAllWindows();
368  break;
369  }
370  }
371  count = 0;
372  }
373  }
374  }
375 
376  m_assertInvariants();
377  return done;
378  }
379 
380  void VideoBasedTracker::drawLedCircleOnStatusImage(Led const &led,
381  bool filled,
382  cv::Vec3b color) {
383  cv::circle(m_statusImage, led.getLocation(),
384  led.getMeasurement().diameter / 2., cv::Scalar(color),
385  filled ? -1 : 1);
386  }
387 
388  void VideoBasedTracker::drawRecognizedLedIdOnStatusImage(Led const &led) {
389 
390  auto label = std::to_string(led.getOneBasedID());
391  cv::putText(m_statusImage, label, led.getLocation(),
392  cv::FONT_HERSHEY_SIMPLEX, 0.25, cv::Scalar(127, 127, 127));
393  }
394 
395 } // namespace vbtracker
396 } // 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
cv::Point2f getLocation() const
Reports the most-recently-added position.
Definition: LED.h:107
Header wrapping include of and for warning quieting.
void output(std::ostream &os) const
Definition: CSV.h:340
bool SetBeacons(const Point3Vector &beacons, Vec3Vector const &emissionDirection, std::vector< double > const &variance, BeaconIDPredicate const &autocalibrationFixedPredicate, double beaconAutocalibErrorScale=1)
The Util library: Functionality not necessarily coupled to any particular core library, serving more as a common base layer behind all systems.
Definition: AlignedMemory.h:41
Class to track an object that has identified LED beacons on it as seen in a camera, where the absolute location of the LEDs with respect to a common frame of reference is known. Returns the transformation that takes points from the model coordinate system to the camera coordinate system.
Header.
float diameter
Blob diameter in pixels.
Header.
A structure defining a 3D (6DOF) rigid body pose: translation and rotation.
Definition: Pose3C.h:54
RowProxy row()
Definition: CSV.h:255
void osvrPose3SetIdentity(OSVR_Pose3 *pose)
Set a pose to identity.
Definition: Pose3C.h:62
Standardized, portable parallel to struct timeval for representing both absolute times and time inter...
Definition: TimeValueC.h:81
detail::Cell< T > cell(const char *header, T const &data)
Helper free function to make a CSV cell.
Definition: CSV.h:401
double Scalar
Common scalar type.