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. 14 - How to check replication status

    The people using PostgreSQL and the Streaming Replication feature seem to ask many of the same quest ...

  2. react高阶组件的使用

    为了提高代码的复用在react中我们可以使用高阶组件 1.添加高阶组件 高阶组件主要代码模板HOC.js export default (WrappedComponent) => { retur ...

  3. IntelliJ IDEA 指定Java编译版本

    在IntelliJ IDEA 15中使用Maven时,IDEA将默认的编译版本.源码版本设置为jdk5.编译项目的时候出现警告:”Warning:Java: 源值1.5已过时, 将在未来所有发行版中删 ...

  4. Mybatis异常--There is no getter for property named 'XXX' in 'class java.lang.String'

    第一种 在service层加@Param(value="ip") void deleteIpsetup(@Param(value="ip")String ip) ...

  5. docker的安装和升级

    1.删除docker sudo apt-get purge docker.io sudo rm -rf /etc/docker/ sudo apt-get autoremove 2. 安装docker ...

  6. VUE-009-页面打开时初始化配置项内容

    网页开发过程中,尤其是在表单开发过程中,不可避免的会有各种各样的基础数据需要展示,供用户使用.例如,大家在办理信用卡时,经常需要填写各种表单数据,其中:性别(男.女).学历(高中及以下.大专.本科.研 ...

  7. 如何修改运行中的docker容器的端口映射和挂载目录

    在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...

  8. TCP/IP协议 网络层

    IP协议介绍 1.IP协议是TCP/IP协议族中最为核心的协议.IP协议将多个包交换网络连接起来,它在源地址和目的地址之间传送一种称为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包 ...

  9. java的智能提示无法打开

    第一步:选中“window”->“preference”   第二步:选中“java”,并展开   第三步:选中“Editor”,并展开   第四步:选中“Content Assist”,在右侧 ...

  10. mysql执行update语句受影响行数是0

    mybatis连接mysql数据库,发现同一个update执行多次,返回的int值都是1. 我记得同样的update再次执行时 受影响行数是0. 后来发现,我之前一直用的SQLyog是这样子的. 原来 ...