首页 > 其他分享 >GOQTTemplate3的多线程化改造

GOQTTemplate3的多线程化改造

时间:2022-12-25 12:03:27浏览次数:37  
标签:inFrame thread GOQTTemplate3 改造 void QPixmap 线程 多线程 processor

    GOQTTemplate3作为一个QT+OpenCV的平台,希望能够为使用者提供基础的跨平台的图像处理框架。图像处理算法和GUI两个线程的隔离,是必然需要的。在之前的版本中,都采用了OnTimer的方法:

    

GOQTTemplate3的多线程化改造_工作线程


    并且在选择并打开摄像头的时候,开启这个timer



GOQTTemplate3的多线程化改造_工作线程_02



    看上去没有问题,但是实际上这种“线程”处理的方法低效却又粗暴;最为严重的是,它可能会降低整个程序的效率。这篇博客,就是从现有问题出发、引入相关资料 、 提出自己的思考,并且最终尝试得到“优雅”的解决方法。


一、使用QTimer存在的问题;


    图像处理问题面临的棘手问题之一,就是“效率”:一个实时的图像处理算法,其单幅处理时间需要降低到50ms以下,这个困难不言而喻;反过来说,比较耗时的视频算法,经常是存在的。在使用 QTimer的 这种情况下,这种比较耗时的图像处理算法,很可能会拉低整个程序的运行速度。


    我们可以通过一个简单的例子来观察:


    通过将现有的图像处理函数,修改为一个比较耗时的操作:



GOQTTemplate3的多线程化改造_工作线程_03


这种情况下,不仅整个界面不响应输入,而且会出现假死亡的情况:



GOQTTemplate3的多线程化改造_ide_04


那们这里模拟的就是比较极端的耗时线程,我们会将这个耗时操作在后面反复使用。


二、引入moveToThread函数;


    要说moveToThread是什么,最好的资料是QT文档。它是一个QObject的函数,也就是基本上所有QT对象都会继承这个函数。


void QObject : :moveToThread(QThread *targetThread)
myObject - >moveToThread(QApplication : :instance() - >thread()); ​



    而我们肯定是要将处理视频的这个工作线程插入到主线程中去。工作线程它的特点,就是重复进行从摄像头中获取图片->处理这张图片->显示处理结果这个过程。


    使用Timer方法,是没有办法使用moveToThread函数的,必须要将工作线程独立出来。


三、引入Process线程的解决方法;


    《ComputerVision with opencv3 and qt5》书中为我们提供了非常好的例子,首先来学习:



GOQTTemplate3的多线程化改造_QT_05


    通过引入videoprocessor这个QT对OpenCV videocapture的再封装来解决问题,其中使用的信号/槽机制非常精彩,代码可以说是非常精简的。


​​class VideoProcessor    :    public QObject   
{
Q_OBJECT
public
:

explicit VideoProcessor(QObject *parent = nullptr);

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

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

private
:

bool stopped;
}; ​​


其实现方式:

