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