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

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

    看上去没有问题,但是实际上这种“线程”处理的方法低效却又粗暴;最为严重的是,它可能会降低整个程序的效率。这篇博客,就是从现有问题出发、引入相关资料、提出自己的思考,并且最终尝试得到“优雅”的解决方法。
一、使用QTimer存在的问题;
    图像处理问题面临的棘手问题之一,就是“效率”:一个实时的图像处理算法,其单幅处理时间需要降低到50ms以下,这个困难不言而喻;反过来说,比较耗时的视频算法,经常是存在的。在使用QTimer的这种情况下,这种比较耗时的图像处理算法,很可能会拉低整个程序的运行速度。
    我们可以通过一个简单的例子来观察:
    通过将现有的图像处理函数,修改为一个比较耗时的操作:
这种情况下,不仅整个界面不响应输入,而且会出现假死亡的情况:
那们这里模拟的就是比较极端的耗时线程,我们会将这个耗时操作在后面反复使用。
二、引入moveToThread函数;
    要说moveToThread是什么,最好的资料是QT文档。它是一个QObject的函数,也就是基本上所有QT对象都会继承这个函数。
void QObject::moveToThread(QThread *targetThread)
myObject->moveToThread(QApplication::instance()->thread());

    而我们肯定是要将处理视频的这个工作线程插入到主线程中去。工作线程它的特点,就是重复进行从摄像头中获取图片->处理这张图片->显示处理结果这个过程。
    使用Timer方法,是没有办法使用moveToThread函数的,必须要将工作线程独立出来。
三、引入Process线程的解决方法;
    《ComputerVision with opencv3 and qt5》书中为我们提供了非常好的例子,首先来学习:
    通过引入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::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 传出的两个信号直接显示在界面上。
这种情况下,将那句
写入主要线程,产生的结果是虽然有2、3秒的延时,但是还是最终能够退掉的。证明这种效果下线程的时效性更好,应该被采用。
其中,值得注意的是moveToThread的运用,是在主线程中生成工作线程,而后通过moveToThread方法插入主线程中去。
四、采用videoprocess方法升级GOQttemplate
    细节不赘述,你可以直接看结果。这里需要说明的是改成这种线程方法后可能存在的问题。在原来的方法中,由于函数都在主线程中,这样包括videocapture的号,是否使用算法等问题,都直接可以传递变量。但是在目前这种情况下,则必须采用一些方法才能够传递。
比如原代码中的StartVideo,默认打开的是camera(0),这个肯定是需要修改的。
这里就涉及到一些传值的问题,我们还是力图用最简单、直接的方法解决问题,这里还是采用传递信号变量的方式。

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

//打开摄像头
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线程和图像处理线程分开
    在前面提到了“​从摄像头中获取图片->处理这张图片->显示处理结果”,实际上这不是一个原子操作,图片的获取、显示和图片的处理应该是可以分开了的。
     但是这里肯定就涉及到了较为复杂的线程间通信问题。那么这个操作对于用户体验是否会有提高了?答案应该是要和这个项目本身有关。对于视频处理程序来说,只关心的是最终处理的结果,那么将处理操作放在工作线程中(而不是开两个工作线程)就是最省时间的方法;但是也可能存在这种情况,一方面用户需要看到全图,另一方面又需要将处理的结果叠加到原图上去,那么分两个工作现场就是必要的了。
     在这个思想的指导下,我完成了这方面工作设计:
     
     为了创造2个线程,所以就必须创建2个类。其中1个是这样
1个是这样
实现这块,主要是看这个消息/槽的机制
这个连接的定义,是写在主程序中的。为了让Mat能够被QT识别,还需要写一句
感谢阅读至此,希望有所帮助!


