1、背景介绍

在应用QT做我的项目开发过程中,常常会用到多线程,比方图像采集一个线程,图像处理一个线程、数据通讯一个线程。这些不同的线程中会呈现数据共享的需要,Qt线程间共享数据次要有三种形式:

1.应用共享内存;即两个线程都可能共享的变量(全局变量),这样两个线程都可能拜访和批改变量,从而达到祝贺目标;
2.应用信号槽机制,将数据从一个线程传递到另外一个线程
3.共享类指针来进行拜访不同类的变量和函数;

第三种是我本人罕用的办法,在上面我总结记录一下。

2、 办法介绍

第一种办法,应用全局变量或全局函数,在其余类或线程中调用,这是各种编程语言中都通用的办法,但全局变量长时间占用内存,影响程序空间使用率,且全局变量批改影响整个程序,程序的安全性无奈保障,个别尽量少用全局变量或函数,这种办法不开展介绍了。

2.1 信号槽进行数据通讯

信号槽性能是QT特有的性能,应用信号槽须要留神以下几个事项:

  1. 只有QObject类及其派生的类能力应用信号和槽的机制
  2. 在线程间应用信号槽进行通信时,槽参数必须应用元数据类型的参数;
  3. 如果应用自定义的数据类型,须要在connect之前将其注册(qRegisterMetaType)为元数据类型;
  4. 线程间用信号槽传递参数的话,要加const,因为const文字常量存在常量区中,生命周期和程序一样长。这样能够防止slot调用的时候参数的运行期已过造成援用有效;

这里我用一个balser相机线程采图应用信号槽发到UI线程显示的Demo来展现一下线程间通过信号槽的数据通讯。

