Visual Servoing Platform  version 3.6.1 under development (2024-11-15)
grabV4l2MultiCpp11Thread.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See https://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Acquire images using 1394 device with cfox (MAC OSX) and display it
33  * using GTK or GTK.
34  *
35 *****************************************************************************/
36 
44 #include <iostream>
45 
46 #include <visp3/core/vpConfig.h>
47 
48 #if defined(VISP_HAVE_V4L2) && (defined(VISP_HAVE_X11) || defined(VISP_HAVE_GTK)) && defined(VISP_HAVE_THREADS)
49 
50 #include <condition_variable>
51 #include <iostream>
52 #include <limits>
53 #include <mutex>
54 #include <queue>
55 #include <thread>
56 
57 #include <visp3/core/vpDisplay.h>
58 #include <visp3/core/vpImageFilter.h>
59 #include <visp3/core/vpIoTools.h>
60 #include <visp3/core/vpTime.h>
61 #include <visp3/gui/vpDisplayGTK.h>
62 #include <visp3/gui/vpDisplayX.h>
63 #include <visp3/io/vpParseArgv.h>
64 #include <visp3/io/vpVideoWriter.h>
65 #include <visp3/sensor/vpV4l2Grabber.h>
66 
67 #define GETOPTARGS "d:oh"
68 
69 #ifdef ENABLE_VISP_NAMESPACE
70 using namespace VISP_NAMESPACE_NAME;
71 #endif
72 
73 namespace
74 {
75 
76 void usage(const char *name, const char *badparam)
77 {
78  fprintf(stdout, "\n\
79 SYNOPSIS:\n\
80  %s [-d <device count>] [-o] [-h]\n\
81 \n\
82 DESCRIPTION:\n\
83  Capture multiple camera streams and save the stream without slowing down the acquisition.\n\
84  \n\
85 OPTIONS: \n\
86  -d <device count> \n\
87  Open the specified number of camera streams.\n\
88  \n\
89  -o \n\
90  Save each stream in a dedicated folder.\n\
91  \n\
92  -h \n\
93  Print the help.\n\n",
94  name);
95 
96  if (badparam)
97  fprintf(stdout, "\nERROR: Bad parameter [%s]\n", badparam);
98 }
99 
100 bool getOptions(int argc, char **argv, unsigned int &deviceCount, bool &saveVideo)
101 {
102  const char *optarg;
103  const char **argv1 = (const char **)argv;
104  int c;
105  while ((c = vpParseArgv::parse(argc, argv1, GETOPTARGS, &optarg)) > 1) {
106 
107  switch (c) {
108  case 'd':
109  deviceCount = (unsigned int)atoi(optarg);
110  break;
111  case 'o':
112  saveVideo = true;
113  break;
114  case 'h':
115  usage(argv[0], nullptr);
116  return false;
117  break;
118 
119  default:
120  usage(argv[0], optarg);
121  return false;
122  break;
123  }
124  }
125 
126  if ((c == 1) || (c == -1)) {
127  // standalone param or error
128  usage(argv[0], nullptr);
129  std::cerr << "ERROR: " << std::endl;
130  std::cerr << " Bad argument " << optarg << std::endl << std::endl;
131  return false;
132  }
133 
134  return true;
135 }
136 
137 #ifndef DOXYGEN_SHOULD_SKIP_THIS
138 // Code adapted from the original author Dan MaĊĦek to be compatible with ViSP
139 // image
140 class vpFrameQueue
141 {
142 
143 public:
144  struct vpCancelled_t
145  { };
146 
147  vpFrameQueue()
148  : m_cancelled(false), m_cond(), m_queueColor(), m_maxQueueSize(std::numeric_limits<size_t>::max()), m_mutex()
149  { }
150 
151  void cancel()
152  {
153  std::lock_guard<std::mutex> lock(m_mutex);
154  m_cancelled = true;
155  m_cond.notify_all();
156  }
157 
158  // Push the image to save in the queue (FIFO)
159  void push(const vpImage<vpRGBa> &image)
160  {
161  std::lock_guard<std::mutex> lock(m_mutex);
162 
163  m_queueColor.push(image);
164 
165  // Pop extra images in the queue
166  while (m_queueColor.size() > m_maxQueueSize) {
167  m_queueColor.pop();
168  }
169 
170  m_cond.notify_one();
171  }
172 
173  // Pop the image to save from the queue (FIFO)
174  vpImage<vpRGBa> pop()
175  {
176  std::unique_lock<std::mutex> lock(m_mutex);
177 
178  while (m_queueColor.empty()) {
179  if (m_cancelled) {
180  throw vpCancelled_t();
181  }
182 
183  m_cond.wait(lock);
184 
185  if (m_cancelled) {
186  throw vpCancelled_t();
187  }
188  }
189 
190  vpImage<vpRGBa> image(m_queueColor.front());
191  m_queueColor.pop();
192 
193  return image;
194  }
195 
196  void setMaxQueueSize(const size_t max_queue_size) { m_maxQueueSize = max_queue_size; }
197 
198 private:
199  bool m_cancelled;
200  std::condition_variable m_cond;
201  std::queue<vpImage<vpRGBa> > m_queueColor;
202  size_t m_maxQueueSize;
203  std::mutex m_mutex;
204 };
205 
206 class vpStorageWorker
207 {
208 public:
209  vpStorageWorker(vpFrameQueue &queue, const std::string &filename, unsigned int width, unsigned int height)
210  : m_queue(queue), m_filename(filename), m_width(width), m_height(height)
211  { }
212 
213  // Thread main loop
214  void run()
215  {
216  vpImage<vpRGBa> O_color(m_height, m_width);
217 
218  vpVideoWriter writer;
219  if (!m_filename.empty()) {
220  writer.setFileName(m_filename);
221  writer.open(O_color);
222  }
223 
224  try {
225  for (;;) {
226  vpImage<vpRGBa> image(m_queue.pop());
227 
228  if (!m_filename.empty()) {
229  writer.saveFrame(image);
230  }
231  }
232  }
233  catch (vpFrameQueue::vpCancelled_t &) {
234  }
235  }
236 
237 private:
238  vpFrameQueue &m_queue;
239  std::string m_filename;
240  unsigned int m_width;
241  unsigned int m_height;
242 };
243 
244 class vpShareImage
245 {
246 
247 private:
248  bool m_cancelled;
249  std::condition_variable m_cond;
250  std::mutex m_mutex;
251  unsigned char *m_pImgData;
252  unsigned int m_totalSize;
253 
254 public:
255  struct vpCancelled_t
256  { };
257 
258  vpShareImage() : m_cancelled(false), m_cond(), m_mutex(), m_pImgData(nullptr), m_totalSize(0) { }
259 
260  virtual ~vpShareImage()
261  {
262  if (m_pImgData != nullptr) {
263  delete[] m_pImgData;
264  }
265  }
266 
267  void cancel()
268  {
269  std::lock_guard<std::mutex> lock(m_mutex);
270  m_cancelled = true;
271  m_cond.notify_all();
272  }
273 
274  // Get the image to display
275  void getImage(unsigned char *const imageData, const unsigned int totalSize)
276  {
277  std::unique_lock<std::mutex> lock(m_mutex);
278 
279  if (m_cancelled) {
280  throw vpCancelled_t();
281  }
282 
283  m_cond.wait(lock);
284 
285  if (m_cancelled) {
286  throw vpCancelled_t();
287  }
288 
289  // Copy to imageData
290  if (totalSize <= m_totalSize) {
291  memcpy(imageData, m_pImgData, totalSize * sizeof(unsigned char));
292  }
293  else {
294  std::cerr << "totalSize <= m_totalSize !" << std::endl;
295  }
296  }
297 
298  bool isCancelled()
299  {
300  std::lock_guard<std::mutex> lock(m_mutex);
301  return m_cancelled;
302  }
303 
304  // Set the image to display
305  void setImage(const unsigned char *const imageData, const unsigned int totalSize)
306  {
307  std::lock_guard<std::mutex> lock(m_mutex);
308 
309  if (m_pImgData == nullptr || m_totalSize != totalSize) {
310  m_totalSize = totalSize;
311 
312  if (m_pImgData != nullptr) {
313  delete[] m_pImgData;
314  }
315 
316  m_pImgData = new unsigned char[m_totalSize];
317  }
318 
319  // Copy from imageData
320  memcpy(m_pImgData, imageData, m_totalSize * sizeof(unsigned char));
321 
322  m_cond.notify_one();
323  }
324 };
325 
326 void capture(vpV4l2Grabber *const pGrabber, vpShareImage &share_image)
327 {
328  vpImage<vpRGBa> local_img;
329 
330  // Open the camera stream
331  pGrabber->open(local_img);
332 
333  while (true) {
334  if (share_image.isCancelled()) {
335  break;
336  }
337 
338  pGrabber->acquire(local_img);
339 
340  // Update share_image
341  share_image.setImage((unsigned char *)local_img.bitmap, local_img.getSize() * 4);
342  }
343 }
344 
345 void display(unsigned int width, unsigned int height, int win_x, int win_y, unsigned int deviceId,
346  vpShareImage &share_image, vpFrameQueue &queue, bool save)
347 {
348  vpImage<vpRGBa> local_img(height, width);
349 
350 #if defined(VISP_HAVE_X11)
351  vpDisplayX display;
352 #elif defined(VISP_HAVE_GTK)
353  vpDisplayGTK display;
354 #endif
355 
356  // Init Display
357  {
358  std::stringstream ss;
359  ss << "Camera stream " << deviceId;
360  display.init(local_img, win_x, win_y, ss.str());
361  }
362 
363  try {
365 
366  vpImage<unsigned char> I_red(height, width), I_green(height, width), I_blue(height, width), I_alpha(height, width);
367  vpImage<unsigned char> I_red_gaussian(height, width), I_green_gaussian(height, width),
368  I_blue_gaussian(height, width);
369  vpImage<double> I_red_gaussian_double, I_green_gaussian_double, I_blue_gaussian_double;
370 
371  bool exit = false, gaussian_blur = false;
372  while (!exit) {
373  double t = vpTime::measureTimeMs();
374 
375  // Get image
376  share_image.getImage((unsigned char *)local_img.bitmap, local_img.getSize() * 4);
377 
378  // Apply gaussian blur to simulate a computation on the image
379  if (gaussian_blur) {
380  // Split channels
381  vpImageConvert::split(local_img, &I_red, &I_green, &I_blue, &I_alpha);
382  vpImageConvert::convert(I_red, I_red_gaussian_double);
383  vpImageConvert::convert(I_green, I_green_gaussian_double);
384  vpImageConvert::convert(I_blue, I_blue_gaussian_double);
385 
386  vpImageFilter::gaussianBlur(I_red_gaussian_double, I_red_gaussian_double, 21);
387  vpImageFilter::gaussianBlur(I_green_gaussian_double, I_green_gaussian_double, 21);
388  vpImageFilter::gaussianBlur(I_blue_gaussian_double, I_blue_gaussian_double, 21);
389 
390  vpImageConvert::convert(I_red_gaussian_double, I_red_gaussian);
391  vpImageConvert::convert(I_green_gaussian_double, I_green_gaussian);
392  vpImageConvert::convert(I_blue_gaussian_double, I_blue_gaussian);
393 
394  vpImageConvert::merge(&I_red_gaussian, &I_green_gaussian, &I_blue_gaussian, nullptr, local_img);
395  }
396 
397  t = vpTime::measureTimeMs() - t;
398  std::stringstream ss;
399  ss << "Time: " << t << " ms";
400 
401  vpDisplay::display(local_img);
402 
403  vpDisplay::displayText(local_img, 20, 20, ss.str(), vpColor::red);
404  vpDisplay::displayText(local_img, 40, 20, "Left click to quit, right click for Gaussian blur.", vpColor::red);
405 
406  vpDisplay::flush(local_img);
407 
408  if (save) {
409  queue.push(local_img);
410  }
411 
412  if (vpDisplay::getClick(local_img, button, false)) {
413  switch (button) {
415  gaussian_blur = !gaussian_blur;
416  break;
417 
418  default:
419  exit = true;
420  break;
421  }
422  }
423  }
424  }
425  catch (vpShareImage::vpCancelled_t &) {
426  std::cout << "Cancelled!" << std::endl;
427  }
428 
429  share_image.cancel();
430 }
431 
432 } // Namespace
433 #endif // DOXYGEN_SHOULD_SKIP_THIS
434 
435 int main(int argc, char *argv[])
436 {
437  unsigned int deviceCount = 1;
438  unsigned int cameraScale = 1; // 640x480
439  bool saveVideo = false;
440 
441  // Read the command line options
442  if (!getOptions(argc, argv, deviceCount, saveVideo)) {
443  return EXIT_FAILURE;
444  }
445 
446  std::vector<vpV4l2Grabber *> grabbers;
447 
448  const unsigned int offsetX = 100, offsetY = 100;
449  for (unsigned int devicedId = 0; devicedId < deviceCount; devicedId++) {
450  try {
451  vpV4l2Grabber *pGrabber = new vpV4l2Grabber;
452  std::stringstream ss;
453  ss << "/dev/video" << devicedId;
454  pGrabber->setDevice(ss.str());
455  pGrabber->setScale(cameraScale);
456 
457  grabbers.push_back(pGrabber);
458  }
459  catch (const vpException &e) {
460  std::cerr << "Exception: " << e.what() << std::endl;
461  }
462  }
463 
464  std::cout << "Grabbers: " << grabbers.size() << std::endl;
465 
466  std::vector<vpShareImage> share_images(grabbers.size());
467  std::vector<std::thread> capture_threads;
468  std::vector<std::thread> display_threads;
469 
470  // Synchronized queues for each camera stream
471  std::vector<vpFrameQueue> save_queues(grabbers.size());
472  std::vector<vpStorageWorker> storages;
473  std::vector<std::thread> storage_threads;
474 
475  std::string parent_directory = vpTime::getDateTime("%Y-%m-%d_%H.%M.%S");
476  for (size_t deviceId = 0; deviceId < grabbers.size(); deviceId++) {
477  // Start the capture thread for the current camera stream
478  capture_threads.emplace_back(capture, grabbers[deviceId], std::ref(share_images[deviceId]));
479  int win_x = deviceId * offsetX, win_y = deviceId * offsetY;
480 
481  // Start the display thread for the current camera stream
482  display_threads.emplace_back(display, grabbers[deviceId]->getWidth(), grabbers[deviceId]->getHeight(), win_x, win_y,
483  deviceId, std::ref(share_images[deviceId]), std::ref(save_queues[deviceId]),
484  saveVideo);
485 
486  if (saveVideo) {
487  std::stringstream ss;
488  ss << parent_directory << "/Camera_Stream" << deviceId;
489  std::cout << "Create directory: " << ss.str() << std::endl;
490  vpIoTools::makeDirectory(ss.str());
491  ss << "/%06d.png";
492  std::string filename = ss.str();
493 
494  storages.emplace_back(std::ref(save_queues[deviceId]), std::cref(filename), grabbers[deviceId]->getWidth(),
495  grabbers[deviceId]->getHeight());
496  }
497  }
498 
499  if (saveVideo) {
500  for (auto &s : storages) {
501  // Start the storage thread for the current camera stream
502  storage_threads.emplace_back(&vpStorageWorker::run, &s);
503  }
504  }
505 
506  // Join all the worker threads, waiting for them to finish
507  for (auto &ct : capture_threads) {
508  ct.join();
509  }
510 
511  for (auto &dt : display_threads) {
512  dt.join();
513  }
514 
515  // Clean first the grabbers to avoid camera problems when cancelling the
516  // storage threads in the terminal
517  for (auto &g : grabbers) {
518  delete g;
519  }
520 
521  if (saveVideo) {
522  std::cout << "\nWaiting for finishing thread to write images..." << std::endl;
523  }
524 
525  // We're done reading, cancel all the queues
526  for (auto &qu : save_queues) {
527  qu.cancel();
528  }
529 
530  // Join all the worker threads, waiting for them to finish
531  for (auto &st : storage_threads) {
532  st.join();
533  }
534 
535  return EXIT_SUCCESS;
536 }
537 #else
538 #if !(defined(VISP_HAVE_X11) || defined(VISP_HAVE_GTK))
539 int main()
540 {
541  std::cout << "You do not have X11, or GTK functionalities to display images..." << std::endl;
542  std::cout << "Tip if you are on a unix-like system:" << std::endl;
543  std::cout << "- Install X11, configure again ViSP using cmake and build again this example" << std::endl;
544  std::cout << "Tip if you are on a windows-like system:" << std::endl;
545  std::cout << "- Install GTK, configure again ViSP using cmake and build again this example" << std::endl;
546  return EXIT_SUCCESS;
547 }
548 #elif !defined(VISP_HAVE_V4L2)
549 int main()
550 {
551  std::cout << "You do not have Video 4 Linux 2 functionality enabled" << std::endl;
552  std::cout << "Tip if you are on a unix-like system:" << std::endl;
553  std::cout << "- Install libv4l2, configure again ViSP using cmake and build again this example" << std::endl;
554  return EXIT_SUCCESS;
555 }
556 #else
557 int main()
558 {
559  std::cout << "You do not build ViSP with c++11 or higher compiler flag" << std::endl;
560  std::cout << "Tip:" << std::endl;
561  std::cout << "- Configure ViSP again using cmake -DUSE_CXX_STANDARD=11, and build again this example" << std::endl;
562  return EXIT_SUCCESS;
563 }
564 #endif
565 #endif
static const vpColor red
Definition: vpColor.h:217
The vpDisplayGTK allows to display image using the GTK 3rd party library. Thus to enable this class G...
Definition: vpDisplayGTK.h:133
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void flush(const vpImage< unsigned char > &I)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
error that can be emitted by ViSP classes.
Definition: vpException.h:60
const char * what() const
Definition: vpException.cpp:71
static void merge(const vpImage< unsigned char > *R, const vpImage< unsigned char > *G, const vpImage< unsigned char > *B, const vpImage< unsigned char > *a, vpImage< vpRGBa > &RGBa)
static void split(const vpImage< vpRGBa > &src, vpImage< unsigned char > *pR, vpImage< unsigned char > *pG, vpImage< unsigned char > *pB, vpImage< unsigned char > *pa=nullptr)
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void gaussianBlur(const vpImage< ImageType > &I, vpImage< FilterType > &GI, unsigned int size=7, FilterType sigma=0., bool normalize=true, const vpImage< bool > *p_mask=nullptr)
unsigned int getSize() const
Definition: vpImage.h:221
Type * bitmap
points toward the bitmap
Definition: vpImage.h:135
static void makeDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:550
static bool parse(int *argcPtr, const char **argv, vpArgvInfo *argTable, int flags)
Definition: vpParseArgv.cpp:70
Class that is a wrapper over the Video4Linux2 (V4L2) driver.
void open(vpImage< unsigned char > &I)
void setScale(unsigned scale=vpV4l2Grabber::DEFAULT_SCALE)
void setDevice(const std::string &devname)
void acquire(vpImage< unsigned char > &I)
Class that enables to write easily a video file or a sequence of images.
void saveFrame(vpImage< vpRGBa > &I)
void setFileName(const std::string &filename)
void open(vpImage< vpRGBa > &I)
VISP_EXPORT std::string getDateTime(const std::string &format="%Y/%m/%d %H:%M:%S")
VISP_EXPORT double measureTimeMs()