[Qt学习笔记]Qt线程间数据通讯及数据共享

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_OBJECT

public:
	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_OBJECT

public:
    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”)来进行注册。

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 这个指针自由的调用数据类的成员变量和函数了,当然这里要引用数据类的头文件。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt中,线程通讯可以使用共享内存来实现。共享内存是一种跨进程共享数据的机制,可以在不同的线程传递数据。 Qt提供了QSharedMemory类来操作共享内存。下面是一个使用共享内存进行线程通讯的示例: ```cpp // 创建一个共享内存对象 QSharedMemory sharedMemory; // 设置共享内存的名称 sharedMemory.setKey("MySharedMemory"); // 在一个线程中写入数据到共享内存 if (sharedMemory.create(1024)) { sharedMemory.lock(); char* data = static_cast<char*>(sharedMemory.data()); // 在这里写入数据到共享内存 // 注意:要确保多个线程对共享内存的访问是互斥的,可以使用QMutex来实现互斥访问 sharedMemory.unlock(); } // 在另一个线程中读取共享内存中的数据 if (sharedMemory.attach()) { sharedMemory.lock(); char* data = static_cast<char*>(sharedMemory.data()); // 在这里读取共享内存中的数据 sharedMemory.unlock(); sharedMemory.detach(); } ``` 在上面的示例中,我们创建了一个名为"MySharedMemory"的共享内存对象,并设置了它的大小为1024字节。然后,在一个线程中写入数据到共享内存中,另一个线程则可以读取共享内存中的数据。 需要注意的是,在多个线程对共享内存进行读写时,需要确保访问的互斥性,以避免数据竞争。可以使用QMutex或其他同步机制来实现互斥访问。 另外,还可以使用信号与槽机制来实现线程的通讯。Qt提供了QObject::moveToThread()方法,可以将一个QObject对象移动到指定的线程中,从而实现线程的信号与槽的连接。这种方式相对于共享内存来说更加方便和安全。 希望以上信息能对你有帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值