Reputation: 6089
I am building an application which gets the image from an IP camera using OpenCV in one thread, then after processing it converts the Mat
to QImage
, then emits the QImage
to the signal, and then the image will be received in the GUI thread by the corresponding slots.
signal declares in the processing image thread class:
void sendImage(const QImage &frame);
slot declares in the GUI thread class:
void onGetImage(const QImage &img);
but after that when I show the image on QLabel
or saving the image to the disk, the program crashes.
I am a little new to C++
, so is there any problem with the passing reference of the QImage
object? If yes, then what is the correct way to pass Objects between threads without any copying (reallocating memory) overhead? Because I want to prevent even a small overhead in a long time running application.
Update: Sometimes run for some while, and then crashes, sometimes it crashes even in the first frame.
Thanks!
Code: which process the image
class ImageProcess
{
public:
ImageProcess(std::function<void(cv::Mat&)> imgCallback):imgCallback(imgCallback){}
public:
void start()
{
while(start) // loop till start become false
{
videpCapture.read(frame);
// .... some process on image
imgCallback(frame)
}
}
private:
cv::Mat frame;
std::function<void(cv::Mat&)> imgCallback;
};
code: which communicate between image process unit and the GUI
class WorkerThread: public QObject
{
Q_OBJECT
public:
WorkerThread()
{
imgProcess = new ImageProcess(std::bind(.....)); // bind the callback
}
public slots:
void onStart()
{
if(imProcessThread != nullptr)
{
imgProcess.start = false; // stop if running
imProcessThread->quit(); // quit the thread
imProcessThread->wait(); // wait to finish
delete imProcessThread; // delete the pointer
}
imProcessThread = new ImageProcessThread(imgProcess); // create
imProcessThread->start(); // start thread
}
signals:
void sendMessage(const QString& msg, const int& code);
private:
ImageProcess *imgProcess;
void frameCallback(const cv::Mat& frame); // frame callback
{
emit sendImage(matToQImage(frame)); // send the image to UI
}
// Mat to QImage converter
QImage matToQImage(const cv::Mat &mat)
{
cv::Mat rgbMat;
if(mat.channels() == 1) { // if grayscale image
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step,
QImage::Format_Indexed8);// declare and return a QImage
} else if(mat.channels() == 3) { // if 3 channel color image
cv::cvtColor(mat, rgbMat, CV_BGR2RGB); // invert BGR to RGB
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows,
(int)mat.step, QImage::Format_RGB888);// declare and return a QImage
}
return QImage();
}
// image process thread
class ImageProcessThread : public QThread
{
public:
ImageProcessThread(ImageProcess *ip) : ip(ip){}
protected:
void run()
{
ip->start();
}
private:
ImageProcess *ip;
} *imProcessThread = nullptr;
};
code: UI
class Camera : public QWidget
{
Q_OBJECT
public:
explicit Camera(QWidget *parent = 0)
{
wt = new WorkerThread;
thread = new QThread;
//conncet the signal-slots
connect(wt, &WorkerThread::sendImage, this, &Camera::onGetImage);
connect(ui->btStart, &QPushButton::clicked, wt, &WorkerThread::start);
}
private slots:
void onGetImage(const QImage &img)
{
// set the image to QLabel
}
private:
WorkerThread *wt;
QThread *thread;
};
wt.moveToThread(thread);
thread->start();
So what can be a goog design for this purpose?
Upvotes: 1
Views: 1790
Reputation: 6089
By the accepting @G.M.'s answer, I want to add some more details what I have discovered in this problem to help anyone other may have the same question.
The problem was when converting cv::Mat
to QImage
, the image data was not copied to the QImage, so cv:Mat
and QImage
was sharing the same data. After because there are two threads running simultaneously, then when the QImage has been created then when it was sending to the UI
thread, at this time, the processing image thread (background worker thread) has already cleaned up the previous data for the next frame before the UI
thread has finished the refreshing image job. So the program has crashed.
Upvotes: 0
Reputation: 12899
Since you haven't provided an mcve I'm guessing but, your implementation of matToQImage
looks slightly suspect. You have...
QImage matToQImage (const cv::Mat &mat)
{
cv::Mat rgbMat;
if (mat.channels() == 1) {
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8);
} else if (mat.channels() == 3) {
cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888);
}
return QImage();
}
But, from the documentation, the QImage
constructor you're using to convert from cv::Mat
...
Constructs an image with the given width, height and format, that uses an existing memory buffer, data. The width and height must be specified in pixels. bytesPerLine specifies the number of bytes per line (stride).
The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer [my emphasis]. The image does not delete the buffer at destruction. You can provide a function pointer cleanupFunction along with an extra pointer cleanupInfo that will be called when the last copy is destroyed.
So the QImage
returned is probably referencing a dangling pointer after the cv::Mat
destructor has been invoked.
If that is the problem then the easiest solution would be to make a deep copy of the QImage
and return that...
QImage matToQImage (const cv::Mat &mat)
{
cv::Mat rgbMat;
if (mat.channels() == 1) {
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8).copy();
} else if (mat.channels() == 3) {
cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888).copy();
}
return QImage();
}
Upvotes: 3