I have an app that has to pull frames from video, transform one a little, transform one a lot, and simultaneously display them in GUI. In a worker thread, there's an OpenCV loop:
while(1) {
cv::VideoCapture kalibrowanyPlik;
kalibrowanyPlik.open(kalibracja.toStdString()); //open file from url
int maxFrames = kalibrowanyPlik.get(CV_CAP_PROP_FRAME_COUNT);
for(int i=0; i<maxFrames; i++) //I thought it crashed when finished reading the first time around
{
cv::Mat frame;
cv::Mat gray;
cv::Mat color;
kalibrowanyPlik.read(frame);
cv::cvtColor(frame, gray, CV_BGR2GRAY);
cv::cvtColor(frame, color, CV_BGR2RGB);
QImage image((uchar*)color.data, color.cols, color.rows,QImage::Format_RGB888);
QImage processedImage((uchar*)gray.data, gray.cols, gray.rows,QImage::Format_Indexed8);
emit progressChanged(image, processedImage);
QThread::msleep(50);
}
}
And this is how frames are placed in GUI
void MainWindow::onProgressChagned(QImage image, QImage processedImage) {
QPixmap processed = QPixmap::fromImage(processedImage);
processed = processed.scaledToHeight(379);
ui->labelHsv->clear();
ui->labelHsv->setPixmap(processed);
QPixmap original = QPixmap::fromImage(image); //debug points SIGSEGV here
original = original.scaledToHeight(379);
ui->labelKalibracja->clear();
ui->labelKalibracja->setPixmap(original);
}
The RGB image always crashes, grayscale image never crashes (tested). Why is the RGB image crashing?
edit: I've just discovered that if I change msleep(50) to msleep(100) it executes perfectly. But I don't want that. I need at least 25 frames per second, 10 is not acceptable... why would that cause a SIGSEGV
Standard issue. Problem is memory management!
See my other answer. In comments there is a good link.
So in your code QImage doesn't copy and doesn't take ownership of memory of matrix. And later on when matrix is destroyed and QImage tries access this memory (QImage is copied by creating shallow copy) you have a segfault.
Here is a code form this link (I've tweak it a bit), for some reason this site has some administration issues (some quota exceeded), that is why I'm pasting it here.
inline QImage cvMatToQImage( const cv::Mat &inMat )
{
switch ( inMat.type() )
{
// 8-bit, 4 channel
case CV_8UC4:
{
QImage image( inMat.data, inMat.cols, inMat.rows, inMat.step, QImage::Format_RGB32 );
QImage copy(image);
copy.bits(); //enforce deep copy
return copy;
}
// 8-bit, 3 channel
case CV_8UC3:
{
QImage image( inMat.data, inMat.cols, inMat.rows, inMat.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() )
{
for ( int i = 0; i < 256; ++i )
sColorTable.push_back( qRgb( i, i, i ) );
}
QImage image( inMat.data, inMat.cols, inMat.rows, inMat.step, QImage::Format_Indexed8 );
image.setColorTable( sColorTable );
QImage copy(image);
copy.bits(); //enforce deep copy
return copy;
}
default:
qWarning() << "ASM::cvMatToQImage() - cv::Mat image type not handled in switch:" << inMat.type();
break;
}
return QImage();
}
Your code should utilize this functions like that:
while(1) {
cv::VideoCapture kalibrowanyPlik;
kalibrowanyPlik.open(kalibracja.toStdString()); //open file from url
int maxFrames = kalibrowanyPlik.get(CV_CAP_PROP_FRAME_COUNT);
for(int i=0; i<maxFrames; i++) //I thought it crashed when finished reading the first time around
{
cv::Mat frame;
cv::Mat gray;
kalibrowanyPlik.read(frame);
cv::cvtColor(frame, gray, CV_BGR2GRAY);
QImage image(cvMatToQImage(frame));
QImage processedImage(cvMatToQImage(gray));
emit progressChanged(image, processedImage);
QThread::msleep(10); // this is bad see comments below
}
}
Use of msleep is in 95% cases bad! Remove this loop and create slot which will be invoked by signal from QTimer.
Another solution will be to use a timer :
void ??::timerEvent(QTimerEvent*){
if(kalibrowanssky.isOpened())
cv::Mat frame;
cv::Mat gray;
cv::Mat color;
kalibrowanyPlik.read(frame);
cv::cvtColor(frame, gray, CV_BGR2GRAY);
cv::cvtColor(frame, color, CV_BGR2RGB);
ui->labelHsv->setPixmap(QPixmap::fromImage(Mat2QImage(color)));
ui->labelKalibracja->setPixmap(QPixmap::fromImage(Mat2QImage(gray)));
}
In your main :
cv::VideoCapture kalibrowanyPlik;
startTimer(1000/25); // 25 frames by second
And the function Mat2QImage (I found it here : how to convert an opencv cv::Mat to qimage) :
QImage ??::Mat2QImage(cv::Mat const& src) {
cv::Mat temp;
cvtColor(src, temp,CV_BGR2RGB);
QImage dest((const uchar *) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
dest.bits();
return dest;
}
Related
like the title says I am trying to convert a cv::mat to a QImage. What I am doing is using the equalizeHist() function on the mat and then converting it to a QImage to display in widget window in Qt. I know the mat works and loads the image correctly because the equalized image will show in the new window with imshow(), however when converting this mat to a QImage, I can not get it to display in the window. I believe the problem is with the conversion from the mat to QImage but cant find the issue. Below is a part of my code snippet.
Mat image2= imread(directoryImage1.toStdString(),0);
//cv::cvtColor(image2,image2,COLOR_BGR2GRAY);
Mat histEquImg;
equalizeHist(image2,histEquImg);
imshow("Histogram Equalized Image 2", histEquImg);
//QImage img=QImage((uchar*) histEquImg.data, histEquImg.cols, histEquImg.rows, histEquImg.step, QImage::Format_ARGB32);
imageObject= new QImage((uchar*) histEquImg.data, histEquImg.cols, histEquImg.rows, histEquImg.step, QImage::Format_RGB888);
image = QPixmap::fromImage(*imageObject);
scene=new QGraphicsScene(this); //create a frame for image 2
scene->addPixmap(image); //put image 1 inside of the frame
ui->graphicsView_4->setScene(scene); //put the frame, which contains image 3, to the GUI
ui->graphicsView_4->fitInView(scene->sceneRect(),Qt::KeepAspectRatio); //keep the dimension ratio of image 3
No errors occur and the program doesnt crash.
Thanks in advance.
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.
I have two functions that pass images using pointers
funct 1: Read a gray image from file, do image processing. Convert the processed image to color (3-channel). Wrapping it in a pointer.
funct 2: have an image pointer as input. Wrap it in a cv mat and show it. Do some other things.
funct 1:
cv::Mat imIn = cv::Mat(height, width,CV_16UC1);
cv::Mat outImage;
// read image
std::ifstream ifs{ imagesPathVec, std::ios::in | std::ios::binary };
if ( ifs.is_open() )
{
ifs.read( reinterpret_cast<char *>( imIn.data ), imIn.total() * imIn.elemSize() );
ifs.close();
}
imIn.convertTo(outImage, CV_32F);
//... some image processing applied to outImage
outImage.convertTo(outImage, CV_8UC1);
//wrapping the pointer outStreamBuffer in a cv::Mat.
cv::Mat outStream(height, width, CV_8UC3, static_cast<uint8_t*>(*outStreamBuffer));
// Try two methods to convert to color images.
//Method 1.
std::vector<cv::Mat> images(3);
images.at(0) = outImage;
images.at(1) = outImage;
images.at(2) = outImage;
cv::merge(images, outStream);
//Method 2.
cv::cvtColor(outImage, outStream, CV_GRAY2RGB);
Function 2
//wrap the incomming pointer inStreamBuffer in a Mat
cv::Mat inImage = cv::Mat(height, width, CV_8UC3, static_cast<uint8_t*>(*inStreamBuffer), width*3);
cv::imshow("m_inImage ", inImage);
cv::waitKey(10);
Since it is a gray image with 3 channels. I try to convert it to color by calling cvtColor() before the call to cv::imshow()
cv::cvtColor(inImage, inImage,CV_RGB2BGR);
but the results were the same.
The displayed image is
I would appreciate if you can help me to show the image correctly. Also help me to underestand what it is going on. Why opencv is not recognizing its own interleaved image?
I am using a camera to get a imgTomo1 image which is a cv::Mat object. It is a CV_32F image.
I am trying to show it on a QLabel using a QPixmap.
Here is my code :
cv::Mat imgTomo;
imgTomo1.convertTo(imgTomo,CV_8UC1);
static QVector<QRgb> sColorTable;
// only create our color table the first time
if ( sColorTable.isEmpty() )
sColorTable.resize( 256 );
for ( int i = 0; i < 256; ++i )
{
sColorTable[i] = qRgb( i, i, >i );
}
}
QImage image( imgTomo.data,
imgTomo.cols, imgTomo.rows,
static_cast<int>(imgTomo.step),
QImage::Format_Indexed8);
image.setColorTable( sColorTable );
_afficheImg->setPixmap(QPixmap::fromImage(image));
Unfortunately, the image displayed remain black.
I am a bit lost in the format as i'm new to OpenCV.
I think the conversion should work so i don't really know what i am mising.
EDIT : i have deleted the fllowing line :
imgTomo1.convertTo(imgTomo,CV_8UC1);
It resulted in a loss of information.
Now i dont have a black screen anymore but some "snow" (pixel that are witching form 1 to 0 very quicly i guess) and i can't really see what my camera is suposed to show.
Thank you for your answers,
Grégoire
I am not exactly sure what's wrong with your code but I used the following code to convert a cv::Mat image into a QImage.
if (frame.channels()== 3){
cv::cvtColor(frame, RGBframe, CV_BGR2RGB);
img = QImage((const unsigned char*)(RGBframe.data),
RGBframe.cols,RGBframe.rows,QImage::Format_RGB888);
}
else
{
img = QImage((const unsigned char*)(frame.data),
frame.cols,frame.rows,QImage::Format_Indexed8);
}
You can even check out the follwing link for more information on how to convert Mat image to QImage.
1.Convert CV_8U type
Mat Temp;
CurrentMat.convertTo(Temp, CV_8U);
2.Check the channel number:
int theType = Temp.type();
int channel_number = (theType / 8) + 1;
if(channel_number == 4){
cvtColor(Temp, Temp, CV_BGRA2BGR);
}
3.You can convert Mat type to QImage using this code:
QImage putImage(const 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();
}
else
{
qDebug() << "ERROR: Mat could not be converted to QImage.";
return QImage();
}
}
void doCorrectIntensityVariation(Mat& image)
{
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(19,19));
Mat closed;
morphologyEx(image, closed, MORPH_CLOSE, kernel);
image.convertTo(image, CV_32F); // divide requires floating-point
divide(image, closed, image, 1, CV_32F);
normalize(image, image, 0, 255, NORM_MINMAX);
image.convertTo(image, CV_8UC1); // convert back to unsigned int
}
inline void correctIntensityVariation(IplImage *img)
{
//Mat imgMat(img); copy the img
Mat imgMat;
imgMat = img; //no copy is done, imgMat is a header of img
doCorrectIntensityVariation(imgMat);
imshow("gamma corrected",imgMat); cvWaitKey(0);
}
When I call
cvShowImage ("normal", n_im); cvWaitKey (0);
correctIntensityVariation(n_im);//here n_im is IplImage*
cvShowImage ("After processed", n_im); cvWaitKey (0);
// here I require n_im for further processing
I wanted "After processed" to be same as that of "gamma corrected" but what I found "After processed" was not the same as that of "gamma corrected" but same as that of "normal" . Why?? What is going wrong??
A very simple wrapper should do the job
Cheetsheet of openCV
I rarely use the old api, because Mat are much more easier to deal with, and
they do not have performance penalty when compare with the old c api.Like the openCV
tutorial page say The main downside of the C++ interface is that many embedded development systems at the moment support only C. Therefore, unless you are targeting embedded platforms, there’s no point to using the old methods (unless you’re a masochist programmer and you’re asking for trouble).
openCV tutorial
cv::Mat to Ipl
Ipl to cv::Mat and Mat to Ipl
IplImage* pImg = cvLoadImage(“lena.jpg”);
cv::Mat img(pImg,0); //transform Ipl to Mat, 0 means do not copy
IplImage qImg; //not pointer, it is impossible to overload the operator of raw pointer
qImg = IplImage(img); //transform Mat to Ipl
Edit : I did a mistake earlier, if the Mat would be reallocated in the function, you need
to copy or try to steal the resource(I don't know how to do it yet) from the Mat.
Copy the data
void doCorrectIntensityVariation(cv::Mat& image)
{
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(19,19));
cv::Mat closed;
cv::morphologyEx(image, closed, cv::MORPH_CLOSE, kernel);
image.convertTo(image, CV_32F); // divide requires floating-point
cv::divide(image, closed, image, 1, CV_32F);
cv::normalize(image, image, 0, 255, cv::NORM_MINMAX);
image.convertTo(image, CV_8UC1); // convert back to unsigned int
}
//don't need to change the name of the function, the compiler treat
//these as different function in c++
void doCorrectIntensityVariation(IplImage **img)
{
cv::Mat imgMat;
imgMat = *img; //no copy is done, imgMat is a header of img
doCorrectIntensityVariation(imgMat);
IplImage* old = *img;
IplImage src = imgMat;
*img = cvCloneImage(&src);
cvReleaseImage(&old);
}
int main()
{
std::string const name = "onebit_31.png";
cv::Mat mat = cv::imread(name);
if(mat.data){
doCorrectIntensityVariation(mat);
cv::imshow("gamma corrected mat",mat);
cv::waitKey();
}
IplImage* templat = cvLoadImage(name.c_str(), 1);
if(templat){
doCorrectIntensityVariation(&templat);
cvShowImage("mainWin", templat);
// wait for a key
cvWaitKey(0);
cvReleaseImage(&templat);
}
return 0;
}
you could write a small function to alleviate the chores
void copy_mat_Ipl(cv::Mat const &src, IplImage **dst)
{
IplImage* old = *dst;
IplImage temp_src = src;
*dst = cvCloneImage(&temp_src);
cvReleaseImage(&old);
}
and call it in the function
void doCorrectIntensityVariation(IplImage **img)
{
cv::Mat imgMat;
imgMat = *img; //no copy is done, imgMat is a header of img
doCorrectIntensityVariation(imgMat);
copy_mat_to_Ipl(imgMat, img);
}
I will post how to "steal" the resource from Mat rather than copy after
I figure out a solid solution.Anyone know how to do it?
I have searched on the internet but I cannot find a method of converting a QImage(or QPixmap) to a OpenCV Mat. How would I do this?.
Any help is appreciated.
If the QImage will still exist, and you just need to perform a quick operation on it then you can construct a cv::Mat using the QImage memory:
cv::Mat mat(image.height(), image.width(), CV_8UC3, (cv::Scalar*)image.scanLine(0));
This assumes that the QImage is 3-channels, ie RGB888.
If the QImage is going away then you need to copy the data, see Qimage to cv::Mat convertion strange behaviour.
If QImage is Format_ARGB32_Premultiplied (the preferred format) then you will need to convert each pixel to OpenCV's BGR layout. The cv::cvtcolor() function can convert ARGB to RGB in the latest versions.
Or you can use QImage::convertToFormat() to convert to RGB before copying the data.
One year after you issued this question there've been great answers on the internet:
Convert between cv::mat and Qimage correctly
Converting Between cv::Mat and QImage or QPixmap
But the way I see it, if you're working with Qt and OpenCV at the same time then type QImage is probably just for displaying, that case you might want to use QPixmap since it's optimized for displaying. So this is what I do:
Load image as cv::Mat, if you'd like to display the image, convert to QPixmap using the non-copy method introduced in the second article.
Do your image processing in cv::Mat.
Any time during the workflow you can call upon something like Mat2QPixmap() to get realtime result.
Never convert QPixmap to cv::Mat, there's no sense doing it considering the purpose of each type.
The answer to this with Qt 5.11 (and probably some earlier versions):
cv::Mat mat(image.height(), image.width(),CV_8UC3, image.bits());
// image.scanline() does not exist,
//and height/width is interchanged for a matrix
Again the QImage is assumed to be RGB888 (ie QImage::Format_RGB888)
My attempt in OpenCV 3.1+ style code:
void qimage_to_mat(const QImage& image, cv::OutputArray out) {
switch(image.format()) {
case QImage::Format_Invalid:
{
cv::Mat empty;
empty.copyTo(out);
break;
}
case QImage::Format_RGB32:
{
cv::Mat view(image.height(),image.width(),CV_8UC4,(void *)image.constBits(),image.bytesPerLine());
view.copyTo(out);
break;
}
case QImage::Format_RGB888:
{
cv::Mat view(image.height(),image.width(),CV_8UC3,(void *)image.constBits(),image.bytesPerLine());
cvtColor(view, out, cv::COLOR_RGB2BGR);
break;
}
default:
{
QImage conv = image.convertToFormat(QImage::Format_ARGB32);
cv::Mat view(conv.height(),conv.width(),CV_8UC4,(void *)conv.constBits(),conv.bytesPerLine());
view.copyTo(out);
break;
}
}
}
void mat_to_qimage(cv::InputArray image, QImage& out)
{
switch(image.type())
{
case CV_8UC4:
{
cv::Mat view(image.getMat());
QImage view2(view.data, view.cols, view.rows, view.step[0], QImage::Format_ARGB32);
out = view2.copy();
break;
}
case CV_8UC3:
{
cv::Mat mat;
cvtColor(image, mat, cv::COLOR_BGR2BGRA); //COLOR_BGR2RGB doesn't behave so use RGBA
QImage view(mat.data, mat.cols, mat.rows, mat.step[0], QImage::Format_ARGB32);
out = view.copy();
break;
}
case CV_8UC1:
{
cv::Mat mat;
cvtColor(image, mat, cv::COLOR_GRAY2BGRA);
QImage view(mat.data, mat.cols, mat.rows, mat.step[0], QImage::Format_ARGB32);
out = view.copy();
break;
}
default:
{
throw invalid_argument("Image format not supported");
break;
}
}
}
cv::Mat to_cvmat(QImage img)
{
img = img.convertToFormat(QImage::Format_RGB888, Qt::ColorOnly).rgbSwapped();
return cv::Mat(img.height(), img.width(), CV_8UC3, img.bits(), img.bytesPerLine()).clone();
}