​​void VideoProcessor        :        :startVideo()        
{
using namespace cv;
VideoCapture camera( 0);
Mat inFrame, 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()
{
qDebug() << Q_FUNC_INFO;
stopped = true;
}

​​

在其实现中,只有一个信号量“  stopped   “ ; 主要是StartVideo函数,而图像处理算法以“三明治”方式加载StartVideo函数中。比较一下,这里直接将 inFrame 和 outFrame  这两个Mat以信号的方式 emit 出来,而后在主线程中

​​​:       :MainWindow(QWidget        *parent)        :       
QMainWindow(parent),
ui( new Ui : :MainWindow)
{
ui - >setupUi( this);

processor = new VideoProcessor();

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

connect(processor - >thread(),
SIGNAL(started()),
processor,
SLOT(startVideo()));

connect(processor - >thread(),
SIGNAL(finished()),
processor,
SLOT(stopVideo()));

connect(processor,
SIGNAL(inDisplay(QPixmap)),
ui - >inVideo,
SLOT(setPixmap(QPixmap)));

connect(processor,
SIGNAL(outDisplay(QPixmap)),
ui - >outVideo,
SLOT(setPixmap(QPixmap)));

processor - >thread() - >start();
}

MainWindow : : ~MainWindow()
{
processor - >stopVideo();
processor - >thread() - >quit();
processor - >thread() - >wait();

delete ui;
} ​​​

直接以这种方式启动Started和finished(这两者都是Thread自己的函数),并且将 VideoProcessor  传出的两个信号直接显示在界面上。



GOQTTemplate3的多线程化改造_工作线程_06


这种情况下,将那句



GOQTTemplate3的多线程化改造_QT_07


写入主要线程,产生的结果是虽然有2、3秒的延时,但是还是最终能够退掉的。证明这种效果下线程的时效性更好,应该被采用。


其中,值得注意的是moveToThread的运用,是在主线程中生成工作线程,而后通过


四、采用videoprocess方法升级GOQttemplate



GOQTTemplate3的多线程化改造_QT_08


    细节不赘述,你可以直接看结果。这里需要说明的是改成这种线程方法后可能存在的问题。在原来的方法中,由于函数都在主线程中,这样包括videocapture的号,是否使用算法等问题,都直接可以传递变量。但是在目前这种情况下,则必须采用一些方法才能够传递。



GOQTTemplate3的多线程化改造_ide_09


比如原代码中的StartVideo,默认打开的是camera(0),这个肯定是需要修改的。


GOQTTemplate3的多线程化改造_工作线程_10


这里就涉及到一些传值的问题,我们还是力图用最简单、直接的方法解决问题,这里还是采用传递信号变量的方式。



值得注意的是,由于这里采用了多线程,所以在打开新摄像头的时候要尤其注意。

​​//打开摄像头        
void MainWindow : :on_pushButton_OpenCam_clicked()
{
//stop camera first
processor - >stopVideo();
processor - >thread() - >quit();
processor - >thread() - >wait();
//打开摄像头,从摄像头中获取视频
processor - >n_cameraindex = ui - >comboCamera - >currentIndex();
processor - >thread() - >start();
} ​​

下面我将其它功能进行完善。



五、将camera线程和图像处理线程分开


    在前面提到了“ 从摄像头中获取图片->处理这张图片->显示处理结果”,实际上这不是一个原子操作,图片的获取、显示和图片的处理应该是可以分开了的。


     但是这里肯定就涉及到了较为复杂的线程间通信问题。那么这个操作对于用户体验是否会有提高了?答案应该是要和这个项目本身有关。对于视频处理程序来说,只关心的是最终处理的结果,那么将处理操作放在工作线程中(而不是开两个工作线程)就是最省时间的方法;但是也可能存在这种情况,一方面用户需要看到全图,另一方面又需要将处理的结果叠加到原图上去,那么分两个工作现场就是必要的了。


     在这个思想的指导下,我完成了这方面工作设计:

     

GOQTTemplate3的多线程化改造_QT_11


     为了创造2个线程,所以就必须创建2个类。其中1个是这样


GOQTTemplate3的多线程化改造_QT_12


1个是这样


GOQTTemplate3的多线程化改造_QT_13


实现这块,主要是看这个消息/槽的机制


GOQTTemplate3的多线程化改造_QT_14


这个连接的定义,是写在主程序中的。为了让Mat能够被QT识别,还需要写一句


GOQTTemplate3的多线程化改造_QT_15


感谢阅读至此,希望有所帮助!

标签:inFrame,thread,GOQTTemplate3,改造,void,QPixmap,线程,多线程,processor
From: https://blog.51cto.com/jsxyhelu2017/5968071

相关文章

  • 让人恶心的多线程代码,性能怎么优化!
    小姐姐味道(微信公众号ID:xjjdog)Java中最烦人的,就是多线程,一不小心,代码写的比单线程还慢,这就让人非常尴尬。通常情况下,我们会使用ThreadLocal实现线程封闭,比如避免SimpleD......
  • 多线程
    多线程什么是进程?什么是线程?进程是一个应用程序(1个进程是一个软件)。线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。对于java程序来说,当在DOS命令......
  • 基于ZR.VUE 前端的改造,页面刷新报错
     问题描述:前后端分离开发,分开部署.页面刷新直接报404错误的解决办法提示: 先在 .env.development中配置 VUE_APP_BASE_API,将'/'替换为后端地址'http......
  • 多线程必知必会的知识点
    说说阻塞队列的实现:可以参考ArrayBlockingQueue的底层实现(锁和同步都行);如果队列是空的,消费者会一直等待,当生产者添加元素时候,消费者是如何知道当前队列有元素的呢?如果让你来......
  • 多线程
    多线程什么是进程?什么是线程?进程是一个应用程序(1个进程是一个软件)。线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。对于java程序来说,当在DOS命令......
  • 【并发技术系列】「多线程并发编程」技术体系和并发模型的基础探究(夯实基础)
    让我们通过本篇文章一同进入并发编程技术的世界里面,相信通过这篇文文章一定会对话你的并发技术体系有一定帮助以及夯实你的基础功底。基本概念并发concurrency并行paralleli......
  • 变电站综合自动化系统在某机械制造公司35kV变电站改造应用
    1.项目背景  随着社会的高速发展带动之下,我国企业生产中对于电力能源的依赖程度逐步变大,生产生活的电力供应存在的矛盾更大。某机械制造公司为了拓展公司生产业务,确保企业......
  • jdk 1.8 多线程
    packagecom.sleep.demo;importorg.springframework.util.CollectionUtils;importjava.util.ArrayList;importjava.util.List;importjava.util.Random;import......
  • Java多线程
    核心概念线程就是独立的执行路径在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。main()称之为主线程,为系统的入口,用于执行整个程序;在一个进程......
  • 一、【Java】多线程与高并发
    一、启动多线程的三种方式1、继承Thread接口类实现run()方法staticclassMyThredextendsThread{@Overridepublicvoidrun(){system.out.println("Hellow......