使用QT + OpenCV进行多线程处理

时间:2017-04-23 12:13:02

标签: c++ multithreading qt opencv

我尝试编写一个简单的程序,使用3个不同的线程读取三个视频文件(实际上是同一房间内的3个摄像机)。我使用的代码如下:

mainwindow.cpp

void MainWindow::init()
{
    numCams = 3;

    // Resize the video for displaying to the size of the widget
    int WidgetHeight = ui->CVWidget1->height();
    int WidgetWidth  = ui->CVWidget1->width();

    for (int i = 0; i < numCams; i++){
        // Create threads
        threads[i] = new QThread;

        // Create workers
        string Path = "/Users/alex/Desktop/PruebasHilos/Videos/" + to_string(i+1) + ".m2v";
        workers[i] = new Worker(QString::fromStdString(Path), i, WidgetHeight, WidgetWidth);

        workers[i]->moveToThread(threads[i]);

        connectSignals2Slots(threads[i], workers[i]);

        threads[i]->start();
        qDebug() << "Thread from camera " << (i+1) << " started";
    }
}

void MainWindow::connectSignals2Slots(QThread *thread, Worker *worker)
{
    connect(thread, SIGNAL(started()), worker, SLOT(readVideo()));
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    connect(worker, SIGNAL(frameFinished(Mat, int)), this, SLOT(displayFrame(Mat,int)));
    connect(worker, SIGNAL(finished(int)), thread, SLOT(quit()));
    connect(worker, SIGNAL(finished(int)), worker, SLOT(deleteLater()));
}

void MainWindow::displayFrame(Mat frame, int index)
{
    if (index == 0) {
        // Camera 1
        ui->CVWidget1->showImage(frame);
    }
    else if (index == 1) {
        // Camera 2
        ui->CVWidget2->showImage(frame);
    }
    else if (index == 2) {
        // Camera 3
        ui->CVWidget3->showImage(frame);
    }
}

worker.cpp

Worker::Worker(QString path, int id, int WidgetHeight, int WidgetWidth) : filepath(path), index(id), WidgetHeight(WidgetHeight), WidgetWidth(WidgetWidth) {
}

Worker::~Worker(){
}

void Worker::readVideo()
{
    VideoCapture cap(filepath.toStdString());

    if (! cap.isOpened()) {
        qDebug() << "Can't open video file " << filepath;
        emit finished(index);
        return;
    }

    Mat ActualFrame;
    while (true) {
        cap >> ActualFrame;

        if (ActualFrame.empty()) {
            // Empty frame to display when the video has finished
            ActualFrame = Mat(Size(720, 576), CV_8UC3, Scalar(192, 0, 0));
            emit frameFinished(ActualFrame, index);

            qDebug() << "Video finished";
            break;
        }

        // Background Subtraction
        BackgroundSubtraction(ActualFrame, BackgroundMask);

        emit frameFinished(ActualFrame.clone(), index);
        QThread::msleep(35);
    }
    emit finished(index);
}

void Worker::BackgroundSubtraction(Mat ActualFrame, Mat &BackgroundMask)
{
    pMOG2->apply(ActualFrame, BackgroundMask);
}

只需从VideoCapture中读取帧并通过另一个使用QWidgets的不同类将它们显示在UI中效果很好。
但是,当我包含BackgroundSubstraction方法时,UI不会为三个摄像头显示相同的帧编号,可能Camera1正在计算帧100,而Camera2和Camera3位于帧110中。 这是因为某些帧的计算速度比其他帧快,这会导致合成问题 我在QT中使用线程很新,所以我想在线程之间进行一些同步,所以我知道三个不同的帧何时处理以调用displayFrame方法,因此,显示三个相同的帧在同一时间。

修改
我认为最简单的方法是使用障碍 http://www.boost.org/doc/libs/1_55_0/doc/html/thread/synchronization.html#thread.synchronization.barriers。但我不知道如何做到这一点。

编辑2: 我已经实现了这个Syncronizacion using barriers,现在代码看起来像这样:

barrier.h

#ifndef BARRIER_H
#define BARRIER_H

#include <QMutex>
#include <QWaitCondition>
#include <QSharedPointer>

// Data "pimpl" class (not to be used directly)
class BarrierData
{
public:
    BarrierData(int count) : count(count) {}

    void wait() {
        mutex.lock();
        --count;
        if (count > 0)
            condition.wait(&mutex);
        else
            condition.wakeAll();
        mutex.unlock();
    }
private:
    Q_DISABLE_COPY(BarrierData)
    int count;
    QMutex mutex;
    QWaitCondition condition;
};