GOQTTemplate3的多线程化改造的更多相关文章

  1. 走进异步世界:EnyimMemcached异步化改造引起的内存泄漏

    6月30日我们发布了异步化改造后的博客程序之后,出现了高内存.高CPU.高线程数的不理想情况. 经过一周的追查,终于水落日出——引起不理想情况的根源是我们修改过的EnyimMemcached代码存在内 ...

  2. Docker---大型项目容器化改造

           虚拟化和容器化是项目云化不可避免的两个问题.虚拟化由于是纯平台操作,一个运行于linux操作系统的项目几乎不需要做任何改造就可以支持虚拟化.而项目如果要支持容器化则需要做许多细致的改造工 ...

  3. Operating System-Thread(5)弹出式线程&&使单线程代码多线程化会产生那些问题

    本文主要内容 弹出式线程(Pop-up threads) 使单线程代码多线程化会产生那些问题 一.弹出式线程(Pop-up threads) 以在一个http到达之后一个Service的处理为例子来介 ...

  4. Dubbo 2.7新特性之异步化改造

    这是why技术的第1篇原创文章 我与Dubbo的二三事 我是2016年毕业的,在我毕业之前,我在学校里面学到的框架都是SSH,即struts+spring+hibernate,是的你没有看错,在大学里 ...

  5. 利用GDAL进行工具开源化改造

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 为利于项目实施,团队用AE写过一个插件式的工具集,主要包括了数 ...

  6. txtbook简单HTML可读化改造

    一般来讲下载的txtbook在notepad或者之类的文本编辑器下的阅读体验不是很好,PC上面专门装个txt阅读器好像有点杀鸡用牛刀,可以用HTML简单处理一下就可以放在浏览器下爽快的阅读了,这个操作 ...

  7. ZooKeeper客户端事件串行化处理

    为了提升系统的性能,进一步提高系统的吞吐能力,最近公司很多系统都在进行异步化改造.在异步化改造的过程中,肯定会比以前碰到更多的多线程问题,上周就碰到ZooKeeper客户端异步化过程中的一个死锁问题, ...

  8. 【转】Redis学习---阿里云Redis多线程性能增强版详解

    [原文]https://www.toutiao.com/i6594620107123589635/ 摘要 Redis做为高性能的K-V数据库,由于其高性能,丰富的数据结构支持,易用等特性,而得到广泛的 ...

  9. 第四章 电商云化,4.2 集团AliDocker化双11总结(作者: 林轩、白慕、潇谦)

    4.2 集团AliDocker化双11总结 前言 在基础设施方面,今年双11最大的变化是支撑双11的所有交易核心应用都跑在了Docker容器中.几十万Docker容器撑起了双11交易17.5万笔每秒的 ...

随机推荐

  1. iOS 如何查看APP的jetsamEvent日志

    1.如何在iPhone上查看 设置-通用-分析-分析数据- JetsamEvent+日志 打头的系统日志. 2.如何在Mac 上查看此类分析日志 1.手机链接MAC 2.打开iTunes,点开手机图标 ...

  2. stm32通用定时器详解

    在stm32的开发中我们经常会用到定时器,因此在学习stm32的过程中定时器是必须要学的,而定时主要又分为三大类分别为: 高级控制定时器(TIM1与TIM8) 通用定时器(TIM2~TIM5) 基本定 ...

  3. Spark入门到精通--(第九节)环境搭建(Hive搭建)

    上一节搭建完了Hadoop集群,这一节我们来搭建Hive集群,主要是后面的Spark SQL要用到Hive的环境. Hive下载安装 下载Hive 0.13的软件包,可以在百度网盘进行下载.链接: h ...

  4. pymongo基础

    PyMongo是MongoDB数据库的python模块 MongoDB是由C++语音编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统. win10 安装 4.0 使用官网的配置 使用 n ...

  5. Yoink Mac版(临时文件存储助手)中文版

    Yoink Mac版是Mac上一款临时文件存储助手,当你拖动文件时Yoink for Mac就会出现,拖放文件到Yoink窗口中即可,需要文件时随时都能从Yoink窗口中拖出文件,使用非常便捷,小编准 ...

  6. dll加密工具

    Virbox Protector Standalone 加壳工具可直接对dll文件进行加壳,防止代码反编译,更安全,更方便. 产品简介 Virbox Protector Standalone提供了强大 ...

  7. 运维面试题之linux基础

    吐槽: 某某命令是什么,某个配置文件的路径,呃....你难道不知道有--help和Tab这种东西吗? linux系统的启动过程是怎么样的? grub引导>加载内核>启动init进程依据in ...

  8. SSMP一次请求数据处理过程分析

    控制器代码 @RequestMapping("/changeUserPwd") public TranMessage changeUserPwd(String oriPwd, St ...

  9. Oracle控制文件冗余

    1.备份参数文件.检查控制文件.检查磁盘组名 sqlplus / as sysdba !echo "create pfile='$HOME/pfile_$ORACLE_SID_`date + ...

  10. 谷歌AM HTML视频代码amp-video示例

    ntroduction Use amp-video to embed videos into your AMP HTML files. Video source files must be serve ...