• GOQTTemplate3的多线程化改造


        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识别,还需要写一句
    感谢阅读至此,希望有所帮助!






  • 相关阅读:
    轻量级数据库sqlite的使用
    Integer引发的思考
    css限制显示行数
    数据库 chapter 17 数据仓库与联机分析处理技术
    数据库 chapter 15 对象关系数据库系统
    数据库 chapter 16 XML数据库
    数据库 chapter 14 分布式数据库系统
    数据库 chapter 11 并发控制
    数据库 chapter 12 数据库管理系统
    数据库 chapter 13 数据库技术新发展
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/16947862.html
Copyright © 2020-2023  润新知