class Barrier {
public:
    // Create a barrier that will wait for count threads
    Barrier(int count) : d(new BarrierData(count)) {}
    void wait() {
        d->wait();
    }

private:
    QSharedPointer<BarrierData> d;
};

#endif // BARRIER_H

更新了worker.cpp

void Worker::readVideo()
{
    VideoCapture cap(filepath.toStdString());

    int framenumber = 0;
    if (! cap.isOpened()) {
        qDebug() << "Can't open video file " << filepath;
        emit finished(index);
        return;
    }

    Mat ActualFrame;
    while (true) {
        cap >> ActualFrame;

        if (ActualFrame.empty()) {
            // Empty frame to display when the video has finished
            ActualFrame = Mat(Size(720, 576), CV_8UC3, Scalar(192, 0, 0));
            emit frameFinished(ActualFrame, index);

            qDebug() << "Video finished";
            break;
        }

        // Background Subtraction
        BackgroundSubtraction(ActualFrame, BackgroundMask);

        QThread::msleep(5);
        barrier.wait();
        qDebug() << "Thread " << index << " processing frame " << framenumber ;
        emit frameFinished(ActualFrame.clone(), index);
        framenumber++;
    }
    emit finished(index);
}

void Worker::BackgroundSubtraction(Mat ActualFrame, Mat &BackgroundMask)
{
    pMOG2->apply(ActualFrame, BackgroundMask);
}

它似乎完美无缺,但程序的输出如下:

Thread  1  processing frame  0
Thread  0  processing frame  0
Thread  2  processing frame  0
Thread  2  processing frame  1
Thread  1  processing frame  1
Thread  0  processing frame  1
Thread  2  processing frame  2
Thread  1  processing frame  2
Thread  0  processing frame  2
Thread  2  processing frame  3
Thread  1  processing frame  3
Thread  0  processing frame  3
Thread  2  processing frame  4
Thread  1  processing frame  4
Thread  0  processing frame  4
Thread  2  processing frame  5
Thread  0  processing frame  5
Thread  1  processing frame  5
Thread  2  processing frame  6
Thread  1  processing frame  6
Thread  2  processing frame  7
Thread  0  processing frame  6
Thread  1  processing frame  7
Thread  2  processing frame  8
Thread  0  processing frame  7
Thread  1  processing frame  8
Thread  2  processing frame  9
Thread  0  processing frame  8
Thread  1  processing frame  9
Thread  1  processing frame  10
Thread  2  processing frame  10
Thread  0  processing frame  9
Thread  1  processing frame  11
Thread  2  processing frame  11
Thread  0  processing frame  10
Thread  1  processing frame  12

一开始同步化完全正常,但似乎屏障不起作用,线程不等待彼此...

编辑3:已解决 似乎改变了

的价值
QThread::msleep(5);

QThread::msleep(35);

解决了同步问题,虽然我不太明白原因。

1 个答案:

答案 0 :(得分:1)

即使没有背景减法,您也需要进行一些同步以确保每个线程处理相同的帧编号。

在Qt中,最简单的(也是正确的)方法是删除无限循环,然后调用每个线程的一个槽来计算下一个图像,在所有线程发出它们的信号frameFinished之后。

您可以进一步使用一些缓冲来预先计算线程中的图像,并从该缓冲区加载它们。在这种情况下,您可以执行以下操作:

  1. 只要有可用的空闲缓冲区空间,每个线程就会在无限循环中填充缓冲区。如果缓冲区已满,则线程将等待,直到缓冲区空间被释放。

  2. 当gui显示并等待一段时间后,它会发送一个信号,该信号连接到每个线程的插槽,如sendMeANewImage。

  3. 每个线程从其缓冲区发送下一个可用图像,或者如果缓冲区为空则等待(无限循环或条件等待)图像。然后发出frameFinished信号并释放使用过的缓冲区空间。

  4. 当每个线程发出信号时,显示所有图像,等待一段时间再发出sendMeANewImage。

  5. 这还不是线程安全的,你将从缓冲区读取和写入关键部分。对于每个缓冲区,创建一个QMutex并在从该缓冲区读取或写入或询问大小等时调用mutex.lock()。之后立即调用mutex.unlock()。

    当互斥锁被锁定而另一个线程(甚至同一个线程)试图再次锁定它时,线程将在那里等待,直到另一个线程解锁了互斥锁。这样,只有一个线程可以进入关键部分。