参考《qt5与opencv3计算机视觉应用开发》
通过创建一个示例项目来说明如何使用QThread及其附属类创建多线程应用程序。qt里创建多线程有两种方法:一是子类化 QThread 并重写run方法,另一种是使用moveToThread函数。
1 子类化QThread
1.1 创建应用程序
创建名为 MultithreadedCV 的Qt 控件应用程序,注意在 .pro 文件中包含 opencv。在 ui 界面上添加两个 QLbel 类标签,网格布局,左边标签为 inVideo,右边标签为 outVideo。将两者的 alignment/Horizontal 属性设置为 AlignHCenter。如下图:
1.2 创建 QThread 子类
右键项目,从菜单中选择 " Add new",创建名为 VideoProcessorThread 的新类。确保在新的类向导中的界面与下图一致:
1.3 更新代码
首先在新创建的 .h 头文件中更新包含文件 #include,添加
#include <QThread>
#include "opencv2/opencv.hpp"
#include <QPixmap>
更新类名,将头文件中的继承自 QObject 改成继承自 QThread,如下:
class VideoProcessorThread : public QThread
更新新创建的 .cpp 文件中的构造函数:
VideoProcessorThread::VideoProcessorThread(QObject *parent) : QThread(parent)
{
}
然后就可以编辑函数定义和实现了。
向新添加的头文件中添加一些必要的声明,将下列代码添加至类的私有成员区域:
void run() override;
然后将下列代码添加至 signals 部分:
void inDisplay(QPixmap pixmap);
void outDisplay(QPixmap pixmap);
将 run 函数的实现添加到 .cpp 文件:
void VideoProcessorThread::run()
{
using namespace cv;
VideoCapture camera(0);
Mat inFrame, outFrame;
while(camera.isOpened() && !isInterruptionRequested())
{
camera >> inFrame;
if(inFrame.empty())
continue;
bitwise_not(inFrame, outFrame);
emit inDisplay(
QPixmap::fromImage(
QImage(
inFrame.data,
inFrame.cols,
inFrame.rows,
inFrame.step,
QImage::Format_RGB888)
.rgbSwapped()));
emit outDisplay(
QPixmap::fromImage(
QImage(
outFrame.data,
outFrame.cols,
outFrame.rows,
outFrame.step,
QImage::Format_RGB888)
.rgbSwapped()));
}
}
1.4 启动程序
为了控制线程及其执行行为,需要使用以下函数:
- start: 用于启动一个尚未启动的线程,该函数通过调用所实现的 run 函数来启动执行。在这里就是我们实现的子类 run()。
- terminate: 用于强制终止线程。
- setTerminationEnabled: 用于允许或禁止 terminater 函数。
- wait: 用于阻塞线程(强制等待),直至线程完成或者达到超时值。
- requestInterruption 和 isRequestInterrupted: 用于设置和获取中断请求状态。
- isRunning 和 isFinished: 用于请求线程的执行状态。
在 MainWindow 中使用线程(调用线程),将其头文件包含进 mainwindow.h:
#include "videoprocessorthread.h"
然后,将下面的代码行添加到 MainWindow 的私有成员部分:
VideoProcessorThread processor;
使用 connect 将连接信号和槽,在 MainWindow 构造函数中添加如下代码:
connect(&processor,
SIGNAL(inDisplay(QPixmap)),
ui -> inVideo,
SLOT(setPixmap(QPixmap)));
connect(&processor,
SIGNAL(outDisplay(QPixmap)),
ui -> outVideo,
SLOT(setPixmap(QPixmap)));
processor.start();
将下列代码添加到 MainWindow 析构函数中,放在 delete ui; 代码行之前:
processor.requestInterruption();
processor.wait();
在析构函数中调用 requestInterruption() 函数,能够保证一旦 MainWindow 关闭并且删除 GUI 之前请求线程停止,通过调用 wait 函数,能够确保等待线程完成清理并安全执行完毕之后再继续执行 delete 指令。
1.5 流程总结
-
创建应用程序,一定要包含 QObject,修改 ui 界面,修改 ObjectName 属性,修改布局等;
-
创建新的子类,在头文件中添加要使用的文件,修改相关类和构造函数; 在子类中添加函数声明和实现;
-
在主程序中添加包含文件,并在头文件中的私有成员中使用 VideoProccessorThread 类的构造函数创建线程指针processer,然后添加信号和槽的连接;
-
调用 start() 函数启动线程,在析构函数中添加 requestInterruption 和 wait 函数用于保证安全。
可以在一个进程中创建多个线程,方法就是创建多个 QThread 的实例(即为创建多个类),然后在 MainWindow 的头文件中创建一个 VideoProcessorThread(创建的类名) 的指针,然后使用 start() 函数即可启动多个线程。