1、 Background introduction
In the use of QT During project development , Multithreading is often used , For example, an image acquisition thread , Image processing is a thread 、 Data communication is a thread . There will be a need for data sharing in these different threads ,Qt There are three main ways to share data between threads :
1. Using Shared memory ; That is, variables that both threads can share ( Global variables ), So both threads can access and modify variables , So as to achieve the goal of congratulations ;
2. Use the signal slot mechanism , Passing data from one thread to another
3. Share class pointers to access variables and functions of different classes ;
The third is my own common method , Let me summarize and record below .
2、 Methods to introduce
The first method , Use global variables or global functions , Call... In another class or thread , This is a common method in all kinds of programming languages , But global variables take up memory for a long time , Affect program space usage , And the modification of global variables affects the whole program , The security of the program cannot be guaranteed , Generally, try not to use global variables or functions , This method is not introduced .
2.1 Signal slot for data communication
The function of signal slot is QT Unique function , The following points should be paid attention to when using the signal slot :
- Only QObject Class and its derived classes can use the mechanism of signals and slots
- When the signal slot is used for communication between processes , Slot parameters must use metadata type parameters ;
- If you use a custom data type , Need to be in connect Register it before (qRegisterMetaType) Is metadata type ;
- If parameters are passed between threads through signal slots , To add const, because const The literal constant exists in the constant area , The life cycle is as long as a program . This can be avoided slot When calling, the running time of the parameter has expired, causing the reference to be invalid ;
I use one here balser The camera thread uses the signal slot to send pictures to UI Thread displayed Demo To show the data communication between processes through the signal slot .
/* Image acquisition thread header file */
/*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 Get result pointer
CImageFormatConverter m_formatConverter;//Basler Picture format conversion class
CPylonImage pylonImage; //Basler Image format
QImage m_image; //Qt Image format
QPixmap m_pix;
String_t m_prefix;
bool m_stop;
bool m_init;
bool m_save;
int m_grab; // Get image policy 0 Indicates continuous acquisition ,1 Means to obtain a single frame
int m_num_one;
int m_num_continue;
signals:
// issue UI Thread signals
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())
{
// format conversion
cv::Mat MatImg = Result2Mat(ptrGrabResult);
// qDebug() << " Conversion success " << endl;
// Send a signal
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)
{
//// format conversion
m_formatConverter.Convert(pylonImage, ptrGrabResult);
uchar * din = (uchar *)(pylonImage.GetBuffer()); // Data pointer
cv::Mat cvImage = cv::Mat(ptrGrabResult->GetHeight(),ptrGrabResult->GetWidth(),CV_8UC1,din).clone();
return cvImage;
}
Signals emitted in the acquisition thread , stay UI Threads must have corresponding slot functions .
/* 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:
// Show slot function of image
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()<<" Time consuming "<<ElapsedTimer.elapsed()<<" millisecond ";
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();
}
Here is the first signal slot method , adopt emit ThreadPic(MatImg) Sending signal , stay UI Threads pass through slot functions Thread_Img(cv::Mat img) To receive Mat Type of image , here Mat The type is not Qt Metadata , So use qRegisterMetaType<Mat>("Mat") To register .
2.2 Share class pointers to implement synchronous calls
If I create a data class to store the data during image processing , When collecting images, it is necessary to put the collected images into the data class ,UI The thread will also set different variable parameters and put them into the data class , When the processing thread wants to use data, it needs to go to the data class to read the data , So many classes read and write at the same time , How to realize synchronous sharing , It needs to be here UI Threads share pointers after creating classes .
m_ImgProcessObj = new ImgProcessThread();
// Initialize data class
currentData = new MyData();
m_Product = new productManager(this);
m_Product->GetMyDataPoint(currentData);
m_ImgProcessObj->GetMyDataPoint(currentData);
Use here GetMyDataPoint This function shares the data class pointer with the pointer of other classes that need to call the data , In fact, two pointers point to the same memory address .
void ImgProcessThread::GetMyDataPoint(MyData *DPoint)
{
DataPoint = DPoint;
}
So in the image processing class, you can use DataPoint This pointer can freely call the member variables and functions of the data class , Of course, the header file of the data class should be referenced here .