简体   繁体   English

QT + OpenCV的多线程

[英]Multi-threading with QT + OpenCV

I'm trying to code a simple program that reads three video files (actually, 3 cameras that are in the same room), using 3 different threads. 我正在尝试编写一个简单的程序,该程序使用3个不同的线程读取三个视频文件(实际上是同一房间中的3个摄像机)。 The code I'm using is the following: 我正在使用的代码如下:

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

Just reading the frames from VideoCapture and displaying them into the UI by another different class that uses QWidgets works well. 只需从VideoCapture中读取帧并将其通过另一个使用QWidgets的其他类显示在UI中即可。
However, when I include the BackgroundSubstraction method, the UI does not display the same frame number for the three cameras, maybe Camera1 is computing frame 100 and Camera2 and Camera3 are in frame 110. 但是,当我包括BackgroundSubstraction方法时,UI不会为三个摄像机显示相同的帧号,可能是Camera1在计算帧100,而Camera2和Camera3在帧110。
This is because some frames are calculated faster than other and this leads to syntonization problems. 这是因为某些帧的计算速度比其他帧快,这会导致同步化问题。
I'm quite new using threads in QT so i would like to make some synconization between threads so I know when the three different frames have been process in order to call the displayFrame method, and so, that the three same frames are displayed at the exact same time. 我在QT中使用线程是非常新的,所以我想在线程之间进行一些同步,因此我知道何时处理了三个不同的帧以调用displayFrame方法,因此,三个相同的帧显示在完全相同的时间。

EDIT: 编辑:
I assume that the easiest way to do this is using Barriers. 我认为最简单的方法是使用屏障。
http://www.boost.org/doc/libs/1_55_0/doc/html/thread/synchronization.html#thread.synchronization.barriers . http://www.boost.org/doc/libs/1_55_0/doc/html/thread/synchronization.html#thread.synchronization.barriers But I have no clue how to do this. 但是我不知道该怎么做。

EDIT 2: I have implemented this Syncronizacion using barriers and now the code looks like this: 编辑2:我已经使用障碍实现了这个Syncronizacion ,现在代码看起来像这样:

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

updated worker.cpp 更新的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);
}

It seems to work perfectly, however the output of the program is the following: 它似乎工作正常,但是程序的输出如下:

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

At the beginning the syncronization is perfectly working, but then it seems that the barrier is not working and threads are not waiting to each other... 在开始时,同步是完美的,但是似乎障碍不起作用,线程没有彼此等待...

EDIT 3: SOLVED It seems that changing the value of 编辑3:解决似乎改变了

QThread::msleep(5);

to

QThread::msleep(35);

solves the problem of the syncronization although I do not really understand the reason. 解决了同步化的问题,尽管我不太了解其原因。

even without the background subtraction you'd need some synchronization to be sure that the same frame number is processed by each thread. 即使没有背景减法,您也需要进行一些同步,以确保每个线程都处理相同的帧号。

In Qt the easiest (and imho the right) way to do it is to remove the infinite loop and instead call a slot of each thread to compute the next image, after all the threads emitted their signal frameFinished. 在Qt中,最简单(也是正确的方法)的方法是在所有线程发出信号frameFinished之后,删除无限循环,而是调用每个线程的插槽以计算下一个图像。

You could further use some buffering to precompute images in your threads and just load them from that buffer. 您可以进一步使用一些缓冲区来预先计算线程中的图像,然后仅从该缓冲区中加载它们。 In that scenario you could do the following: 在这种情况下,您可以执行以下操作:

  1. each of your threads fills his buffer in an endless loop as long as there is free buffer space available. 只要有可用的缓冲区空间,您的每个线程都会无休止地填充缓冲区。 If the buffer is full, the thread waits until buffer space got freed. 如果缓冲区已满,线程将等待直到缓冲区空间释放为止。

  2. when your gui has displayed and has waited some time, it sends a signal that is connected to each thread's slot like sendMeANewImage. 当gui显示并等待了一段时间后,它会发送一个连接到每个线程的插槽的信号,例如sendMeANewImage。

  3. each thread sends the next available image from its buffer, or waits (infinite loop or conditional wait) for an image, if the buffer is empty. 每个线程从其缓冲区发送下一个可用图像,或者如果缓冲区为空,则等待(无限循环或条件等待)一个图像。 Then emits a frameFinished signal and frees the used buffer-space. 然后发出frameFinished信号并释放使用的缓冲区空间。

  4. when each thread has emitted the signal, display all the images, wait some time and emit sendMeANewImage again. 每个线程发出信号后,显示所有图像,等待一段时间,然后再次发出sendMeANewImage。

This isn't threadsafe yet, you'll have the critical sections in reading and writing from the buffer. 这还不是线程安全的,您将具有从缓冲区读取和写入的关键部分。 For each buffer, create a QMutex and call mutex.lock() whenever reading or writing or asking size etc. from that buffer. 对于每个缓冲区,无论何时从该缓冲区读取或写入或询问大小等,都创建一个QMutex并调用mutex.lock()。 Call mutex.unlock() immediately afterwards. 之后立即调用mutex.unlock()。

When a mutex is locked and another thread (or even the same thread) tries to lock it again, the thread will wait there, until the other thread has unlocked the mutex. 当互斥锁被锁定并且另一个线程(甚至同一线程)试图再次锁定它时,该线程将在那里等待,直到另一个线程已解锁互斥锁为止。 That way, only a single thread can enter a critical section. 这样,只有一个线程可以进入关键部分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM