Cv::Mat to Qimage and Back

cv::Mat to QImage and back

Code looks fine with one exception.

Memory management. cv::Mat doesn't work like QImage in this mater. Remember that QImage is using copy on write mechanism and shares memory for each copy.
cv::Mat also shares memory but it doesn't do copy on write (I'm also new with open cv (2 weeks) so I can't explain yet exactly how it works but I've stumbled on some crushes because of that)!

Another thing is that when you are creating QImage from memory image is using this memory and doesn't take ownership of it.

Final outcome is that on Linux and Qt5 your code is crashes because of problems with memory management. On your screen shot you can see at the top of second window that something strange is going on and you see some memory trash.

So I've corrected your conversion functions it works perfectly:

QImage Mat2QImage(cv::Mat const& src)
{
cv::Mat temp; // make the same cv::Mat
cvtColor(src, temp,CV_BGR2RGB); // cvtColor Makes a copt, that what i need
QImage dest((const uchar *) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
dest.bits(); // enforce deep copy, see documentation
// of QImage::QImage ( const uchar * data, int width, int height, Format format )
return dest;
}

cv::Mat QImage2Mat(QImage const& src)
{
cv::Mat tmp(src.height(),src.width(),CV_8UC3,(uchar*)src.bits(),src.bytesPerLine());
cv::Mat result; // deep copy just in case (my lack of knowledge with open cv)
cvtColor(tmp, result,CV_BGR2RGB);
return result;
}

So we both have to do a reading about memory management in open-CV :).

OFFTOPIC:

Best way to include openCV in qt projects on Linux is to add to pro file something like:

# add open CV
unix {
CONFIG += link_pkgconfig
PKGCONFIG += opencv
}

You will be free of path problems when moving code to another machine.

Converting cv::mat to QImage

Your problem is the conversion of the QImage to cv::Mat, when using the flag 0 in cv::imread implies the reading is grayscale, and you are using the conversion with the format QImage::Format_RGB888. I use the following function to make the conversion of cv::Mat to QImage:

static QImage MatToQImage(const cv::Mat& mat)
{
// 8-bits unsigned, NO. OF CHANNELS=1
if(mat.type()==CV_8UC1)
{
// Set the color table (used to translate colour indexes to qRgb values)
QVector<QRgb> colorTable;
for (int i=0; i<256; i++)
colorTable.push_back(qRgb(i,i,i));
// Copy input Mat
const uchar *qImageBuffer = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage img(qImageBuffer, mat.cols, mat.rows, mat.step, QImage::Format_Indexed8);
img.setColorTable(colorTable);
return img;
}
// 8-bits unsigned, NO. OF CHANNELS=3
if(mat.type()==CV_8UC3)
{
// Copy input Mat
const uchar *qImageBuffer = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage img(qImageBuffer, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
return img.rgbSwapped();
}
return QImage();
}

After that I see that you have misconceptions of how QGraphicsView and QGraphicsScene work when commenting: put the frame, which contains image 3, to the GUI, with ui->graphicsView_4->setScene(scene); you are not setting a frame but a scene, and the scene should only be set once and preferably in the constructor.

// constructor
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);

So when you want to load the image just use the scene:

cv::Mat image= cv::imread(filename.toStdString(), CV_LOAD_IMAGE_GRAYSCALE);

cv::Mat histEquImg;
equalizeHist(image, histEquImg);

QImage qimage = MatToQImage(histEquImg);
QPixmap pixmap = QPixmap::fromImage(qimage);
scene->addPixmap(pixmap);
ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);


The complete example can be found in the following link.

cv::Mat to QImage conversion

Your solution to the problem is not efficient, in particular it is less efficient then the code I posted on the other question you link to.

Your problem is that you have to convert from grayscale to color, or RGBA. As soon as you need this conversation, naturally a copy of the data is needed.

My solution does the conversion between grayscale and color, as well as between cv::Mat and QImage at the same time. That's why it is the most efficient you can get.

In your solution, you first try to convert and then want to build QImage around OpenCV data directly to spare a second copy. But, the data you point to is temporary. As soon as you leave the function, the cv::Mat free's its associated memory and that's why it is not valid anymore also within the QImage. You could manually increase the reference counter of the cv::Mat beforehand, but that opens the door for a memory leak afterwards.

In the end, you attempt a dirty solution to a problem better solved in a clean fashion.

from cvMat to QImage

as @SpamBot mentioned, the data is freed when Mat goes out of scope. try deep copying the data:

QImage mat2qimage(const Mat& mat) 
{
Mat rgb;
cvtColor(mat, rgb, CV_BGR2RGB);
return QImage((const unsigned char*)(rgb.data), rgb.cols, rgb.rows, QImage::Format_RGB888).copy();
}

Qt - Transform cv::Mat to QImage in worker thread crashes

It doesn't have a chance of working, since the QImage wraps transient data that then is deallocated by cvMat. At the minimum you should emit the copy of the image (literally qImage.copy(). Ideally, you'd wrap the cvMat lifetime management in QImage's deleter, so that the copy is not necessary, and the matrix gets destroyed along with the image that wraps it. Since format conversions are usually needed between cv::Mat and QImage, it's probably best to perform the conversion between the source cv::Mat and another cv::Mat that wraps the QImage-owned memory. That solution avoids memory reallocations, since the QImage can be retained in the class performing the conversion. It is then also comaptible with Qt 4, where QImage doesn't support deleters.

See this answer for a complete example of an OpenCV video capture Qt-based widget viewer, and this answer for a complete example of multi-format conversion from cv::Mat to QImage.

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

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"


Related Topics



Leave a reply



Submit