Ask Your Question

Revision history [back]

click to hide/show revision 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;
}

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;
}

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;
}