Bahramdun Adil
Bahramdun Adil

Reputation: 6089

QImage is not passing through signal-slots Qt

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

Answers (2)

Bahramdun Adil
Bahramdun Adil

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

G.M.
G.M.

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

Related Questions