【OpenCV与Qt5】多线程【2使用QThread的low-level多线程方法】

概述

这一部分我们将学习使用QThread及其附属类来创建多线程应用,我们将创建一个示例程序,这个程序在使用单独的线程在界面显示相机的输入、输出图像。这使得GUI线程(主线程)空闲并保持响应,密集的计算通过第二个进程进行。正如之前所述,我们将使用计算机视觉的GUI开发的示例,不过这个方法具有普适性,可用于其它多线程场合。
我们将使用QThread中的两种方式来完成这一任务。第一,子类化并重写run方法。第二,使用Qt objects中都有的moveToThread函数完成多线程。

子类化 QThread

首先创建一个Qt Widgets应用,命名为MultithreadedCV,将openCV库加载到项目中。
在mainwindow.ui文件中,移除工具栏、菜单栏和状态栏,然后添加两个label, 一个用于显示原始图像,另一个用于显示处理后的图像。
属性树
确保objectName属性为inVideo和outVideo。将label的alignment/Horizontal设为AlignHCenter。
现在,创建一个名为VideoProcessorThread的新类,右击工程目录在弹出的菜单中选择Add New,然后选择C++ Class,如下图,
这里写图片描述
创建完成后,videoprocessorthread.h和videoprocessthread.cpp添加到项目中去了,我们将在这个类中实现将GUI与图像显示线程分离。首先,确保此类继承于QThread

#include <QThread>
#include "opencv2/opencv.hpp"

class VidwoProcessorThread : public QThread

在cpp文件中修构造函数代码如下

VidwoProcessorThread::VidwoProcessorThread(QObject *parent)
    : QThread(parent)

现在在头文件中添加必要的声明,在私有成员中添加如下代码

void run() override;

然后在signals部分添加:

void inDisplay(QPixmap pixmap);
void outDisplay(QPixmap pixmap);

最后,在cpp文件中添加run函数的实现:

void VidwoProcessorThread::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()));
    }

}

run函数被重写,在run函数的实现中添加了视频处理的任务。假如你在mainwindow.cpp中执行上述代码,你会发现你的程序不会响应,最终强制退出。然而通过这种方式,同样的代码段就得以正确的执行,你只需调用start函数(不是run!),注意run是用来内部调用的,所以你只需实现它的代码即可,要控制这个线程的执行行为,你要使用下面这个函数:

  • start: 此函数启动一个线程,调用run函数,可以向该函数传递一个参数以控制线程的优先级
    • QThread::LowestPriority
    • QThread::LowPriority
    • QThread::NormalPriority
    • QThread::HighPriority
    • QThread::HighesetPriority
    • QThread::TimeCriticalPriority (尽可能的多安排处理该线程)
    • QThread::InheritPriority(默认值,简单的继承于父对象的优先级)
  • terminate: 强制终止线程,仅用于极端情况,尽量不要用
  • setTerminationEnabled: 允许或禁止terminate函数
  • wait: 强制等待线程结束,或等待时间溢出(time out)
  • requestInterruption 和 isRequestInterrupted: 分别为设置和获取中断请求状态,这是中止线程的一个安全的方式。
  • isRunning 和 isFinished:用于获取线程的执行状态

除了这些函数外,QThread还有其它有用的函数,例如quit exit idealThreadCount等等。可以自己尝试一下使用这些函数。QThread可以让你的应用程序达到最佳性能。
让我们继续我们的例子,在run函数中,我们使用了VideoCaputre 类来读取视频帧(死循环),然后对原始图像进行了按位取反bitwise_not操作,当然了,你可以对其进行任意的处理,然后发向射了两个信号。注意我们的循环是死的,不断检查摄像机是否开启及线程是否有中断请求。

现在我们在mainwindow.h中添加这个线程子类的头文件

#include "videoprocessorthread.h"

然后在private成员中添加

VideoPorcessorThread processor;

现在我们去MainWindow的构造函数中添加

connect(&processor,
        SIGNAL(inDisplay(QPixmap)),
        ui->inVidwo,
        SLOT(setPixmap(QPixmap));
connect(&processor,
        SIGNAL(outDisplay(QPixmap)),
        ui->outVideo,
        SLOT(setPixmap(QPixmap));
processor.start();

然后在析构函数delete ui;之前添加

processor.requestInterruption();
processor.wait();

我们把VideoProcessorThread类中的两个信号与MainWindow GUI中的label连接起来,然后启动了这个线程。在GUI删除ui之前,我们请求中止线程,wait函数确保了程序在退出之前线程被安全中止并清除。现在你可以运行这个程序了,界面大概是这样的。
程序运行界面

程序开始,从默认摄像头中读取的图像,只要程序关闭,这一过程就会中止。你可以开启多个线程,只要connect线程中的信号与界面的槽函数即可,这样你就可以同时动态的显示多个摄像机的内容了。

使用moveToThread函数

之前讲到,你可以在任何QObject中使用moveToThread函数确保其运行在单独线程中。我们用此方法重复上面的示例。首先创建相同的GUI,然后添加一个新C++类,但是这次命名为VideoProcessor,创建完成后,你不需要继承于QThread, 让它保持QObjcet就行,将以下成员加到videoprocessor.h文件中。

signals:
    void inDisplay(QPixmap pixmap);
    void outDisplay(QPixmap pixmap);

public slots:
    void startVideo();
    void stopVideo();

private:
    bool stopped;

signals与之前的相同,stopped标志位用于中止视频,防止它永久运行。startVideo和stopVideo用于开始和终止从网络摄像头中处理视频。现在我们到videoprocessor.cpp中,添加如下代码。与之前的代码类似,因为这个类不是继承于QThread,所以我们不需要实现run函数了。

void VideoProcessor::startVideo()
{
    using namespace cv;
    VideoCapture camera(0);
    Mat inFrame;
    Mat outFrame;
    stopped = false;
    while(camera.isOpened() && !stopped) {
        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()));
    }
}

void VideoProcessor::stopVideo()
{
    stopped = true;
}

现在我们可以在MainWindow类中引用这个类了,在mainwindow头文件中include videoprocessor,然后在私有成员中添加 一个VideoProcessor成员。

VideoProcessor *processor;

在MainWindow构造函数中添加以下代码:

processor = new VideoProcessor();

processor->moveToThread(new QThread(this));

connect(processor->thread(),
        SIGNAL(started()),
        processor,
        SLOT(startVideo()));
connect(processor->thread(),
        SIGNAL(finished()),
        processor,
        SLOT(deleteLater()));
connect(processor,
        SIGNAL(inDisplay(QPixmap)),
        ui->inVideo,
        SLOT(setPixmap(QPixmap)));
connect(processor,
        SIGNAL(outDisplay(QPixmap)),
        ui->outVideo,
        SLOT(setPixmap(QPixmap)));
processor->thread()->start();

上面的代码段中,我们创建了一个VideoProcessor实例,注意我们没有在构造函数中为其指派父对象,而且我们将其定义为一个指针。当我们要使用moveToThread函数时,这样做是很关键的。一个有父对象的对象是不可以被移动到一个新线程中去。第二个重要的经验是我们不应该直接调用startVideo函数,它应该connect一个适当的信号,我们这里使用了线程的一个开启信号,你可以使用任何有同样签名的信号来connect。
在MainWindow的析构函数中,添加以下几行

processor->stopVideo();
processor->thread()->quit();
processor->thread()->wait();

像这样开启一个新线程之后,它就必须调用quit函数来终止运行,并且此时线程中不应该有任何的循环及未执行的指令(我们用stopVideo()终止了线程中的所有任务),如果不这样做,将面临严重的线程处理问题。

  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值