latency-detector.h
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2017 Intel Corporation. All Rights Reserved.
3 
4 #pragma once
5 
6 #include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
7 #include <librealsense2/h/rs_internal.h> // Access librealsense internal clock
8 #include <opencv2/opencv.hpp> // Include OpenCV API
9 #include "../cv-helpers.hpp" // Helper functions for conversions between RealSense and OpenCV
10 #include "../../../src/concurrency.h" // We are borrowing from librealsense concurrency infrastructure for this sample
11 #include <algorithm>
12 
13 // Helper class to keep track of measured data
14 // and generate basic statistics
15 template<class T>
17 {
18 public:
19  measurement(int cap = 10) : _capacity(cap), _sum() {}
20 
21  // Media over rolling window
22  T median() const
23  {
24  std::lock_guard<std::mutex> lock(_m);
25 
26  if (_total == 0) return _sum;
27 
28  std::vector<T> copy(begin(_data), end(_data));
29  std::sort(begin(copy), end(copy));
30  return copy[copy.size() / 2];
31  }
32 
33  // Average over all samples
34  T avg() const
35  {
36  std::lock_guard<std::mutex> lock(_m);
37  if (_total > 0) return _sum / _total;
38  return _sum;
39  }
40 
41  // Total count of measurements
42  int total() const
43  {
44  std::lock_guard<std::mutex> lock(_m);
45  return _total;
46  }
47 
48  // Add new measurement
49  void add(T val)
50  {
51  std::lock_guard<std::mutex> lock(_m);
52  _data.push_back(val);
53  if (_data.size() > _capacity)
54  {
55  _data.pop_front();
56  }
57  _total++;
58  _sum += val;
59  }
60 
61 private:
62  mutable std::mutex _m;
64  std::deque<T> _data;
65  int _capacity;
66  int _total = 0;
67 };
68 
69 // Helper class to encode / decode numbers into
70 // binary sequences, with 2-bit checksum
72 {
73 public:
74  bit_packer(int digits)
75  : _digits(digits), _bits(digits, false)
76  {
77  }
78 
79  void reset()
80  {
81  std::fill(_bits.begin(), _bits.end(), false);
82  }
83 
84  // Try to reconstruct the number from bits inside the class
85  bool try_unpack(int* number)
86  {
87  // Calculate and verify Checksum
88  auto on_bits = std::count_if(_bits.begin() + 2, _bits.end(),
89  [](bool f) { return f; });
90  if ((on_bits % 2 == 1) == _bits[0] &&
91  ((on_bits / 2) % 2 == 1) == _bits[1])
92  {
93  int res = 0;
94  for (int i = 2; i < _digits; i++)
95  {
96  res = res * 2 + _bits[i];
97  }
98  *number = res;
99  return true;
100  }
101  else return false;
102  }
103 
104  // Try to store the number as bits into the class
105  bool try_pack(int number)
106  {
107  if (number < 1 << (_digits - 2))
108  {
109  _bits.clear();
110  while (number)
111  {
112  _bits.push_back(number & 1);
113  number >>= 1;
114  }
115 
116  // Pad with zeros
117  while (_bits.size() < _digits) _bits.push_back(false);
118  reverse(_bits.begin(), _bits.end());
119 
120  // Apply 2-bit Checksum
121  auto on_bits = std::count_if(_bits.begin() + 2, _bits.end(),
122  [](bool f) { return f; });
123  _bits[0] = (on_bits % 2 == 1);
124  _bits[1] = ((on_bits / 2) % 2 == 1);
125 
126  return true;
127  }
128  else return false;
129  }
130 
131  // Access bits array
132  std::vector<bool>& get() { return _bits; }
133 
134 private:
135  std::vector<bool> _bits;
136  int _digits;
137 };
138 
139 // Main class in charge of detecting latency measurements
140 class detector
141 {
142 public:
143  detector(int digits, int display_w)
144  : _digits(digits), _packer(digits),
145  _display_w(display_w),
146  _t([this]() { detect(); }),
147  _preview_size(600, 350),
148  _next_value(0), _next(false), _alive(true)
149  {
150  using namespace cv;
151 
153  _render_start = std::chrono::high_resolution_clock::now();
154 
155  _last_preview = Mat::zeros(_preview_size, CV_8UC1);
156  _instructions = Mat::zeros(Size(display_w, 120), CV_8UC1);
157 
158  putText(_instructions, "Point the camera at the screen. Ensure all white circles are being captured",
159  Point(display_w / 2 - 470, 30), FONT_HERSHEY_SIMPLEX,
160  0.8, Scalar(255, 255, 255), 2, LINE_AA);
161 
162  putText(_instructions, "Press any key to exit...",
163  Point(display_w / 2 - 160, 70), FONT_HERSHEY_SIMPLEX,
164  0.8, Scalar(255, 255, 255), 2, LINE_AA);
165  }
166 
168  {
169  _render_start = std::chrono::high_resolution_clock::now();
170  }
171 
172  void end_render()
173  {
174  auto duration = std::chrono::high_resolution_clock::now() - _render_start;
175  _render_time.add(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()));
176  }
177 
179  {
180  _alive = false;
181  _t.join();
182  }
183 
184  // Add new frame from the camera
186  {
187  record frame_for_processing;
188 
189  frame_for_processing.f = f;
190 
191  // Read how much time the frame spent from
192  // being released by the OS-dependent driver (Windows Media Foundation, V4L2 or libuvc)
193  // to this point in time (when it is first accessible to the application)
195  rs2_error* e;
196  _processing_time.add(static_cast<int>(rs2_get_time(&e) - toa));
197 
198  auto duration = std::chrono::high_resolution_clock::now() - _start_time;
199  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
200  frame_for_processing.ms = ms; // Store current clock into the record
201 
202  _queue.enqueue(std::move(frame_for_processing));
203  }
204 
205  // Get next value to transmit
206  // This will either be the same as the last time
207  // Or a new value when needed
209  {
210  // Capture clock for next cycle
211  if (_next.exchange(false))
212  {
214  auto duration = now - _start_time;
215  auto ms = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
216  auto next = ms % (1 << (_digits - 2));
217  if (next == _next_value) next++;
218  _next_value = next;
219  }
220  return _next_value;
221  }
222 
223  // Copy preview image stored inside into a matrix
224  void copy_preview_to(cv::Mat& display)
225  {
226  std::lock_guard<std::mutex> lock(_preview_mutex);
227  cv::Rect roi(cv::Point(_display_w / 2 - _preview_size.width / 2, 200), _last_preview.size());
228  _last_preview.copyTo(display(roi));
229 
230  cv::Rect text_roi(cv::Point(0, 580), _instructions.size());
231  _instructions.copyTo(display(text_roi));
232  }
233 
234 private:
235  struct record
236  {
238  long long ms;
239  };
240 
241  void next()
242  {
243  record r;
244  while (_queue.try_dequeue(&r));
245  _next = true;
246  }
247 
249  {
251  : _owner(owner) {}
252 
253  void abort() { _owner = nullptr; }
254 
255  ~detector_lock() { if(_owner) _owner->next(); }
256 
258  };
259 
260  // Render textual instructions
262  {
263  using namespace cv;
264 
265  std::lock_guard<std::mutex> lock(_preview_mutex);
266  _instructions = Mat::zeros(Size(_display_w, 120), CV_8UC1);
267 
268  std::stringstream ss;
269 
270  ss << "Total Collected Samples: " << _latency.total();
271 
272  putText(_instructions, ss.str().c_str(),
273  Point(80, 20), FONT_HERSHEY_SIMPLEX,
274  0.8, Scalar(255, 255, 255), 2, LINE_AA);
275 
276  ss.str("");
277  ss << "Estimated Latency: (Rolling-Median)" << _latency.median() << "ms, ";
278  ss << "(Average)" << _latency.avg() << "ms";
279 
280  putText(_instructions, ss.str().c_str(),
281  Point(80, 60), FONT_HERSHEY_SIMPLEX,
282  0.8, Scalar(255, 255, 255), 2, LINE_AA);
283 
284  ss.str("");
285  ss << "Software Processing: " << _processing_time.median() << "ms";
286 
287  putText(_instructions, ss.str().c_str(),
288  Point(80, 100), FONT_HERSHEY_SIMPLEX,
289  0.8, Scalar(255, 255, 255), 2, LINE_AA);
290  }
291 
292  // Detector main loop
293  void detect()
294  {
295  using namespace cv;
296 
297  while (_alive)
298  {
299  std::this_thread::sleep_for(std::chrono::milliseconds(1));
300  record r;
301  if (_queue.try_dequeue(&r))
302  {
303  // Make sure we request new number,
304  // UNLESS we decide to keep waiting
305  detector_lock flush_queue_after(this);
306 
307  auto color_mat = frame_to_mat(r.f);
308 
309  if (color_mat.channels() > 1)
310  cvtColor(color_mat, color_mat, COLOR_BGR2GRAY);
311  medianBlur(color_mat, color_mat, 5);
312 
313  std::vector<Vec3f> circles;
314  cv::Rect roi(Point(0, 0), Size(color_mat.size().width, color_mat.size().height / 4));
315  HoughCircles(color_mat(roi), circles, HOUGH_GRADIENT, 1, 10, 100, 30, 1, 100);
316  for (size_t i = 0; i < circles.size(); i++)
317  {
318  Vec3i c = circles[i];
319  Rect r(c[0] - c[2] - 5, c[1] - c[2] - 5, 2 * c[2] + 10, 2 * c[2] + 10);
320  rectangle(color_mat, r, Scalar(0, 100, 100), -1, LINE_AA);
321  }
322 
323  cv::resize(color_mat, color_mat, _preview_size);
324  {
325  std::lock_guard<std::mutex> lock(_preview_mutex);
326  _last_preview = color_mat;
327  }
328 
329  sort(circles.begin(), circles.end(),
330  [](const Vec3f& a, const Vec3f& b) -> bool
331  {
332  return a[0] < b[0];
333  });
334  if (circles.size() > 1)
335  {
336  int min_x = static_cast<int>(circles[0][0]);
337  int max_x = static_cast<int>(circles[circles.size() - 1][0]);
338 
339  int circle_est_size = (max_x - min_x) / (_digits + 1);
340  min_x += circle_est_size / 2;
341  max_x -= circle_est_size / 2;
342 
343  _packer.reset();
344  for (int i = 1; i < circles.size() - 1; i++)
345  {
346  const int x = static_cast<int>(circles[i][0]);
347  const int idx = static_cast<int>(_digits * ((float)(x - min_x) / (max_x - min_x)));
348  if (idx >= 0 && idx < _packer.get().size())
349  _packer.get()[idx] = true;
350  }
351 
352  int res;
353  if (_packer.try_unpack(&res))
354  {
355  if (res == _next_value)
356  {
357  auto cropped = static_cast<int>(r.ms) % (1 << (_digits - 2));
358  if (cropped > res)
359  {
360  auto avg_render_time = _render_time.avg();
361  _latency.add(static_cast<int>((cropped - res) - avg_render_time));
362  update_instructions();
363  }
364  }
365  else
366  {
367  // Only in case we detected valid number other then expected
368  // We continue processing (since this was most likely older valid frame)
369  flush_queue_after.abort();
370  }
371  }
372  }
373  }
374  }
375  }
376 
377  const int _digits;
378  const int _display_w;
379 
380  std::atomic_bool _alive;
382  std::thread _t;
384  std::chrono::high_resolution_clock::time_point _start_time;
385  std::chrono::high_resolution_clock::time_point _render_start;
386 
387  std::atomic_bool _next;
388  std::atomic<int> _next_value;
389 
390  const cv::Size _preview_size;
391  std::mutex _preview_mutex;
392  cv::Mat _last_preview;
393  cv::Mat _instructions;
394 
398 };
399 
400 
static const textual_icon lock
Definition: model-views.h:218
GLuint GLuint end
GLboolean GLboolean GLboolean b
T median() const
measurement< int > _processing_time
detector_lock(detector *owner)
rs2_metadata_type get_frame_metadata(rs2_frame_metadata_value frame_metadata) const
Definition: rs_frame.hpp:497
bool try_pack(int number)
bit_packer(int digits)
cv::Mat _last_preview
std::atomic_bool _alive
std::atomic_bool _next
std::deque< T > _data
std::chrono::high_resolution_clock::time_point _render_start
measurement< int > _latency
Exposes RealSense internal functionality for C compilers.
void end_render()
e
Definition: rmse.py:177
std::mutex _preview_mutex
void sort(sort_type m_sort_type, const std::string &in, const std::string &out)
void begin_render()
GLenum cap
Definition: glext.h:8882
GLboolean GLboolean GLboolean GLboolean a
GLuint GLfloat * val
std::thread _t
void update_instructions()
not_this_one begin(...)
GLdouble f
rs2_time_t rs2_get_time(rs2_error **error)
Definition: rs.cpp:2422
const GLubyte * c
Definition: glext.h:12690
GLdouble GLdouble r
GLdouble x
std::vector< bool > _bits
single_consumer_queue< record > _queue
std::chrono::high_resolution_clock::time_point _start_time
detector(int digits, int display_w)
void submit_frame(rs2::frame f)
const cv::Size _preview_size
int get_next_value()
measurement< int > _render_time
void next(auto_any_t cur, type2type< T, C > *)
Definition: foreach.hpp:757
int total() const
bool try_unpack(int *number)
std::atomic< int > _next_value
GLint GLsizei count
typename::boost::move_detail::remove_reference< T >::type && move(T &&t) BOOST_NOEXCEPT
measurement(int cap=10)
void display(void)
Definition: boing.c:194
T avg() const
void add(T val)
int i
GLuint res
Definition: glext.h:8856
cv::Mat _instructions
bit_packer _packer
std::mutex _m
static cv::Mat frame_to_mat(const rs2::frame &f)
Definition: cv-helpers.hpp:11
const int _display_w
void copy(void *dst, void const *src, size_t size)
Definition: types.cpp:836
void copy_preview_to(cv::Mat &display)
::geometry_msgs::Point_< std::allocator< void > > Point
Definition: Point.h:57
const int _digits


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:47:21