OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
OfflineProcessing.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 #define OSVR_HAVE_BOOST
26 
27 // Internal Includes
28 #include "../ConfigParams.h"
29 #include "../ConfigurationParser.h"
30 #include "../MakeHDKTrackingSystem.h"
31 #include "../TrackedBodyTarget.h"
32 #include "CSVCellGroup.h"
33 #include "GenerateBlobDebugImage.h"
34 #include "QuatToEuler.h"
35 #include <CameraParameters.h>
37 #include <UndistortMeasurements.h>
38 #include <cvUtils.h>
39 #include <osvr/Util/CSV.h>
41 #include <osvr/Util/TimeValue.h>
42 
43 // Library/third-party includes
44 #include <boost/algorithm/string/predicate.hpp>
45 #include <opencv2/core/core.hpp>
46 #include <opencv2/highgui/highgui.hpp>
47 #include <opencv2/imgproc/imgproc.hpp>
48 
49 // Standard includes
50 #include <chrono>
51 #include <fstream>
52 #include <iomanip>
53 #include <iostream>
54 #include <sstream>
55 #include <stdexcept>
56 
57 namespace osvr {
58 namespace vbtracker {
59 
60  namespace {
61  struct BodyIdOrdering {
62  bool operator()(BodyId const &lhs, BodyId const &rhs) const {
63  return lhs.value() < rhs.value();
64  }
65  };
66  } // namespace
67 
69  public:
70  TrackerOfflineProcessing(ConfigParams const &initialParams)
71  : camParamsDistorted_(osvr::vbtracker::getHDKCameraParameters()),
72  camParams_(camParamsDistorted_.createUndistortedVariant()),
73  params_(initialParams), extractor_(initialParams.extractParams) {
74  params_.performingOptimization = true;
75  params_.silent = true;
76  params_.debug = false;
77  params_.streamBeaconDebugInfo = true;
78 
79  params_.offsetToCentroid = false;
80  params_.includeRearPanel = false;
81  params_.imu.path = "";
82  params_.imu.useOrientation = false;
83 
84  system_ = makeHDKTrackingSystem(params_);
85  body_ = &(system_->getBody(bodyIdOfInterest));
86  target_ = body_->getTarget(targetIdOfInterest);
87  }
88 
89  void processFrame(cv::Mat const &frame);
90 
91  bool everHadPose() const { return everHadPose_; }
92  bool hasPose() const { return hasPose_; }
93 
94  cv::Mat getDebugImage();
95 
96  void outputCSV(std::ostream &os) { csv_.output(os); }
97 
99  std::size_t getFrameCount() const { return frame_ + 1; }
100 
103  std::string carefullyFormatElapsedTime() const;
104 
105  using FrameTimeUnit = std::chrono::microseconds;
106 
107  private:
111  ImageOutputDataPtr imageProc(cv::Mat const &frame);
112  void logRow();
113 
116  const int fps_ = 100;
117  const FrameTimeUnit frameTime_ =
118  std::chrono::duration_cast<FrameTimeUnit>(std::chrono::seconds(1)) /
119  fps_;
120 
121  const BodyId bodyIdOfInterest = BodyId(0);
122  const TargetId targetIdOfInterest = TargetId(0);
124 
126  std::chrono::seconds getSeconds() const;
127 
130  FrameTimeUnit getFractionalRemainder() const;
131 
132  const CameraParameters camParamsDistorted_;
133  const CameraParameters camParams_;
134  ConfigParams params_;
135  EdgeHoleBasedLedExtractor extractor_;
136  std::unique_ptr<TrackingSystem> system_;
137  TrackedBody *body_ = nullptr;
138  TrackedBodyTarget *target_ = nullptr;
139  util::time::TimeValue currentTime_ = {};
140  LedMeasurementVec rawMeasurements_;
141  LedMeasurementVec undistortedMeasurements_;
142  cv::Mat lastFrame_;
143  util::CSV csv_;
144  std::size_t frame_ = 0;
145  bool hasPose_ = false;
146  bool everHadPose_ = false;
147  };
148 
149  void TrackerOfflineProcessing::processFrame(cv::Mat const &frame) {
150  if ((frame_ % 100) == 0) {
151  std::cout << "Processing frame " << frame_ << std::endl;
152  }
154  currentTime_.microseconds += frameTime_.count();
155  osvrTimeValueNormalize(&currentTime_);
156 
158  auto imageData = imageProc(frame);
159 
161  auto indices = system_->updateBodiesFromVideoData(std::move(imageData));
162  logRow();
163  frame_++;
164  }
165 
167  cv::Mat input = extractor_.getInputGrayImage();
168  const cv::Size sz = input.size();
171  auto getRect = [&](std::size_t i) {
172  return cv::Rect(cv::Point2i(i * sz.width, 0), sz);
173  };
174 
175  const cv::Size largerSize{sz.width * 4, sz.height};
176  cv::Mat composite{};
177  composite.create(largerSize, CV_8UC3);
179  auto copyToComposite = [&](cv::Mat const &mat, std::size_t i) {
180  cv::Mat colorSrc;
181  if (mat.channels() == 1) {
182  cv::cvtColor(mat, colorSrc, cv::COLOR_GRAY2BGR);
183  } else {
184  colorSrc = mat;
185  }
186  colorSrc.copyTo(composite(getRect(i)));
187  };
188 
189  copyToComposite(input, 0);
190  copyToComposite(extractor_.getEdgeDetectedImage(), 1);
191  copyToComposite(extractor_.getEdgeDetectedBinarizedImage(), 2);
192  copyToComposite(generateBlobDebugImage(lastFrame_, extractor_), 3);
193 
194  return composite;
195  }
196 
197  ImageOutputDataPtr
198  TrackerOfflineProcessing::imageProc(cv::Mat const &frame) {
199  lastFrame_ = frame.clone();
200  ImageOutputDataPtr ret(new ImageProcessingOutput);
201  ret->tv = currentTime_;
202  ret->frame = frame;
203  cv::Mat gray;
204  cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
205  ret->frameGray = gray;
206  ret->camParams = camParams_; // undistorted!
207  rawMeasurements_ = extractor_(gray, params_.blobParams, false);
208  undistortedMeasurements_ =
209  undistortLeds(rawMeasurements_, camParamsDistorted_);
210  ret->ledMeasurements = undistortedMeasurements_;
211  return ret;
212  }
213 
214  void TrackerOfflineProcessing::logRow() {
215  using namespace osvr::util;
216  auto row = csv_.row();
217 #if 0
218  // time as the two-part time value
219  row << cellGroup(currentTime_);
220 #endif
221 
222  // time as a pristinely-formatted decimal number of seconds
223  row << cell("Time", carefullyFormatElapsedTime());
224 
225  hasPose_ = body_->hasPoseEstimate();
226  everHadPose_ |= hasPose_;
227 
228  row << cell("TrackerDropped", hasPose_ ? "" : "0");
229 
230  if (hasPose_) {
231  Eigen::Quaterniond quat = body_->getState().getQuaternion();
232  Eigen::Vector3d xlate = body_->getState().position();
233  row << cellGroup(xlate) << cellGroup(quat)
234  << cellGroup<QuatAsEulerTag>(quat);
235  }
236  row << cell("Measurements", rawMeasurements_.size());
237 
238  std::size_t numArea = 0;
239  std::size_t numCenterPointValue = 0;
240  std::size_t numCircularity = 0;
241  std::size_t numConvexity = 0;
242  for (auto &reject : extractor_.getRejectList()) {
243  RejectReason reason = std::get<1>(reject);
244 
245  switch (reason) {
246  case RejectReason::Area:
247  numArea++;
248  break;
249  case RejectReason::CenterPointValue:
250  numCenterPointValue++;
251  break;
252  case RejectReason::Circularity:
253  numCircularity++;
254  break;
255  case RejectReason::Convexity:
256  numConvexity++;
257  break;
258  default:
259  break;
260  }
261  }
262 
263  auto getStatus = [&](TargetStatusMeasurement meas) {
264  return target_->getInternalStatusMeasurement(meas);
265  };
266  row << cell("Rejects.Area", numArea)
267  << cell("Rejects.CenterPointValue", numCenterPointValue)
268  << cell("Rejects.Circularity", numCircularity)
269  << cell("Rejects.Convexity", numConvexity)
270  << cell("Leds", getStatus(TargetStatusMeasurement::NumUsableLeds))
271  << cell("UsedLeds",
272  getStatus(TargetStatusMeasurement::NumUsedLeds));
273  if (everHadPose_) {
274  row << cell("PosErrorVariance",
275  getStatus(TargetStatusMeasurement::MaxPosErrorVariance))
276  << cell("PosErrorVarianceBound",
277  getStatus(
278  TargetStatusMeasurement::PosErrorVarianceLimit));
279  }
280  }
281 
284  template <typename Rat> inline std::size_t countDigits() {
285  auto denom = Rat::den;
286  auto ret = std::size_t{0};
287  while (denom > 1) {
288  denom /= 10;
289  ret++;
290  }
291  return ret;
292  }
293 
294  inline std::string
296  std::ostringstream os;
297  os << getSeconds().count() << ".";
298  using FractionalType = decltype(getFractionalRemainder());
299  using FractionalPeriod = FractionalType::period;
300  static const auto numDigits = countDigits<FractionalPeriod>();
301  os << std::setw(numDigits) << std::setfill('0')
302  << getFractionalRemainder().count();
303  return os.str();
304  }
305 
306  inline std::chrono::seconds TrackerOfflineProcessing::getSeconds() const {
307  return std::chrono::seconds(getFrameCount() / fps_);
308  }
309 
310  inline TrackerOfflineProcessing::FrameTimeUnit
311  TrackerOfflineProcessing::getFractionalRemainder() const {
312  auto remainderFrames = getFrameCount() % fps_;
313  auto ret = remainderFrames * frameTime_;
314  BOOST_ASSERT_MSG(
315  ret < std::chrono::seconds(1),
316  "This is a remainder - it must be less than a second!");
317  return ret;
318  }
319 
320  static bool g_saveFramesLostFix = false;
321 
322  bool processAVI(std::string const &fn, TrackerOfflineProcessing &app) {
323  cv::VideoCapture capture;
324  capture.open(fn);
325  if (!capture.isOpened()) {
326  std::cerr << "Could not open video file " << fn << std::endl;
327  return false;
328  }
329  cv::Mat frame;
330  capture >> frame;
331  while (capture.read(frame)) {
332  app.processFrame(frame);
333  if (g_saveFramesLostFix && !app.hasPose() && app.everHadPose()) {
334  // we had pose but lost it
335  std::ostringstream os;
336  os << fn << "." << app.carefullyFormatElapsedTime() << ".png";
337  cv::Mat image = app.getDebugImage();
338  cv::imwrite(os.str(), image);
339  }
340  }
341  return true;
342  }
343 
344 } // namespace vbtracker
345 } // namespace osvr
346 
347 static const auto DEBUG_FRAMES_SWITCH = "--save-debug-frames";
348 
349 using namespace osvr::util::args;
350 int main(int argc, char *argv[]) {
351 
352  bool gotParams = false;
354  std::vector<std::string> videoNames;
355  auto args = makeArgList(argc, argv);
356  try {
358  auto numJson = handle_arg(args, [&](std::string const &arg) {
359  if (!boost::iends_with(arg, ".json")) {
360  return false;
361  }
362  std::ifstream configFile(arg);
363  if (!configFile) {
364  std::cerr << "Tried to load " << arg
365  << " as a config file but could not open it!"
366  << std::endl;
367  throw std::invalid_argument(
368  "Could not open json config file passed");
369  }
370  Json::Value root;
371  Json::Reader reader;
372  if (!reader.parse(configFile, root)) {
373  std::cerr << "Could not parse " << arg << " as JSON! "
374  << reader.getFormattedErrorMessages() << std::endl;
375  throw std::runtime_error(
376  "Config file could not be parsed as JSON!");
377  }
378  params = osvr::vbtracker::parseConfigParams(root);
379  return true;
380  });
381  if (numJson > 1) {
382  std::cerr << "At most one .json config file passed to this app!"
383  << std::endl;
384  return -1;
385  }
386 
388  auto numVideoNames = handle_arg(args, [&](std::string const &arg) {
389  auto ret = boost::iends_with(arg, ".avi");
390  if (ret) {
391  videoNames.push_back(arg);
392  }
393  return ret;
394  });
395  if (numVideoNames < 1) {
396  std::cerr << "Must pass at least one video filename to this app!"
397  << std::endl;
398  return -1;
399  }
400 
401  osvr::vbtracker::g_saveFramesLostFix =
402  handle_has_iswitch(args, DEBUG_FRAMES_SWITCH);
403  if (osvr::vbtracker::g_saveFramesLostFix) {
404  std::cout << "Will save debug frames when tracker loses fix"
405  << std::endl;
406  }
407 
408  if (!args.empty()) {
409  std::cerr
410  << "Unrecognized arguments left after parsing command line!"
411  << std::endl;
412  return -1;
413  }
414  } catch (std::exception &e) {
415  std::cerr << e.what() << std::endl;
416  return -1;
417  }
418 
420  int returnValue = 0;
421  for (auto &videoName : videoNames) {
422  std::cout << "Processing input video " << videoName << std::endl;
424  auto success = osvr::vbtracker::processAVI(videoName, app);
425 
426  if (success) {
427  std::cout << "Processed a total of " << app.getFrameCount()
428  << " frames." << std::endl;
429  auto outname = videoName + ".csv";
430  std::cout << "Writing output data to: " << outname << std::endl;
431  std::ofstream of(outname);
432  if (!of) {
433  std::cout << "Can't write to that file!" << std::endl;
434  returnValue++;
435  } else {
436  app.outputCSV(of);
437  }
438  std::cout << "File finished!\n\n" << std::endl;
439  } else {
440  std::cout << "File skipped!\n\n" << std::endl;
441  returnValue++;
442  }
443  }
444 
445  if (returnValue != 0) {
446  std::cerr << "One or more errors! Press enter to exit after reviewing "
447  "the errors."
448  << std::endl;
449  std::cin.ignore();
450  }
451 
452  return returnValue;
453 }
bool silent
For optimization usage.
Definition: ConfigParams.h:89
Header containing some simple args-handling routines: nothing sophisticated, just enough to keep the ...
std::size_t getFrameCount() const
To get a time that matches the timestamp.
BlobParams blobParams
Parameters specific to the blob-detection step of the algorithm.
Definition: ConfigParams.h:102
void output(std::ostream &os) const
Definition: CSV.h:340
detail::CellGroup< T > cellGroup(const char *groupHeader, T const &data)
Helper function to create a cell group with a group header prefix.
Definition: CSVCellGroup.h:160
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
IMUInputParams imu
IMU input-related parameters.
Definition: ConfigParams.h:254
General configuration parameters.
Definition: ConfigParams.h:82
Header providing a C++ wrapper around TimeValueC.h.
Header.
bool debug
Whether to show the debug windows and debug messages.
Definition: ConfigParams.h:137
Header.
int main(int argc, char *argv[])
OSVR_TimeValue_Microseconds microseconds
Microseconds portion of the time value.
Definition: TimeValueC.h:85
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
bool useOrientation
Should orientation reports be used once calibration completes?
Definition: ConfigParams.h:52
void osvrTimeValueNormalize(OSVR_TimeValue *tv)
"Normalizes" a time value so that the absolute number of microseconds is less than 1...
Definition: TimeValueC.cpp:46
EdgeHoleParams extractParams
Parameters specific to the edge hole based LED extraction algorithm.
Definition: ConfigParams.h:105