【问题标题】:Qt + OpenCV play videos with std::threadQt + OpenCV 使用 std::thread 播放视频
【发布时间】:2015-03-30 04:07:51
【问题描述】:

这是我的图形用户界面,

我想要在这里做的是一起显示四个不同的视频。用户输入特定视频文件的路径并点击打开,应该会创建一个新线程来播放该视频。

我有一个名为 startThread() 的插槽,以及一个要在名为 openCamera() 的线程中调用的函数;

在mainwindow.hpp中有

public:
    static void openCamera(const std::string& address, QLabel* label);
private slots:
    void startThread();

在mainwindow.cpp中有

void MainWindow::startThread() {
    std::string address;
    QLabel* label;
    // Get the input from GUI and assign it to std::string address
    // and find the label corresponding to clicked button.
    // ...
    std::thread t(openCamera, address, label);
}
void MainWindow::openCamera(std::string address, QLabel* label) {
    cv::VideoCapture cap(address);
    cv::Mat frame;
    while (cap.isOpened()) {
        cap >> frame;
        // cv::imwrite("/Users/wking/Desktop/test.jpg", frame); // This function works!
        label->setPixmap(QPixmap::fromImage(QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888).rgbSwapped()));
    }
}

我想通过使用 std::thread 来实现它。但是,它什么也没显示。在我单击输入正确的按钮后,程序不会更新标签。我在这里错过了什么吗?谢谢。

【问题讨论】:

  • 在 Qt 中,您只能从主线程访问小部件。
  • 如果我想从其他线程更新小部件怎么办?我想同时播放多个视频。你有更好的线程处理实践吗?

标签: multithreading qt opencv


【解决方案1】:

在 Qt 中,您只能从主线程访问小部件。您可以使用 Qt 信号槽机制将数据从工作线程传递到主线程:

class MainWindow
{
    ...

    static void openCamera(const std::string& address, QLabel* label, MainWindow *window);

signals:
    void updateLabelSig(QLabel *label, QPixmap pic);

private slots:
    void updateLabel(QLabel *label, QPixmap pic);

    ...
}

MainWindow::MainWindow()
{
    connect(this, SIGNAL(updateLabelSig(QLabel*, QPixmap), this, SLOT(QLabel*, QPixmap));
    ...
}

void MainWindow::startThread() 
{
    std::string address;
    QLabel* label;
    // Get the input from GUI and assign it to std::string address
    // and find the label corresponding to clicked button.
    // ...
    std::thread t(openCamera, address, label, this);
}

void MainWindow::openCamera(std::string address, QLabel* label, MainWindow *window) 
{
    cv::VideoCapture cap(address);
    cv::Mat frame;
    while (cap.isOpened()) {
        cap >> frame;
        // cv::imwrite("/Users/wking/Desktop/test.jpg", frame); // This function works!

        QPixmap pic = QPixmap::fromImage(QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888).rgbSwapped());

        window->updateLabelSig(label, pic);
    }
}

void MainWindow::updateLabel(QLabel *label, QPixmap pic)
{
     label->setPixmap(pic);
}

【讨论】:

    【解决方案2】:

    只有主线程(GUI 线程)可以更新 UI。
    我有一个类似的项目。我使用 QThread 和 Worker 类。 Worker 类有一个插槽来读取视频文件。如果完成读取一帧,它将发出一个信号。
    在 MainWindow 中,我创建了一些 QThread,将工作对象移动到线程。
    将 Worker 的信号 frameFinished 与函数连接,以在标签上显示 cv::Mat。

    class Worker : public QObject
    {
        Q_OBJECT
    public:
        Worker(QString path, int id);
        ~Worker();
    
    public slots:
        void readVideo(QString path = "");
    
    signals:
        // frame and index of label which frame will be displayed
        void frameFinished(cv::Mat frame, int index);
    
        void finished(int index);
    
    private:
        QString filepath;
        int index;
    };
    

    这里是worker.cpp

    #include "worker.h"
    #include <QDebug>
    #include <QThread>
    #include <QTime>
    Worker::Worker(QString path, int id) : filepath(path), index(id)
    {
    
    }
    
    Worker::~Worker()
    {
    }
    
    void Worker::readVideo(QString path)
    {
        if (path.length() > 0)
            filepath = path;
    
        cv::VideoCapture cap(filepath.toStdString());
    
        if (! cap.isOpened())
        {
            qDebug() << "Can't open video file " << filepath;
            emit finished(index);
            return;
        }
    
        cv::Mat frame;
        while (true)
        {
            cap >> frame;
            if (frame.empty())
            {
                frame = cv::Mat(cv::Size(720, 576), CV_8UC3, cv::Scalar(192, 0, 0));
                emit frameFinished(frame, index);
                break;
            }
    
    
            emit frameFinished(frame.clone(), index);
            QThread::msleep(30);
        }
    
        emit finished(index);
    }
    

    主窗口.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <opencv2/opencv.hpp>
    
    #include "worker.h"
    
    #define MAX_NUM_CAM 8
    
    namespace Ui {
    class MainWindow;
    }
    
    class QThread;
    class QLabel;
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
        void init();
    
    private slots:
        void displayFrame(cv::Mat frame, int index);
    
    
    private:
        Ui::MainWindow *ui;
    
        int numCams;
        QLabel *labels[MAX_NUM_CAM];
        QThread* threads[MAX_NUM_CAM];
        Worker* workers[MAX_NUM_CAM];
    };
    
    #endif // MAINWINDOW_H
    

    主窗口.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QDebug>
    #include <QThread>
    #include <QLabel>
    #include <QGridLayout>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        qRegisterMetaType< cv::Mat >("cv::Mat");
    
        qDebug() << "Main thread " << QThread::currentThreadId();
        init();
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::init()
    {
        QGridLayout *grid = new QGridLayout;
        int numCols = 2;
    
        numCams = 4;
    
        int row = 0, col = 0;
        for (int i = 0; i < numCams; i++)
        {
            labels[i] = new QLabel;
    
            row = i / numCols;
            col = i % numCols;
            grid->addWidget(labels[i], row, col);
    
    
            threads[i] = new QThread;
            workers[i] = new Worker(QString("/home/shang/Videos/%1.mp4").arg(i+1), i);
            workers[i]->moveToThread(threads[i]);
    
            connect(workers[i], SIGNAL(frameFinished(cv::Mat, int)), this, SLOT(displayFrame(cv::Mat,int)));
            connect(threads[i], SIGNAL(started()), workers[i], SLOT(readVideo()));
    
            connect(workers[i], SIGNAL(finished(int)), threads[i], SLOT(quit()));
            connect(workers[i], SIGNAL(finished(int)), workers[i], SLOT(deleteLater()));
    
            connect(threads[i], SIGNAL(finished()), threads[i], SLOT(deleteLater()));
    
            threads[i]->start();
        }
    
        this->centralWidget()->setLayout(grid);
    
    }
    
    void MainWindow::displayFrame(cv::Mat frame, int index)
    {
        QPixmap p = QPixmap::fromImage(QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888).rgbSwapped());
        p = p.scaled(QSize(frame.cols/2, frame.rows/2));
        labels[index]->setPixmap(p);
    }
    

    【讨论】:

      【解决方案3】:

      1.也许你想念t.detach();

      2.更新ui:可以从子线程发出信号,然后在槽函数中更新ui。(槽函数将在主线程中运行)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-23
        • 2014-02-22
        • 1970-01-01
        • 2021-03-11
        • 1970-01-01
        • 2016-10-14
        • 1970-01-01
        相关资源
        最近更新 更多