OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
TrackingDebugDisplay.cpp
Go to the documentation of this file.
1 
11 // Copyright 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 "TrackingDebugDisplay.h"
27 #include "CameraParameters.h"
28 #include "SBDBlobExtractor.h"
29 #include "TrackedBody.h"
30 #include "TrackedBodyTarget.h"
31 #include "TrackingSystem.h"
32 #include "cvToEigen.h"
33 
34 // Library/third-party includes
35 #include <opencv2/highgui/highgui.hpp> // for GUI window
36 
37 #include <opencv2/imgproc/imgproc.hpp> // for drawing capabilities
38 
39 // Standard includes
40 #include <algorithm>
41 #include <iostream>
42 #include <vector>
43 
44 namespace osvr {
45 namespace vbtracker {
46  static const auto CVCOLOR_RED = cv::Vec3b(0, 0, 255);
47  static const auto CVCOLOR_YELLOW = cv::Vec3b(0, 255, 255);
48  static const auto CVCOLOR_GREEN = cv::Vec3b(0, 255, 0);
49  static const auto CVCOLOR_BLUE = cv::Vec3b(255, 0, 0);
50  static const auto CVCOLOR_GRAY = cv::Vec3b(127, 127, 127);
51  static const auto CVCOLOR_BLACK = cv::Vec3b(0, 0, 0);
52  static const auto CVCOLOR_WHITE = cv::Vec3b(255, 255, 255);
53  static const auto CVCOLOR_VIOLET = cv::Vec3b(127, 0, 255);
54 
55  static const auto DEBUG_WINDOW_NAME = "OSVR Tracker Debug Window";
56  static const auto DEBUG_FRAME_STRIDE = 11;
57  TrackingDebugDisplay::TrackingDebugDisplay(ConfigParams const &params)
58  : m_enabled(params.debug), m_windowName(DEBUG_WINDOW_NAME),
59  m_debugStride(DEBUG_FRAME_STRIDE),
60  m_performingOptimization(params.performingOptimization) {
61  if (!m_enabled) {
62  return;
63  }
64  // cv::namedWindow(m_windowName);
65  if (m_performingOptimization) {
66  m_mode = DebugDisplayMode::StatusWithAllReprojections;
67  } else {
68  std::cout << "\nVideo-based tracking debug windows help:\n";
69  std::cout
70  << " - press 's' to show the detected blobs and the status of "
71  "recognized beacons (default)\n"
72  << " - press 'p' to show the same status view as above, but "
73  "with the (re)projected locations of even non-detected "
74  "beacons drawn\n"
75  << " - press 'b' to show the labeled blobs and the "
76  "reprojected beacons\n"
77  << " - press 'i' to show the raw input image\n"
78  << " - press 't' to show the blob-detecting threshold image\n"
79  << " - press 'q' to quit the debug windows (tracker will "
80  "continue operation)\n"
81  << std::endl;
82  }
83  }
84 
85  void TrackingDebugDisplay::showDebugImage(cv::Mat const &image,
86  bool needsCopy) {
87  if (needsCopy) {
88  image.copyTo(m_displayedFrame);
89  } else {
90  m_displayedFrame = image;
91  }
92  cv::imshow(m_windowName, m_displayedFrame);
93  }
94 
95  void TrackingDebugDisplay::quitDebug() {
96  if (!m_enabled) {
97  return;
98  }
99  cv::destroyWindow(m_windowName);
100  m_enabled = false;
101  }
102 
104  // explicit WindowCoordsPoint(cv::Point2f p) : point(p) {}
105  cv::Point2f point;
106  };
107 
108  inline WindowCoordsPoint invertLoc(cv::Mat const &image, cv::Point2f loc) {
109 
110  return USING_INVERTED_LED_POSITION
111  ? WindowCoordsPoint{cv::Point2f(image.cols - loc.x,
112  image.rows - loc.y)}
113  : WindowCoordsPoint{loc};
114  }
115 
116  inline Eigen::Vector2d pointToEigenVec(cv::Point2f pt) {
117  return Eigen::Vector2d(pt.x, pt.y);
118  }
119  inline cv::Point2f eigenVecToPoint(Eigen::Vector2d const &vec) {
120  return cv::Point2f(vec.x(), vec.y());
121  }
122  namespace {
125  class DebugImage {
126  public:
127  explicit DebugImage(cv::Mat &im) : image_(im) {}
128  DebugImage(DebugImage const &) = delete;
129  DebugImage &operator=(DebugImage const &) = delete;
130 
133  void drawLedCircle(Led const &led, bool filled, cv::Vec3b color) {
134  cv::circle(image_, led.getLocation(),
135  led.getMeasurement().diameter / 2.,
136  cv::Scalar(color), filled ? -1 : 1);
137  }
138 
141  void drawLedLabel(OneBasedBeaconId id, WindowCoordsPoint const &loc,
142  cv::Vec3b color = CVCOLOR_GRAY, double size = 0.5,
143  cv::Point2f offset = cv::Point2f(0, 0)) {
144  auto label = std::to_string(id.value());
145  cv::putText(image_, label, loc.point + offset,
146  cv::FONT_HERSHEY_SIMPLEX, size, cv::Scalar(color));
147  }
148 
150  void drawLedLabel(OneBasedBeaconId id, cv::Point2f location,
151  cv::Vec3b color = CVCOLOR_GRAY, double size = 0.5,
152  cv::Point2f offset = cv::Point2f(0, 0)) {
153  drawLedLabel(id, toWindowCoords(location), color, size, offset);
154  }
155 
158  void drawLedLabel(OneBasedBeaconId id, Eigen::Vector2d const &loc,
159  cv::Vec3b color = CVCOLOR_GRAY, double size = 0.5,
160  cv::Point2f offset = cv::Point2f(0, 0)) {
161  drawLedLabel(id, eigenVecToPoint(loc), color, size, offset);
162  }
163 
166  void drawLedLabel(Led const &led, cv::Vec3b color = CVCOLOR_GRAY,
167  double size = 0.5,
168  cv::Point2f offset = cv::Point2f(0, 0)) {
169  drawLedLabel(led.getOneBasedID(),
170  WindowCoordsPoint{led.getLocation()}, color, size,
171  offset);
172  }
173 
174  void drawStatusMessage(std::string message,
175  cv::Vec3b color = CVCOLOR_GREEN,
176  std::int16_t line = 1) {
177  static const auto LINE_OFFSET = 20.f;
178  cv::putText(image_, message, cv::Point2f(0, line * LINE_OFFSET),
179  cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(color));
180  }
181 
182  WindowCoordsPoint toWindowCoords(cv::Point2f loc) const {
183  return USING_INVERTED_LED_POSITION
184  ? WindowCoordsPoint{cv::Point2f(image_.cols - loc.x,
185  image_.rows - loc.y)}
186  : WindowCoordsPoint{loc};
187  }
188 
189  WindowCoordsPoint
190  vecToWindowCoords(Eigen::Vector2d const &loc) const {
191  return toWindowCoords(eigenVecToPoint(loc));
192  }
193 
194  cv::Mat &image() { return image_; }
195 
196  private:
197  cv::Mat &image_;
198  };
199  } // namespace
200  namespace {
202  class Reprojection {
203  public:
204  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
205  Reprojection(TrackedBodyTarget const &target,
206  CameraParameters const &camParams)
207  : m_body(target.getBody()),
208  m_xform3d(m_body.getState().getIsometry()),
209  m_offset(target.getTargetToBody()),
210  m_fl(camParams.focalLength()),
211  m_pp(camParams.eiPrincipalPoint()) {}
212 
215  Eigen::Vector2d operator()(Eigen::Vector3d const &bodyPoint) {
216  Eigen::Vector3d camPoints = m_xform3d * (bodyPoint + m_offset);
217  return (camPoints.head<2>() / camPoints[2]) * m_fl + m_pp;
218  }
219 
220  private:
221  TrackedBody const &m_body;
222  Eigen::Isometry3d m_xform3d;
223  Eigen::Vector3d m_offset;
224  double m_fl;
225  Eigen::Vector2d m_pp;
226  };
227  } // namespace
228  inline void drawOriginAxes(Reprojection &reproject, DebugImage &dbgImg,
229  double size = 0.02) {
230  auto drawLine = [&](Eigen::Vector3d const &axis, cv::Vec3b color) {
231  auto beginWindowPoint =
232  dbgImg.vecToWindowCoords(reproject(axis * 0.5 * size));
233  auto endWindowPoint =
234  dbgImg.vecToWindowCoords(reproject(axis * -0.5 * size));
235  cv::line(dbgImg.image(), beginWindowPoint.point,
236  endWindowPoint.point, cv::Scalar(color));
237  };
238 
239  drawLine(Eigen::Vector3d::UnitX(), CVCOLOR_RED);
240  drawLine(Eigen::Vector3d::UnitY(), CVCOLOR_GREEN);
241  drawLine(Eigen::Vector3d::UnitZ(), CVCOLOR_BLUE);
242  }
243 
244  inline Eigen::Vector2d
245  getBeaconReprojection(Reprojection &reprojection,
246  TrackedBodyTarget const &target,
247  ZeroBasedBeaconId beaconId) {
248 
249 #if 0
250  // We want to un-compensate for the beacon offset here.
251  return reprojection(target.getBeaconAutocalibPosition(beaconId) -
252  target.getBeaconOffset());
253 #else
254  return reprojection(target.getBeaconAutocalibPosition(beaconId));
255 #endif
256  }
257 
258  cv::Mat TrackingDebugDisplay::createAnnotatedBlobImage(
259  TrackingSystem const &tracking, CameraParameters const &camParams,
260  cv::Mat const &blobImage) {
261  cv::Mat output;
262  blobImage.copyTo(output);
263  DebugImage img(output);
264  if (tracking.getNumBodies() == 0) {
266  img.drawStatusMessage(
267  "No tracked bodies registered, only showing detected blobs",
268  CVCOLOR_RED);
269  return output;
270  }
272  auto &body = tracking.getBody(BodyId{0});
273 
274  auto targetPtr = body.getTarget(TargetId{0});
275 
276  if (!targetPtr) {
278  img.drawStatusMessage(
279  "No target registered on body 0, only showing detected blobs",
280  CVCOLOR_RED);
281  return output;
282  }
283  const auto labelOffset = cv::Point2f(1, 1);
284  const auto textSize = 0.5;
285 
287  auto &leds = targetPtr->leds();
288  for (auto &led : leds) {
289  img.drawLedLabel(led, CVCOLOR_RED, textSize, labelOffset);
290  }
291 
292  if (targetPtr->hasPoseEstimate()) {
294  Reprojection reproject{*targetPtr, camParams};
295 
296  auto numBeacons = targetPtr->getNumBeacons();
297  using size_type = decltype(numBeacons);
298  for (size_type i = 0; i < numBeacons; ++i) {
299  auto beaconId = ZeroBasedBeaconId(i);
300  Eigen::Vector2d imagePoint =
301  getBeaconReprojection(reproject, *targetPtr, beaconId);
302  img.drawLedLabel(makeOneBased(beaconId), imagePoint,
303  CVCOLOR_GREEN, textSize, labelOffset);
304  }
305  } else {
306  img.drawStatusMessage("No video tracker pose for this "
307  "target, so green reprojection "
308  "not shown",
309  CVCOLOR_GRAY);
310  }
311  return output;
312  }
313 
314  cv::Mat TrackingDebugDisplay::createStatusImage(
315  TrackingSystem const &tracking, CameraParameters const &camParams,
316  cv::Mat const &baseImage, bool reprojectUnseenBeacons) {
317  cv::Mat output;
318 
319  DebugImage img(output);
320  baseImage.copyTo(output);
321 
322  if (tracking.getNumBodies() == 0) {
324  img.drawStatusMessage("No tracked bodies registered, "
325  "showing raw input image - press "
326  "b to show blobs",
327  CVCOLOR_RED);
328  return output;
329  }
331  auto &body = tracking.getBody(BodyId{0});
332 
333  auto targetPtr = body.getTarget(TargetId{0});
334 
335  if (!targetPtr) {
337  img.drawStatusMessage("No target registered on body 0, "
338  "showing raw input image - press "
339  "b to show blobs",
340  CVCOLOR_RED);
341  return output;
342  }
343 
346 
347  const auto textSize = 0.25;
348  const auto mainBeaconLabelColor = CVCOLOR_BLUE;
349  const auto baseBeaconLabelColor =
350  m_performingOptimization ? CVCOLOR_WHITE : CVCOLOR_BLACK;
351 
352  auto gotPose = targetPtr->hasPoseEstimate();
353  auto numBeacons = targetPtr->getNumBeacons();
354 
355  using BeaconIdContainer = std::vector<ZeroBasedBeaconId>;
356 
357  auto drawnBeaconIds = BeaconIdContainer{};
358  auto recordBeaconAsDrawn = [&](ZeroBasedBeaconId id) {
359  drawnBeaconIds.push_back(id);
360  };
361 
364  auto drawUnidentifiedBlob = [&img](Led const &led) {
366  img.drawLedCircle(led, false, CVCOLOR_RED);
367  };
368 
369  if (gotPose) {
371  Reprojection reproject{*targetPtr, camParams};
372 
374  drawOriginAxes(reproject, img);
375 
376  for (auto const &led : targetPtr->leds()) {
377  if (led.identified()) {
379 
380  auto beaconId = led.getID();
381  recordBeaconAsDrawn(beaconId);
382 
383  // Color-code identified beacons based on
384  // whether or not we used their data.
385  auto color =
386  led.wasUsedLastFrame() ? CVCOLOR_GREEN : CVCOLOR_YELLOW;
387  img.drawLedCircle(led, true, color);
388 
392 
394  img.drawLedLabel(led, baseBeaconLabelColor, textSize);
395 
397  Eigen::Vector2d imagePoint =
398  getBeaconReprojection(reproject, *targetPtr, beaconId);
399  auto windowPoint =
400  WindowCoordsPoint{eigenVecToPoint(imagePoint)};
401  img.drawLedLabel(makeOneBased(beaconId), imagePoint,
402  mainBeaconLabelColor, textSize);
403  } else {
404  drawUnidentifiedBlob(led);
405  }
406  }
407  if (reprojectUnseenBeacons) {
409  auto comparator = [](ZeroBasedBeaconId const &a,
410  ZeroBasedBeaconId const &b) {
411  return a.value() < b.value();
412  };
413  std::sort(begin(drawnBeaconIds), end(drawnBeaconIds),
414  comparator);
415  auto haveDrawnBeaconAlready = [&](ZeroBasedBeaconId id) {
416  return std::binary_search(begin(drawnBeaconIds),
417  end(drawnBeaconIds), id,
418  comparator);
419  };
421  auto numBeacons = targetPtr->getNumBeacons();
422  for (UnderlyingBeaconIdType i = 0; i < numBeacons; ++i) {
423  auto beaconId = ZeroBasedBeaconId(i);
424  if (haveDrawnBeaconAlready(beaconId)) {
426  continue;
427  }
429  Eigen::Vector2d imagePoint =
430  getBeaconReprojection(reproject, *targetPtr, beaconId);
431  auto windowPoint =
432  WindowCoordsPoint{eigenVecToPoint(imagePoint)};
433  img.drawLedLabel(makeOneBased(beaconId), imagePoint,
434  CVCOLOR_VIOLET, textSize);
435  }
436  }
437  } else {
439  for (auto const &led : targetPtr->leds()) {
440  if (led.identified()) {
441  // If identified, but we don't have a pose, draw
442  // them as yellow outlines.
443  img.drawLedCircle(led, false, CVCOLOR_YELLOW);
444  img.drawLedLabel(led, baseBeaconLabelColor, textSize);
445  } else {
446  drawUnidentifiedBlob(led);
447  }
448  }
449  }
450 
451  return output;
452  }
453 
454  void
456  TrackingSystem::Impl const &impl) {
457  if (!m_enabled) {
459  return;
460  }
461 
462  m_debugStride.advance();
463  if (!m_debugStride) {
465  return;
466  }
467  auto &blobEx = impl.blobExtractor;
469  switch (m_mode) {
470  case DebugDisplayMode::InputImage:
471  showDebugImage(impl.frame);
472  break;
473  case DebugDisplayMode::Thresholding:
474  showDebugImage(blobEx->getDebugThresholdImage());
475  break;
476  case DebugDisplayMode::Blobs:
477  showDebugImage(
478  createAnnotatedBlobImage(tracking, impl.camParams,
479  blobEx->getDebugBlobImage()),
480  false);
481  break;
482  case DebugDisplayMode::Status:
483  showDebugImage(
484  createStatusImage(tracking, impl.camParams, impl.frame), false);
485  break;
486  case DebugDisplayMode::StatusWithAllReprojections:
487  showDebugImage(
488  createStatusImage(tracking, impl.camParams, impl.frame, true),
489  false);
490  break;
491  }
492 
494  int key = cv::waitKey(1) & 0xff;
495  if (m_performingOptimization) {
496  // Don't handle any key presses - not safe to switch if running the
497  // optimizer.
498  return;
499  }
500 
501  switch (key) {
502 
503  case 's':
504  case 'S':
505  // Show the concise "status" image (default)
506  msg() << "'s' pressed - Switching to the status image in the "
507  "debug window."
508  << std::endl;
509  m_mode = DebugDisplayMode::Status;
510  break;
511 
512  case 'b':
513  case 'B':
514  // Show the blob/keypoints image
515  msg() << "'b' pressed - Switching to the blobs image in the "
516  "debug window."
517  << std::endl;
518  m_mode = DebugDisplayMode::Blobs;
519  break;
520 
521  case 'i':
522  case 'I':
523  // Show the input image.
524  msg() << "'i' pressed - Switching to the input image in the "
525  "debug window."
526  << std::endl;
527  m_mode = DebugDisplayMode::InputImage;
528  break;
529 
530  case 't':
531  case 'T':
532  // Show the thresholded image
533  msg() << "'t' pressed - Switching to the thresholded image in "
534  "the debug window."
535  << std::endl;
536  m_mode = DebugDisplayMode::Thresholding;
537  break;
538 
539  case 'p':
540  case 'P':
541  msg() << "'p' pressed - Switching to the status image with all "
542  "reprojections in the debug window."
543  << std::endl;
544  m_mode = DebugDisplayMode::StatusWithAllReprojections;
545  break;
546 
547  case 'q':
548  case 'Q':
549  // Close the debug window.
550  msg() << "'q' pressed - quitting the debug window." << std::endl;
551  quitDebug();
552  break;
553 
554  case 'o':
555  case 'O':
556  // toggle orientation from IMU
558  {
559  auto newState = !tracking.getParams().imu.useOrientation;
560  msg() << "Toggling orientation usage to " << std::boolalpha
561  << newState << std::endl;
562  tracking.setUseIMU(newState);
563  break;
564  }
565  default:
566  // something else or nothing at all, no worries.
567  break;
568  }
569  }
570 
571  std::ostream &TrackingDebugDisplay::msg() const {
572  return std::cout << "[Tracking Debug Display] ";
573  }
574 
575 } // namespace vbtracker
576 } // namespace osvr
Private implementation structure for TrackingSystem.
ConfigParams const & getParams() const
detail::size< coerce_list< Ts...>> size
Get the size of a list (number of elements.)
Definition: Size.h:56
IMUInputParams imu
IMU input-related parameters.
Definition: ConfigParams.h:254
Header.
void triggerDisplay(TrackingSystem &tracking, TrackingSystem::Impl const &impl)
bool useOrientation
Should orientation reports be used once calibration completes?
Definition: ConfigParams.h:52
double Scalar
Common scalar type.
CameraParameters camParams
Cached copy of the last (undistorted) camera parameters to be used.