Search code examples
qtopencvqimageqpixmap

How to convert a cv::Mat to QImage or QPixmap?


I've tried to look around and tried everything I've found, but haven't found a solution for this problem.

I'm trying to update an image in a QT application by button click.

In the constructor I've managed to show a image:

    cv::Mat temp = cv::Mat(*this->cv_size,CV_8UC3);
    temp = cv::Scalar(0,255,155);
    ui->image->setPixmap(QPixmap::fromImage( Mat2QImage(temp)));

And then I've created a button and linked this function to it

void UIQT::refreshImage(){
    cv::Mat temp = cv::Mat(*this->cv_size,CV_8UC3);
    temp = cv::Scalar(0,255,0);
    ui->image->setPixmap(QPixmap::fromImage( Mat2QImage(temp)));
    std::cout << "refreshed" << std::endl;
}

Here is the function:

QImage UIQT::Mat2QImage(cv::Mat const& src) {
    cv::Mat temp(src.cols,src.rows,src.type());
    cvtColor(src, temp,CV_BGR2RGB);
    QImage dest= QImage((uchar*) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);

    return dest;
}

But when I hit the button the whole image turns white. Anyone got an idea?


Solution

  • Your code seems to be hopelessly convoluted and you do a lot of unnecessary things. There's no reason to have cv_size a pointer. You should just use an instance of cv::Size. Your Mat2QImage returns a QImage with a dangling pointer to its data.

    The code below is a complete, tested example. It maintains the otherwise unnecessary Ui namespace etc. just to make it similar to your existing code base.

    Some publicized Mat2QImage-style methods are broken as they return a QImage that uses the mat's data without retaining a reference to it. If the source mat ceases to exist, the image references a dangling pointer, and anything can happen. That was your problem. The version below is correct in this respect.

    #include <QApplication>
    #include <QBasicTimer>
    #include <QImage>
    #include <QPixmap>
    #include <QGridLayout>
    #include <QLabel>
    #include <opencv2/opencv.hpp>
    
    namespace Ui { struct UIQT {
      QLabel * image;
      void setupUi(QWidget * w) {
        QGridLayout * layout = new QGridLayout(w);
        layout->addWidget((image = new QLabel));
      }
    }; }
    
    class UIQT : public QWidget {
      Q_OBJECT
      Ui::UIQT ui;
      QBasicTimer m_timer;
      cv::Size m_size;
      void timerEvent(QTimerEvent *);
    public:
      UIQT(QWidget * parent = 0);
      ~UIQT();
      Q_SLOT void refreshImage();
    };
    
    void matDeleter(void* mat) { delete static_cast<cv::Mat*>(mat); }
    
    static QImage imageFromMat(cv::Mat const& src) {
      Q_ASSERT(src.type() == CV_8UC3);
      cv::Mat * mat = new cv::Mat(src.cols,src.rows,src.type());
      cvtColor(src, *mat, CV_BGR2RGB);
      return QImage((uchar*)mat->data, mat->cols, mat->rows, mat->step,
                    QImage::Format_RGB888, &matDeleter, mat);
    }
    
    static cv::Scalar randomScalar() {
      static cv::RNG rng(12345);
      return cv::Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255));
    }
    
    static QPixmap pixmapFromMat(const cv::Mat & src) {
      QImage image(imageFromMat(src));
      return QPixmap::fromImage(image);
    }
    
    UIQT::UIQT(QWidget * parent) :
      QWidget(parent),
      m_size(100, 100)
    {
      ui.setupUi(this);
      m_timer.start(500, this);
      refreshImage();
    }
    
    UIQT::~UIQT() {}
    
    void UIQT::timerEvent(QTimerEvent * ev) {
      if (ev->timerId() != m_timer.timerId()) return;
      refreshImage();
    }
    
    void UIQT::refreshImage() {
      cv::Mat mat(m_size, CV_8UC3, randomScalar());
      ui.image->setPixmap(pixmapFromMat(mat));
    }
    
    int main(int argc, char *argv[]) {
      QApplication app(argc, argv);
      UIQT w;
      w.show();
      return app.exec();
    }
    
    #include "main.moc"