2017-04-23 87 views
0

我試圖編寫一個簡單的程序,使用3個不同的線程讀取三個視頻文件(實際上,3個攝像機在同一個房間)。我正在使用的代碼如下:多線程與QT + OpenCV

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的不同類使用得很好。但是,當我包含BackgroundSubstraction方法時,UI不顯示三個攝像頭的相同幀號,也許Camera1計算幀100,Camera2和Camera3位於幀110中。
這是因爲某些幀計算速度更快比其他,這導致了諧振問題。
我很新使用QT中的線程,所以我想在線程之間做一些synconization,所以我知道何時三個不同的幀已經被處理以調用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); 

解決了同步問題,雖然我不太明白原因。

+0

即使沒有背景減法,你也需要一些同步來確保每個線程處理相同的幀號。在Qt中,最簡單的方法是去除無限循環,然後調用每個線程的槽來計算下一個圖像,在所有線程發出信號frameFinished之後。您可以進一步使用一些緩衝來預先計算線程中的映像,並從緩衝區中加載它們。 – Micka

回答

1

即使沒有背景減法,您也需要進行一些同步以確保每個線程處理相同的幀號。

在Qt中,最簡單的方法是去除無限循環,然後調用每個線程的槽來計算下一個圖像,在所有線程發出信號frameFinished之後。

您可以進一步使用一些緩衝來預先計算線程中的圖像,並從緩衝區中加載它們。在這種情況下,您可以執行以下操作:只要有可用的空閒緩衝區空間

你的線程的
  1. 每個罷了,他在一個無限循環緩衝區。如果緩衝區已滿,則線程將等待緩衝區空間釋放。

  2. 當你的gui顯示並等待一段時間後,它發送一個信號連接到每個線程的插槽,如sendMeANewImage。

  3. 每個線程從其緩衝區發送下一個可用圖像,或者如果緩衝區爲空,則等待(無限循環或條件等待)圖像。然後發出一個frameFinished信號並釋放使用的緩衝區空間。

  4. 當每個線程發出信號時,顯示所有圖像,等待一段時間再發出sendMeANewImage。

這還不是線程安全的,你會在緩衝區讀取和寫入關鍵部分。對於每個緩衝區,創建一個QMutex並調用mutex.lock(),無論何時讀或寫或從該緩衝區詢問大小等。之後立即調用mutex.unlock()。

當一個互斥鎖被鎖定並且另一個線程(甚至是同一個線程)試圖再次鎖定它時,線程將在那裏等待,直到另一個線程已經解鎖互斥鎖。這樣,只有一個線程可以進入關鍵部分。

+0

我發現這個解決方案[鏈接](http://stackoverflow.com/questions/9637374/qt-synchronization-barrier/9639624#9639624),它使用Barrier在線程應該等待的代碼中創建一個點。這意味着當最後一個線程到達屏障時,所有線程都將繼續。我已經實現了,所以等待的呼叫在發出frameFinished()之前。你認爲是正確的嗎? – Alex

+0

只需嘗試一下。可能會工作。沒讀過,但聽起來和我的方法很相似。 – Micka