Example of using V4l2 backend to capture multiple camera streams with C++11 threads.
#include <visp3/core/vpConfig.h>
#if defined(VISP_HAVE_CPP11_COMPATIBILITY) && defined(VISP_HAVE_V4L2) && ( defined(VISP_HAVE_X11) || defined(VISP_HAVE_GTK) )
#include <iostream>
#include <limits>
#include <queue>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <visp3/sensor/vpV4l2Grabber.h>
#include <visp3/core/vpIoTools.h>
#include <visp3/core/vpImageFilter.h>
#include <visp3/core/vpDisplay.h>
#include <visp3/core/vpTime.h>
#include <visp3/gui/vpDisplayX.h>
#include <visp3/gui/vpDisplayGTK.h>
#include <visp3/io/vpParseArgv.h>
#include <visp3/io/vpVideoWriter.h>
#define GETOPTARGS "d:oh"
namespace {
void usage(const char *name, const char *badparam)
{
fprintf(stdout, "\n\
SYNOPSIS:\n\
%s [-d <device count>] [-o] [-h]\n\
\n\
DESCRIPTION:\n\
Capture multiple camera streams and save the stream without slowing down the acquisition.\n\
\n\
OPTIONS: \n\
-d <device count> \n\
Open the specified number of camera streams.\n\
\n\
-o \n\
Save each stream in a dedicated folder.\n\
\n\
-h \n\
Print the help.\n\n",
name);
if (badparam)
fprintf(stdout, "\nERROR: Bad parameter [%s]\n", badparam);
}
bool getOptions(int argc, char **argv,
unsigned int &deviceCount,
bool &saveVideo)
{
const char *optarg;
const char **argv1=(const char**)argv;
int c;
switch (c) {
case 'd': deviceCount = (unsigned int) atoi(optarg); break;
case 'o': saveVideo = true; break;
case 'h': usage(argv[0], NULL); return false; break;
default:
usage(argv[0], optarg);
return false; break;
}
}
if ((c == 1) || (c == -1)) {
usage(argv[0], NULL);
std::cerr << "ERROR: " << std::endl;
std::cerr << " Bad argument " << optarg << std::endl << std::endl;
return false;
}
return true;
}
class FrameQueue {
public:
struct cancelled {
};
FrameQueue() : m_cancelled(false), m_cond(), m_queueColor(), m_maxQueueSize(std::numeric_limits<size_t>::max()), m_mutex() {
}
void cancel() {
std::lock_guard<std::mutex> lock(m_mutex);
m_cancelled = true;
m_cond.notify_all();
}
std::lock_guard<std::mutex> lock(m_mutex);
m_queueColor.push(image);
while(m_queueColor.size() > m_maxQueueSize) {
m_queueColor.pop();
}
m_cond.notify_one();
}
std::unique_lock<std::mutex> lock(m_mutex);
while (m_queueColor.empty()) {
if (m_cancelled) {
throw cancelled();
}
m_cond.wait(lock);
if (m_cancelled) {
throw cancelled();
}
}
m_queueColor.pop();
return image;
}
void setMaxQueueSize(const size_t max_queue_size) {
m_maxQueueSize = max_queue_size;
}
private:
bool m_cancelled;
std::condition_variable m_cond;
std::queue<vpImage<vpRGBa> > m_queueColor;
size_t m_maxQueueSize;
std::mutex m_mutex;
};
class StorageWorker {
public:
StorageWorker(FrameQueue &queue, const std::string &filename,
const unsigned int width, const unsigned int height) :
m_queue(queue), m_filename(filename), m_width(width), m_height(height) {
}
void run() {
if(!m_filename.empty()) {
}
try {
for (;;) {
if(!m_filename.empty()) {
}
}
} catch (FrameQueue::cancelled &) {
}
}
private:
FrameQueue &m_queue;
std::string m_filename;
unsigned int m_width;
unsigned int m_height;
};
class ShareImage {
private:
bool m_cancelled;
std::condition_variable m_cond;
std::mutex m_mutex;
unsigned char *m_pImgData;
unsigned int m_totalSize;
public:
struct cancelled {
};
ShareImage() : m_cancelled(false), m_cond(), m_mutex(), m_pImgData(NULL), m_totalSize(0) {
}
virtual ~ShareImage() {
if(m_pImgData != NULL) {
delete []m_pImgData;
}
}
void cancel() {
std::lock_guard<std::mutex> lock(m_mutex);
m_cancelled = true;
m_cond.notify_all();
}
void getImage(unsigned char * const imageData, const unsigned int totalSize) {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_cancelled) {
throw cancelled();
}
m_cond.wait(lock);
if (m_cancelled) {
throw cancelled();
}
if(totalSize <= m_totalSize) {
memcpy(imageData, m_pImgData, totalSize*sizeof(unsigned char));
} else {
std::cerr << "totalSize <= m_totalSize !" << std::endl;
}
}
bool isCancelled() {
std::lock_guard<std::mutex> lock(m_mutex);
return m_cancelled;
}
void setImage(const unsigned char * const imageData, const unsigned int totalSize) {
std::lock_guard<std::mutex> lock(m_mutex);
if(m_pImgData == NULL || m_totalSize != totalSize) {
m_totalSize = totalSize;
if(m_pImgData != NULL) {
delete []m_pImgData;
}
m_pImgData = new unsigned char[m_totalSize];
}
memcpy(m_pImgData, imageData, m_totalSize*sizeof(unsigned char));
m_cond.notify_one();
}
};
void capture(
vpV4l2Grabber *
const pGrabber, ShareImage &share_image) {
pGrabber->
open(local_img);
while(true) {
if(share_image.isCancelled()) {
break;
}
share_image.setImage((
unsigned char *) local_img.
bitmap, local_img.
getSize()*4);
}
}
void display(const unsigned int width, const unsigned int height, const int win_x, const int win_y,
const unsigned int deviceId, ShareImage &share_image, FrameQueue &queue, const bool save) {
#if defined VISP_HAVE_X11
#elif defined VISP_HAVE_GTK
#endif
std::stringstream ss;
ss << "Camera stream " << deviceId;
display.
init(local_img, win_x, win_y, ss.str());
try {
vpImage<unsigned char> I_red(height, width), I_green(height, width), I_blue(height, width), I_alpha(height, width);;
vpImage<unsigned char> I_red_gaussian(height, width), I_green_gaussian(height, width), I_blue_gaussian(height, width);
vpImage<double> I_red_gaussian_double, I_green_gaussian_double, I_blue_gaussian_double;
bool exit = false, gaussian_blur = false;
while(!exit) {
share_image.getImage((
unsigned char *) local_img.
bitmap, local_img.
getSize()*4);
if(gaussian_blur) {
}
std::stringstream ss;
ss << "Time: " << t << " ms";
if(save) {
queue.push(local_img);
}
switch(button) {
gaussian_blur = !gaussian_blur;
break;
default:
exit = true;
break;
}
}
}
} catch(ShareImage::cancelled &) {
std::cout << "Cancelled!" << std::endl;
}
share_image.cancel();
}
}
int main(int argc, char *argv[])
{
unsigned int deviceCount = 1;
unsigned int cameraScale = 1;
bool saveVideo = false;
if (!getOptions(argc, argv, deviceCount, saveVideo)) {
return (-1);
}
std::vector<vpV4l2Grabber *> grabbers;
const unsigned int offsetX = 100, offsetY = 100;
for(unsigned int devicedId = 0; devicedId < deviceCount; devicedId++) {
try {
std::stringstream ss;
ss << "/dev/video" << devicedId;
grabbers.push_back(pGrabber);
std::cerr <<
"Exception: " << e.
what() << std::endl;
}
}
std::cout << "Grabbers: " << grabbers.size() << std::endl;
std::vector<ShareImage> share_images(grabbers.size());
std::vector<std::thread> capture_threads;
std::vector<std::thread> display_threads;
std::vector<FrameQueue> save_queues(grabbers.size());
std::vector<StorageWorker> storages;
std::vector<std::thread> storage_threads;
if(saveVideo) {
std::cout << "Create parent_directory: " << parent_directory << std::endl;
}
for(size_t deviceId = 0; deviceId < grabbers.size(); deviceId++) {
capture_threads.emplace_back( capture, grabbers[deviceId], std::ref(share_images[deviceId]) );
int win_x = deviceId * offsetX, win_y = deviceId * offsetY;
display_threads.emplace_back( display, grabbers[deviceId]->getWidth(), grabbers[deviceId]->getHeight(),
win_x, win_y, deviceId, std::ref(share_images[deviceId]),
std::ref(save_queues[deviceId]), saveVideo );
if(saveVideo) {
std::stringstream ss;
ss << parent_directory << "/Camera_Stream" << deviceId;
std::cout << "Create directory: " << ss.str() << std::endl;
ss << "/%06d.png";
std::string filename = ss.str();
storages.emplace_back( std::ref(save_queues[deviceId]), std::cref(filename),
grabbers[deviceId]->getWidth(), grabbers[deviceId]->getHeight() );
}
}
if(saveVideo) {
for(auto& s : storages) {
storage_threads.emplace_back(&StorageWorker::run, &s);
}
}
for(auto& ct : capture_threads) {
ct.join();
}
for (auto& dt : display_threads) {
dt.join();
}
for(auto& g : grabbers) {
delete g;
}
if(saveVideo) {
std::cout << "\nWaiting for finishing thread to write images..." << std::endl;
}
for (auto& qu : save_queues) {
qu.cancel();
}
for (auto& st : storage_threads) {
st.join();
}
return 0;
}
#else
#include <iostream>
int main()
{
std::cout << "Warning: This example need to be build with cxx11 compiler flags, v4l2 and x11 or gtk 3rd partiess. " << std::endl;
return 0;
}
#endif