/*图像采集线程头文件*//*GrabThread.h*/#pragma execution_character_set("utf-8")#ifndef _GRABTHREAD_H#define _GRABTHREAD_H#include <Qtwidgets>#include <QtCore>#include <QtGui>#include <pylon/PylonIncludes.h>#include <QThread>#include "opencv2/opencv.hpp"using namespace Pylon;class GrabThread : public QThread{    Q_OBJECTpublic:    GrabThread();    ~GrabThread();    void run();    void init(CInstantCamera &m_camera);    bool isInit();    void stop();    void save(bool);    void grab(int g =1);    cv::Mat Result2Mat(CGrabResultPtr &ptrGrabResult);    CInstantCamera *m_camera;    CGrabResultPtr ptrGrabResult; //Basler 获取后果指针    CImageFormatConverter m_formatConverter;//Basler 图片格式转换类    CPylonImage pylonImage; //Basler 图像格式    QImage m_image; //Qt图片格式    QPixmap m_pix;    String_t m_prefix;    bool m_stop;    bool m_init;    bool m_save;        int m_grab; //获取图像策略 0示意间断获取,1示意获取单帧    int m_num_one;    int m_num_continue;    signals:    //发给UI线程的信号    void ThreadPic(cv::Mat outputPix);};#endif// GRABTHREAD_H
GrabThread.cpp
#include "GrabThread.h"GrabThread::GrabThread(){    m_formatConverter.OutputPixelFormat = PixelType_Mono8;    m_stop = false;    m_init = false;    m_save = false;    m_grab = 0;    m_num_continue = 0;    m_num_one = 0;}GrabThread::~GrabThread(){}void GrabThread::run(){    try    {        m_camera->StartGrabbing(GrabStrategy_LatestImageOnly);        while (m_camera->IsGrabbing() && !m_stop)        {            m_camera->RetrieveResult(5000000, ptrGrabResult);            if (ptrGrabResult->GrabSucceeded())            {                //格局转换                cv::Mat MatImg = Result2Mat(ptrGrabResult);                // qDebug() << "转换胜利" << endl;                //发射信号                emit ThreadPic(MatImg);            }        }        m_stop = false;        m_camera->StopGrabbing();    }    catch (const GenericException &e)    {        // Error handling.        qDebug() << "An exception occurred." << endl            << e.GetDescription() << endl;    }    return;}void GrabThread::init(CInstantCamera &input_camera){    m_camera = &input_camera;    m_init = true;}bool GrabThread::isInit(){    return m_init;}void GrabThread::stop(){    m_stop = true;    this->wait();}void GrabThread::save(bool s){    m_save = s;}void GrabThread::grab(int g){    m_grab = g;}cv::Mat GrabThread::Result2Mat(CGrabResultPtr &ptrGrabResult){    ////格局转换    m_formatConverter.Convert(pylonImage, ptrGrabResult);    uchar * din = (uchar *)(pylonImage.GetBuffer()); //数据指针    cv::Mat cvImage = cv::Mat(ptrGrabResult->GetHeight(),ptrGrabResult->GetWidth(),CV_8UC1,din).clone();    return cvImage;}
在采集线程中收回的信号,在UI线程就要有对应的槽函数。
/* imgShowWidget.h */#ifndef IMGSHOWWIDGET_H#define IMGSHOWWIDGET_H#include <QWidget>#include "opencv2/opencv.hpp"namespace Ui {class ImgShowWidget;}class ImgShowWidget : public QWidget{    Q_OBJECTpublic:    explicit ImgShowWidget(QWidget *parent = 0);    ~ImgShowWidget();private:    Ui::ImgShowWidget *ui;    QImage cvMat2QImage(const cv::Mat& mat);    cv::Mat QImage2Mat(QImage image);private slots:    //显示图像的槽函数    void Thread_Img(cv::Mat img);};#endif // IMGSHOWWIDGET_H
ImgShowWidget.cpp
#include "imgshowwidget.h"#include "ui_imgshowwidget.h"#include <QDebug>#include <QElapsedTimer>using namespace cv;ImgShowWidget::ImgShowWidget(QWidget *parent) :    QWidget(parent),    ui(new Ui::ImgShowWidget){    ui->setupUi(this);    qRegisterMetaType<Mat>("Mat");}ImgShowWidget::~ImgShowWidget(){    delete ui;}void ImgShowWidget::Thread_Img(cv::Mat img){    QImage Qimg;    if(isWork)    {        QElapsedTimer ElapsedTimer;        ElapsedTimer.start();        Mat ResultImg = m_ProcessObj->DetectProcess(img);        qDebug()<<"耗时"<<ElapsedTimer.elapsed()<<"毫秒";        Qimg = cvMat2QImage(ResultImg);    }    else    {        Qimg = cvMat2QImage(img);    }    QPixmap m_pix = QPixmap::fromImage(Qimg);    m_pix = m_pix.scaled(ui->PicShow->size(), Qt::KeepAspectRatio);    ui->PicShow->setPixmap(m_pix);}QImage ImgShowWidget::cvMat2QImage(const cv::Mat &mat){    switch ( mat.type() )    {    // 8-bit  4 channel    case CV_8UC4:    {        QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB32 );        return image;    }        // 8-bit  3 channel    case CV_8UC3:    {        QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB888 );        return image.rgbSwapped();    }        // 8-bit  1 channel    case CV_8UC1:    {        static QVector<QRgb>  sColorTable;        // only create our color table once        if ( sColorTable.isEmpty() )        {            sColorTable.resize( 256 );            for ( int i = 0; i < 256; ++i )            {                sColorTable[i] = qRgb( i, i, i );            }        }        QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_Indexed8 );        image.setColorTable( sColorTable );        return image;    }    default:        qDebug("Image format is not supported: depth=%d and %d channels\n", mat.depth(), mat.channels());        qWarning() << "cvMatToQImage - cv::Mat image type not handled in switch:" << mat.type();        break;    }    return QImage();}

这里就是第一种信号槽的办法,通过emit ThreadPic(MatImg)发送信号,在UI线程通过槽函数Thread_Img(cv::Mat img)来接管Mat类型的图像进行显示,这里Mat类型不是Qt的元数据,所以要应用qRegisterMetaType<Mat>("Mat")来进行注册。

2.2 共享类指针来实现同步调用

如果我创立了一个数据类来保留图像处理时的数据,在图像采集的时候要讲采集的图像放到数据类里,UI线程还会设置不同的变量参数也要放到数据类里,在解决线程要应用数据的时候就须要去数据类去读取数据,这么多类同时去读写,如何能力实现同步共享,这里就须要在UI线程创立各个类之后进行指针的共享。

        m_ImgProcessObj = new ImgProcessThread();        //初始化数据类        currentData = new MyData();        m_Product = new productManager(this);        m_Product->GetMyDataPoint(currentData);        m_ImgProcessObj->GetMyDataPoint(currentData);

这里应用GetMyDataPoint这个函数将数据类指针共享给其余类的须要调用数据的指针,其实就是两个指针指向同一内存地址。

void ImgProcessThread::GetMyDataPoint(MyData *DPoint){    DataPoint = DPoint;}

这样在图像处理类里就能够用DataPoint 这个指针自在的调用数据类的成员变量和函数了,当然这里要援用数据类的头文件。