1、背景介绍
在应用 QT 做我的项目开发过程中,常常会用到多线程,比方图像采集一个线程,图像处理一个线程、数据通讯一个线程。这些不同的线程中会呈现数据共享的需要,Qt 线程间共享数据次要有三种形式:
1. 应用共享内存;即两个线程都可能共享的变量(全局变量),这样两个线程都可能拜访和批改变量,从而达到祝贺目标;
2. 应用信号槽机制,将数据从一个线程传递到另外一个线程
3. 共享类指针来进行拜访不同类的变量和函数;
第三种是我本人罕用的办法,在上面我总结记录一下。
2、办法介绍
第一种办法,应用全局变量或全局函数,在其余类或线程中调用,这是各种编程语言中都通用的办法,但全局变量长时间占用内存,影响程序空间使用率,且全局变量批改影响整个程序,程序的安全性无奈保障,个别尽量少用全局变量或函数,这种办法不开展介绍了。
2.1 信号槽进行数据通讯
信号槽性能是 QT 特有的性能,应用信号槽须要留神以下几个事项:
- 只有 QObject 类及其派生的类能力应用信号和槽的机制
- 在线程间应用信号槽进行通信时,槽参数必须应用元数据类型的参数;
- 如果应用自定义的数据类型,须要在 connect 之前将其注册(qRegisterMetaType)为元数据类型;
- 线程间用信号槽传递参数的话,要加 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>(“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 这个指针自在的调用数据类的成员变量和函数了,当然这里要援用数据类的头文件。