1 | initial version |
Try using 2 threads. Here is a simple GrabberThread class. Below you can find an example
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
/** @brief Example Class that grabs from cv::VideoCapture into a queue.
It can be used as worker thread.
Even if you grab N frames and create N x Mat in the queue
only few blocks will be really allocated thanks to @c std::vector
and @c cv::Mat memory recycling. Look at @c matMemoryCounter to
know how many memory blocks has been allocated
@author PkLab answer to question:
@see http://answers.opencv.org/question/95193/capture-from-two-webcam/
@note It can be done much better but it's better than nothing :)
@note It requires C++11 or later
*/
class GrabberThread{
private:
int device;
std::mutex mtx;
std::queue<cv::Mat > buffer;
std::atomic<bool> grabOn;
VideoCapture cap;
size_t bufSizeMax;
std::map<unsigned char*, int> matMemoryCounter;
public:
GrabberThread() : grabOn(false), device(0), bufSizeMax(0){};
~GrabberThread()
{
grabOn.store(false);
cap.release();
while (!buffer.empty())
buffer.pop();
matMemoryCounter.clear();
}
/* @brief initialize members and open the capture device
@return true if the device has successfully opened
*/
bool Init(int dev = 0)
{
device = dev;
cap.open(device);
return cap.isOpened();
}
/* @brief Disable the grabbing loop control var.
This will stop the grabbing thread
*/
void StopGrabing()
{
grabOn.store(false);
}
/** @brief This is the thread that grabs into a queue
@note In real application locks and multiple copyTo can be avoided
using better queue/circular buffer!
*/
void GrabThread()
{
matMemoryCounter.clear();
uchar * frameMemoryAddr;
if (!cap.isOpened()) cap.open(device);
if (!cap.isOpened()) return;
cv::Mat tmp;
grabOn.store(true);
while (grabOn.load() == true) //this is lock free
{
//read() will wait for cam FPS.
//keep grab out of lock so that
//idle time can be used by other threads
if (!cap.read(tmp))
continue;
//get lock only when we have a frame
mtx.lock();
/** @warning Avoid @c buffer.push_back(tmp) because it stores items by reference.
We have to create a new cv::Mat for each frame
STL and cv::Mat will recycle memory
*/
buffer.push(cv::Mat(tmp.size(), tmp.type()));
tmp.copyTo(buffer.back());
//count how many times this memory block has been used
frameMemoryAddr = buffer.front().data;
matMemoryCounter[frameMemoryAddr]++;
bufSizeMax = max(bufSizeMax, buffer.size());
mtx.unlock();
/* SHOW THE FRAME if do you want
string winName = "Live Thread Dev: " + to_string(device);
int font = CV_FONT_HERSHEY_PLAIN;
cv::putText(tmp, winName, cv::Point(10, 10), font, 1, cv::Scalar(0, 255, 0));
imshow(winName, tmp);
cv::waitKey(1); //just for imshow
*/
} // while
} // func
/** @brief Get the oldest grabbed frame if available
The frame is copied from the buffer than it can used freely
@param [out]frame the the oldest grabbed frame. if not available it will be empty
@param [out]pBufSize a pointer where returns current buffer size. If 0 it will be ignored
@param [out]pBufSizeMax a pointer where returns maximum buffer size. If 0 it will be ignored
@return true if a frame is available
*/
bool PopFrame(Mat &frame, size_t *pBufSize = 0, size_t *pBufSizeMax = 0)
{
mtx.lock();
size_t bufSize = buffer.size();
bool res = bufSize > 0;
if (res) {
// get the oldest grabbed frame (queue=FIFO)
buffer.front().copyTo(frame);
// release the item
buffer.pop();
}
else frame = Mat();
if (pBufSize) *pBufSize = bufSize;
if (pBufSizeMax) *pBufSizeMax = bufSizeMax;
mtx.unlock();
return res;
}
// ----------------------------------------------
// GET SET METHODS
/** @see VideoCapture::set */
virtual bool capSet(int propId, double value) {
std::lock_guard<std::mutex> lock(mtx);
return cap.set(propId, value);
}
/** @see VideoCapture::get */
virtual double capGet(int propId) {
std::lock_guard<std::mutex> lock(mtx);
return cap.get(propId);
}
size_t GetBufSize() {
std::lock_guard<std::mutex> lock(mtx);
return buffer.size();
}
size_t GetBufSizeMax() {
std::lock_guard<std::mutex> lock(mtx);
return bufSizeMax;
}
size_t GetMemoryCounter() {
std::lock_guard<std::mutex> lock(mtx);
return matMemoryCounter.size();
}
void PrintStats() {
std::lock_guard<std::mutex> lock(mtx);
cout << "DEVICE: " << to_string(device) << endl;
cout << "\t MaxBufSize: " << bufSizeMax << endl;
cout << "\t Mat Memory Counter: " << matMemoryCounter.size() << endl;
}
}; //class Grabber
and here is an example
void ProcessFrameUtil(cv::Mat &frame1, cv::Mat &frame2)
{
if (!frame1.empty())
imshow("cap1", frame1);
if (!frame2.empty())
imshow("cap2", frame2);
}
int main(int argc *, char argv**) {
GrabberThread grab1, grab2;
cv::Mat frame1, frame2;
if (!grab1.Init(0 + CAP_DSHOW)) return -1;
if (!grab2.Init(1 + CAP_DSHOW)) return -1;
// start 2 grabbing thread from 2 cams
std::thread t1(&GrabberThread::GrabThread, &grab1);
std::thread t2(&GrabberThread::GrabThread, &grab2);
size_t bufSize1, bufSize2;
while (true) {
grab1.PopFrame(frame1, &bufSize1);
grab2.PopFrame(frame2, &bufSize2);
// do some processing
ProcessFrameUtil(frame1, frame2);
if (bufSize1 > 10)
cout << "WARNING buf1 too large(" << bufSize1 << ")" << endl;
if (bufSize2 > 10)
cout << "WARNING buf2 too large(" << bufSize2 << ")" << endl;
if (cv::waitKey(5) >= 0) // press any key to terminate
{
grab1.StopGrabing();
grab2.StopGrabing();
t1.join(); // wait thread exits
t2.join(); // wait thread exits
cout << "Flushing buffer1 of:" << grab1.GetBufSize() << " frames...";
cout << "Flushing buffer2 of:" << grab2.GetBufSize() << " frames...";
while (!(frame1.empty() && frame2.empty()))
{
grab1.PopFrame(frame1);
grab2.PopFrame(frame2);
// do some processing
ProcessFrameUtil(frame1, frame2);
}
break; // exit from process loop
}
}
std::cout << "done" << endl;
grab1.PrintStats();
grab2.PrintStats();
std::cout << std::endl << "Press Enter to terminate ";
std::cin.get()
return 0;
}
2 | No.2 Revision |
Try using 2 threads. Here is a simple GrabberThread class. Below you can find an example
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
/** @brief Example Class that grabs from cv::VideoCapture into a queue.
It can be used as worker thread.
Even if you grab N frames and create N x Mat in the queue
only few blocks will be really allocated thanks to @c std::vector
and @c cv::Mat memory recycling. Look at @c matMemoryCounter to
know how many memory blocks has been allocated
@author PkLab answer to question:
@see http://answers.opencv.org/question/95193/capture-from-two-webcam/
@note It can be done much better but it's better than nothing :)
@note It requires C++11 or later
*/
class GrabberThread{
private:
int device;
std::mutex mtx;
std::queue<cv::Mat > buffer;
std::atomic<bool> grabOn;
VideoCapture cap;
size_t bufSizeMax;
std::map<unsigned char*, int> matMemoryCounter;
public:
GrabberThread() : grabOn(false), device(0), bufSizeMax(0){};
~GrabberThread()
{
grabOn.store(false);
cap.release();
while (!buffer.empty())
buffer.pop();
matMemoryCounter.clear();
}
/* @brief initialize members and open the capture device
@return true if the device has successfully opened
*/
bool Init(int dev = 0)
{
device = dev;
cap.open(device);
return cap.isOpened();
}
/* @brief Disable the grabbing loop control var.
This will stop the grabbing thread
*/
void StopGrabing()
{
grabOn.store(false);
}
/** @brief This is the thread that grabs into a queue
@note In real application locks and multiple copyTo can be avoided
using better queue/circular buffer!
*/
void GrabThread()
{
matMemoryCounter.clear();
uchar * frameMemoryAddr;
if (!cap.isOpened()) cap.open(device);
if (!cap.isOpened()) return;
cv::Mat tmp;
grabOn.store(true);
while (grabOn.load() == true) //this is lock free
{
//read() will wait for cam FPS.
//keep grab out of lock so that
//idle time can be used by other threads
if (!cap.read(tmp))
continue;
//get lock only when we have a frame
mtx.lock();
/** @warning Avoid @c buffer.push_back(tmp) because it stores items by reference.
We have to create a new cv::Mat for each frame
STL and cv::Mat will recycle memory
*/
buffer.push(cv::Mat(tmp.size(), tmp.type()));
tmp.copyTo(buffer.back());
//count how many times this memory block has been used
frameMemoryAddr = buffer.front().data;
matMemoryCounter[frameMemoryAddr]++;
bufSizeMax = max(bufSizeMax, buffer.size());
mtx.unlock();
/* SHOW THE FRAME if do you want
string winName = "Live Thread Dev: " + to_string(device);
int font = CV_FONT_HERSHEY_PLAIN;
cv::putText(tmp, winName, cv::Point(10, 10), font, 1, cv::Scalar(0, 255, 0));
imshow(winName, tmp);
cv::waitKey(1); //just for imshow
*/
} // while
} // func
/** @brief Get the oldest grabbed frame if available
The frame is copied from the buffer than it can used freely
@param [out]frame the the oldest grabbed frame. if not available it will be empty
@param [out]pBufSize a pointer where returns current buffer size. If 0 it will be ignored
@param [out]pBufSizeMax a pointer where returns maximum buffer size. If 0 it will be ignored
@return true if a frame is available
*/
bool PopFrame(Mat &frame, size_t *pBufSize = 0, size_t *pBufSizeMax = 0)
{
mtx.lock();
size_t bufSize = buffer.size();
bool res = bufSize > 0;
if (res) {
// get the oldest grabbed frame (queue=FIFO)
buffer.front().copyTo(frame);
// release the item
buffer.pop();
}
else frame = Mat();
if (pBufSize) *pBufSize = bufSize;
if (pBufSizeMax) *pBufSizeMax = bufSizeMax;
mtx.unlock();
return res;
}
// ----------------------------------------------
// GET SET METHODS
/** @see VideoCapture::set */
virtual bool capSet(int propId, double value) {
std::lock_guard<std::mutex> lock(mtx);
return cap.set(propId, value);
}
/** @see VideoCapture::get */
virtual double capGet(int propId) {
std::lock_guard<std::mutex> lock(mtx);
return cap.get(propId);
}
size_t GetBufSize() {
std::lock_guard<std::mutex> lock(mtx);
return buffer.size();
}
size_t GetBufSizeMax() {
std::lock_guard<std::mutex> lock(mtx);
return bufSizeMax;
}
size_t GetMemoryCounter() {
std::lock_guard<std::mutex> lock(mtx);
return matMemoryCounter.size();
}
void PrintStats() {
std::lock_guard<std::mutex> lock(mtx);
cout << format( "DEVICE: " << to_string(device) %d ",device) << endl;
cout << "\t MaxBufSize: " << bufSizeMax << endl;
cout << "\t Mat Memory Counter: " << matMemoryCounter.size() << endl;
}
}; //class Grabber
and here is an example
void ProcessFrameUtil(cv::Mat &frame1, cv::Mat &frame2)
{
if (!frame1.empty())
imshow("cap1", frame1);
if (!frame2.empty())
imshow("cap2", frame2);
}
int main(int argc *, char argv**) {
GrabberThread grab1, grab2;
cv::Mat frame1, frame2;
if (!grab1.Init(0 + CAP_DSHOW)) return -1;
if (!grab2.Init(1 + CAP_DSHOW)) return -1;
// start 2 grabbing thread from 2 cams
std::thread t1(&GrabberThread::GrabThread, &grab1);
std::thread t2(&GrabberThread::GrabThread, &grab2);
size_t bufSize1, bufSize2;
while (true) {
grab1.PopFrame(frame1, &bufSize1);
grab2.PopFrame(frame2, &bufSize2);
// do some processing
ProcessFrameUtil(frame1, frame2);
if (bufSize1 > 10)
cout << "WARNING buf1 too large(" << bufSize1 << ")" << endl;
if (bufSize2 > 10)
cout << "WARNING buf2 too large(" << bufSize2 << ")" << endl;
if (cv::waitKey(5) >= 0) // press any key to terminate
{
grab1.StopGrabing();
grab2.StopGrabing();
t1.join(); // wait thread exits
t2.join(); // wait thread exits
cout << "Flushing buffer1 of:" << grab1.GetBufSize() << " frames...";
cout << "Flushing buffer2 of:" << grab2.GetBufSize() << " frames...";
while (!(frame1.empty() && frame2.empty()))
{
grab1.PopFrame(frame1);
grab2.PopFrame(frame2);
// do some processing
ProcessFrameUtil(frame1, frame2);
}
break; // exit from process loop
}
}
std::cout << "done" << endl;
grab1.PrintStats();
grab2.PrintStats();
std::cout << std::endl << "Press Enter to terminate ";
std::cin.get()
std::cin.get();
return 0;
}
3 | No.3 Revision |
Try using 2 threads. Here is a simple GrabberThread class. Below you can find an example
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
#include <string>
/** @brief Example Class that grabs from cv::VideoCapture into a queue.
It can be used as worker thread.
Even if you grab N frames and create N x Mat in the queue
only few blocks will be really allocated thanks to @c std::vector
and @c cv::Mat memory recycling. Look at @c matMemoryCounter to
know how many memory blocks has been allocated
@author PkLab answer to question:
@see http://answers.opencv.org/question/95193/capture-from-two-webcam/
@note It can be done much better but it's better than nothing :)
@note It requires C++11 or later
*/
class GrabberThread{
private:
int device;
std::mutex mtx;
std::queue<cv::Mat > buffer;
std::atomic<bool> grabOn;
VideoCapture cap;
size_t bufSizeMax;
std::map<unsigned char*, int> matMemoryCounter;
public:
GrabberThread() : grabOn(false), device(0), bufSizeMax(0){};
~GrabberThread()
{
grabOn.store(false);
cap.release();
while (!buffer.empty())
buffer.pop();
matMemoryCounter.clear();
}
/* @brief initialize members and open the capture device
@return true if the device has successfully opened
*/
bool Init(int dev = 0)
{
device = dev;
cap.open(device);
return cap.isOpened();
}
/* @brief Disable the grabbing loop control var.
This will stop the grabbing thread
*/
void StopGrabing()
{
grabOn.store(false);
}
/** @brief This is the thread that grabs into a queue
@note In real application locks and multiple copyTo can be avoided
using better queue/circular buffer!
*/
void GrabThread()
{
matMemoryCounter.clear();
uchar * frameMemoryAddr;
if (!cap.isOpened()) cap.open(device);
if (!cap.isOpened()) return;
cv::Mat tmp;
grabOn.store(true);
while (grabOn.load() == true) //this is lock free
{
//read() will wait for cam FPS.
//keep grab out of lock so that
//idle time can be used by other threads
if (!cap.read(tmp))
continue;
//get lock only when we have a frame
mtx.lock();
/** @warning Avoid @c buffer.push_back(tmp) because it stores items by reference.
We have to create a new cv::Mat for each frame
STL and cv::Mat will recycle memory
*/
buffer.push(cv::Mat(tmp.size(), tmp.type()));
tmp.copyTo(buffer.back());
//count how many times this memory block has been used
frameMemoryAddr = buffer.front().data;
matMemoryCounter[frameMemoryAddr]++;
bufSizeMax = max(bufSizeMax, buffer.size());
mtx.unlock();
/* SHOW THE FRAME if do you want
string winName = "Live Thread Dev: " + to_string(device);
std::to_string(device);
int font = CV_FONT_HERSHEY_PLAIN;
cv::putText(tmp, winName, cv::Point(10, 10), font, 1, cv::Scalar(0, 255, 0));
imshow(winName, tmp);
cv::waitKey(1); //just for imshow
*/
} // while
} // func
/** @brief Get the oldest grabbed frame if available
The frame is copied from the buffer than it can used freely
@param [out]frame the the oldest grabbed frame. if not available it will be empty
@param [out]pBufSize a pointer where returns current buffer size. If 0 it will be ignored
@param [out]pBufSizeMax a pointer where returns maximum buffer size. If 0 it will be ignored
@return true if a frame is available
*/
bool PopFrame(Mat &frame, size_t *pBufSize = 0, size_t *pBufSizeMax = 0)
{
mtx.lock();
size_t bufSize = buffer.size();
bool res = bufSize > 0;
if (res) {
// get the oldest grabbed frame (queue=FIFO)
buffer.front().copyTo(frame);
// release the item
buffer.pop();
}
else frame = Mat();
if (pBufSize) *pBufSize = bufSize;
if (pBufSizeMax) *pBufSizeMax = bufSizeMax;
mtx.unlock();
return res;
}
// ----------------------------------------------
// GET SET METHODS
/** @see VideoCapture::set */
virtual bool capSet(int propId, double value) {
std::lock_guard<std::mutex> lock(mtx);
return cap.set(propId, value);
}
/** @see VideoCapture::get */
virtual double capGet(int propId) {
std::lock_guard<std::mutex> lock(mtx);
return cap.get(propId);
}
size_t GetBufSize() {
std::lock_guard<std::mutex> lock(mtx);
return buffer.size();
}
size_t GetBufSizeMax() {
std::lock_guard<std::mutex> lock(mtx);
return bufSizeMax;
}
size_t GetMemoryCounter() {
std::lock_guard<std::mutex> lock(mtx);
return matMemoryCounter.size();
}
void PrintStats() {
std::lock_guard<std::mutex> lock(mtx);
cout << format( "DEVICE: %d ",device) " << std::to_string(device) << endl;
cout << "\t MaxBufSize: " << bufSizeMax << endl;
cout << "\t Mat Memory Counter: " << matMemoryCounter.size() << endl;
}
}; //class Grabber
and here is an example
void ProcessFrameUtil(cv::Mat &frame1, cv::Mat &frame2)
{
if (!frame1.empty())
imshow("cap1", frame1);
if (!frame2.empty())
imshow("cap2", frame2);
}
int main(int argc *, char argv**) {
GrabberThread grab1, grab2;
cv::Mat frame1, frame2;
if (!grab1.Init(0 + CAP_DSHOW)) return -1;
if (!grab2.Init(1 + CAP_DSHOW)) return -1;
// start 2 grabbing thread from 2 cams
std::thread t1(&GrabberThread::GrabThread, &grab1);
std::thread t2(&GrabberThread::GrabThread, &grab2);
size_t bufSize1, bufSize2;
while (true) {
grab1.PopFrame(frame1, &bufSize1);
grab2.PopFrame(frame2, &bufSize2);
// do some processing
ProcessFrameUtil(frame1, frame2);
if (bufSize1 > 10)
cout << "WARNING buf1 too large(" << bufSize1 << ")" << endl;
if (bufSize2 > 10)
cout << "WARNING buf2 too large(" << bufSize2 << ")" << endl;
if (cv::waitKey(5) >= 0) // press any key to terminate
{
grab1.StopGrabing();
grab2.StopGrabing();
t1.join(); // wait thread exits
t2.join(); // wait thread exits
cout << "Flushing buffer1 of:" << grab1.GetBufSize() << " frames...";
cout << "Flushing buffer2 of:" << grab2.GetBufSize() << " frames...";
while (!(frame1.empty() && frame2.empty()))
{
grab1.PopFrame(frame1);
grab2.PopFrame(frame2);
// do some processing
ProcessFrameUtil(frame1, frame2);
}
break; // exit from process loop
}
}
std::cout << "done" << endl;
grab1.PrintStats();
grab2.PrintStats();
std::cout << std::endl << "Press Enter to terminate ";
std::cin.get();
return 0;
}