Qt + OpenCV 使用 std::thread 播放视频
Posted
技术标签:
【中文标题】Qt + OpenCV 使用 std::thread 播放视频【英文标题】:Qt + OpenCV play videos with 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 中,您只能从主线程访问小部件。 如果我想从其他线程更新小部件怎么办?我想同时播放多个视频。你有更好的线程处理实践吗? 【参考方案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。(槽函数将在主线程中运行)
【讨论】:
以上是关于Qt + OpenCV 使用 std::thread 播放视频的主要内容,如果未能解决你的问题,请参考以下文章