I'm currently building a Qt application that using some camera.
In this application the uses capture images, and then they are automatically saved in a specific folder. Everything works great.
Now, when the "library" button is clicked, I want to read all the images (JPEG files) and display all the images that were taken one by one in a QLabel.
I couldn't find any tutorials for this, only found tutorials and uses the argv argument which is no good for me, because in my application the user may capture images and then display them in the same run.
How can read the files list and display it?
Thank you very much :)
If you have a single QLabel then you have to join the images together a single one. I find easier to display a list of QLabels:
auto layout = new QVBoxLayout();
Q_FOREACH (auto imageName, listOfImages) {
QPixmap pixmap(dirPath + "/" + imageName);
if (!pixmap.isNull()) {
auto label = new QLabel();
label->setPixmap(pixmap);
layout->addWidget(label);
}
}
a_wdiget_where_to_show_images->setLayout(layout);
The last line will depend on when do you want to place the labels. I suggest some widget with a scroll bar.
Now, you want to read all the images from a directory (the listOfImages variable above). If you don't have it:
const auto listOfImages = QDir(dirPath).entryList(QStringList("*.jpg"), QDir::Files);
You may have layout problems if your images are too big. In that case you should scale them if they are bigger than a given size. Take a look at QPixmap::scaled or QPixmap::scaledToWidth. Also, if image quality is important, specify Qt::SmoothTransformation as the transformation mode.
You can use opencv library to read all images in a directory.
vector<String> filenames; // notice here that we are using the Opencv's embedded "String" class
String folder = "Deri-45x45/"; // again we are using the Opencv's embedded "String" class
float sayi = 0;
glob(folder, filenames); // new function that does the job ;-)
float toplam = 0;
for (size_t i = 0; i < filenames.size(); ++i)
{
Mat img = imread(filenames[i],0);
//Display img in QLabel
QImage imgIn = putImage(img);
imgIn = imgIn.scaled(ui->label_15->width(), ui->label_15->height(),Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
ui->label_15->setPixmap(QPixmap::fromImage(imgIn));
}
In order to convert Mat type to QImage, we use putImage function:
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();
}
}
Related
I'm trying to change the brightness of an image by coverting it from BGR to LAB and changing the L parameter to L+brightness. It works to change the brightness but the output image is blue , why?
void MainWindow::BrightnessSlider(cv::Mat image)
{
cv::Mat image2;
cv::cvtColor(image,image2,cv::COLOR_BGR2Lab);
for (int i=0; i < image2.rows; i++)
{
for (int j=0; j < image2.cols; j++)
{
image2.at<cv::Vec3b>(i,j)[0] = cv::saturate_cast<uchar>(image2.at<cv::Vec3b>(i,j)[0] + brightness);
}
}
cv::cvtColor(image2,image2,cv::COLOR_Lab2BGR);
QImage imageupdate= QImage((const unsigned char*)(image2.data), image2.cols,image2.rows,QImage::Format_RGB888);
int w = ui->label->width();
int h =ui-> label->height();
ui->label->setPixmap(QPixmap::fromImage(imageupdate.scaled(w,h,Qt::KeepAspectRatio)));
}
The main problem here is that 3-channel color images in OpenCV use BGR memory layout, while in Qt they use RGB memory layout. That's why your image shown in QLabel looks "blue".
To fix the memory layout problem, you should change cv::COLOR_Lab2BGR to cv::COLOR_Lab2RGB in the second cv::cvtColor():
cv::cvtColor(image2, image2, cv::COLOR_Lab2RGB);
Or append .rgbSwapped() to imageupdate (note that imageupdate will not share memory block with image2):
QImage imageupdate = QImage((const unsigned char*)(image2.data),
image2.cols, image2.rows, QImage::Format_RGB888).rgbSwapped();
BTW, you can just use Mat::operator+(const Scalar&) to change the value for all pixels, the color conversion and for-loops are unnecessary:
cv::Mat image2 = image + cv::Scalar::all(brightness);
// convert BGR to RGB if you don't want to allocate additional memory
// for imageupdate with QImage::rgbSwapped():
cv::cvtColor(image2, image2, cv::COLOR_BGR2RGB);
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 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();
}
}
I am wondering how would I convert the OpenCV C++ standard cv::Mat type to QImage. I have been searching around, but have no luck. I have found some code that converts the IPlimage to QImage, but that is not what I want. Thanks.
Michal Kottman's answer is valid and give expected result for some images but it'll fail on some cases. Here is a solution i found to that problem.
QImage imgIn= QImage((uchar*) img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
Difference is adding img.step part. qt won't complain without it but some images won't show properly without it. Hope this will help.
To convert from cv::Mat to QImage, you could try to use the QImage(uchar * data, int width, int height, Format format) constructor as follows (mat is a cv::Mat) :
QImage img((uchar*)mat.data, mat.cols, mat.rows, QImage::Format_RGB32);
It is more efficient than manually converting the pixels to the QImage, but you have to keep the original cv::Mat image in memory. It can be easily converted to a QPixmap and displayed using a QLabel:
QPixmap pixmap = QPixmap::fromImage(img);
myLabel.setPixmap(pixmap);
Update
Because OpenCV uses BGR order by default, you should first use cvtColor(src, dst, CV_BGR2RGB) to get an image layout that Qt understands.
Update 2:
If the image you are trying to show has nonstandard stride (when it is non-continuous, submatrix), the image may appeard distorted. In this case, it is better to explicitly specify the stride using cv::Mat::step1():
QImage img((uchar*)mat.data, mat.cols, mat.rows, mat.step1(), QImage::Format_RGB32);
Here is code for 24bit RGB and grayscale floating point. Easily adjustable for other types. It is as efficient as it gets.
QImage Mat2QImage(const cv::Mat3b &src) {
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
for (int y = 0; y < src.rows; ++y) {
const cv::Vec3b *srcrow = src[y];
QRgb *destrow = (QRgb*)dest.scanLine(y);
for (int x = 0; x < src.cols; ++x) {
destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255);
}
}
return dest;
}
QImage Mat2QImage(const cv::Mat_<double> &src)
{
double scale = 255.0;
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
for (int y = 0; y < src.rows; ++y) {
const double *srcrow = src[y];
QRgb *destrow = (QRgb*)dest.scanLine(y);
for (int x = 0; x < src.cols; ++x) {
unsigned int color = srcrow[x] * scale;
destrow[x] = qRgba(color, color, color, 255);
}
}
return dest;
}
OpenCV loads images into a Mat in Blue-Green-Red (BGR) format by default, while QImage expects RGB. This means that if you convert a Mat to QImage, the blue and red channels will be swapped. To fix this, before constructing the QImage, you need to change the BRG format of your Mat to RGB, via the cvtColor method using argument CV_BGR2RGB, like so:
Mat mat = imread("path/to/image.jpg");
cvtColor(mat, mat, CV_BGR2RGB);
QImage image(mat.data, mat.cols, mat.rows, QImage::Format_RGB888);
Alternatively, use rgbSwapped() on the QImage
QImage image = QImage(mat.data, mat.cols, mat.rows, QImage::Format_RGB888).rgbSwapped());
Mat opencv_image = imread("fruits.jpg", CV_LOAD_IMAGE_COLOR);
Mat dest;
cvtColor(opencv_image, dest,CV_BGR2RGB);
QImage image((uchar*)dest.data, dest.cols, dest.rows,QImage::Format_RGB888);
This is what worked for me. I modified Michal Kottman's code above.
I have the same problem as you too, so I develop four functions to alleviate my pain, they are
QImage mat_to_qimage_cpy(cv::Mat const &mat, bool swap = true);
QImage mat_to_qimage_ref(cv::Mat &mat, bool swap = true);
cv::Mat qimage_to_mat_cpy(QImage const &img, bool swap = true);
cv::Mat qimage_to_mat_ref(QImage &img, bool swap = true);
These functions can handle the images with 1, 3, 4 channels, every pixel must occupy one byte only(CV_8U->Format_Indexed8, CV_8UC3->QImage::Format_RGB888, CV_8UC4->QImage::Format_ARGB32), I do not deal with other types yet(QImage::Format_RGB16, QImage::Format_RGB666 and so on). The codes are located
at github.
The key concepts of **transform mat to Qimage ** are
/**
* #brief copy QImage into cv::Mat
*/
struct mat_to_qimage_cpy_policy
{
static QImage start(cv::Mat const &mat, QImage::Format format)
{
//The fourth parameters--mat.step is crucial, because
//opencv may do padding on every row, you need to tell
//the qimage how many bytes per row
//The last thing is if you want to copy the buffer of cv::Mat
//to the qimage, you need to call copy(), else the qimage
//will share the buffer of cv::Mat
return QImage(mat.data, mat.cols, mat.rows, mat.step, format).copy();
}
};
struct mat_to_qimage_ref_policy
{
static QImage start(cv::Mat &mat, QImage::Format format)
{
//every thing are same as copy policy, but this one share
//the buffer of cv::Mat but not copy
return QImage(mat.data, mat.cols, mat.rows, mat.step, format);
}
};
The key concepts of transform cv::Mat to Qimage are
/**
* #brief copy QImage into cv::Mat
*/
struct qimage_to_mat_cpy_policy
{
static cv::Mat start(QImage const &img, int format)
{
//same as convert mat to qimage, the fifth parameter bytesPerLine()
//indicate how many bytes per row
//If you want to copy the data you need to call clone(), else QImage
//cv::Mat will share the buffer
return cv::Mat(img.height(), img.width(), format,
const_cast<uchar*>(img.bits()), img.bytesPerLine()).clone();
}
};
/**
* #brief make Qimage and cv::Mat share the same buffer, the resource
* of the cv::Mat must not deleted before the QImage finish
* the jobs.
*/
struct qimage_to_mat_ref_policy
{
static cv::Mat start(QImage &img, int format)
{
//same as copy policy, but this one will share the buffer
return cv::Mat(img.height(), img.width(), format,
img.bits(), img.bytesPerLine());
}
};
If would be good if some one can extend these functions and make them support more types, please inform me if there are any bugs.
cv::Mat has a conversion operator to IplImage, so if you have something that converts the IplImage to a QImage, just use that (or make the - probably minor - adjustments to take the cv::Mat directly, the memory layout is the same, it's "just" the header that is different.)
This post shows how to convert a QImage to OpenCV's IplImage and vise-versa.
After that, if you need help to convert between IplImage* to cv::Mat:
// Assume data is stored by:
// IplImage* image;
cv::Mat mat(image, true); // Copies the data from image
cv::Mat mat(image, false); // Doesn't copy the data!
It's a hack, but will get the job done.
Use the static function convert16uc1 for the depth image:
QPixmap Viewer::convert16uc1(const cv::Mat& source)
{
quint16* pSource = (quint16*) source.data;
int pixelCounts = source.cols * source.rows;
QImage dest(source.cols, source.rows, QImage::Format_RGB32);
char* pDest = (char*) dest.bits();
for (int i = 0; i < pixelCounts; i++)
{
quint8 value = (quint8) ((*(pSource)) >> 8);
*(pDest++) = value; // B
*(pDest++) = value; // G
*(pDest++) = value; // R
*(pDest++) = 0; // Alpha
pSource++;
}
return QPixmap::fromImage(dest);
}
QPixmap Viewer::convert8uc3(const cv::Mat& source)
{
quint8* pSource = source.data;
int pixelCounts = source.cols * source.rows;
QImage dest(source.cols, source.rows, QImage::Format_RGB32);
char* pDest = (char*) dest.bits();
for (int i = 0; i < pixelCounts; i++)
{
*(pDest++) = *(pSource+2); // B
*(pDest++) = *(pSource+1); // G
*(pDest++) = *(pSource+0); // R
*(pDest++) = 0; // Alpha
pSource+=3;
}
return QPixmap::fromImage(dest);
}
QPixmap Viewer::convert16uc3(const cv::Mat& source)
{
quint16* pSource = (quint16*) source.data;
int pixelCounts = source.cols * source.rows;
QImage dest(source.cols, source.rows, QImage::Format_RGB32);
char* pDest = (char*) dest.bits();
for (int i = 0; i < pixelCounts; i++)
{
*(pDest++) = *(pSource+2); // B
*(pDest++) = *(pSource+1); // G
*(pDest++) = *(pSource+0); // R
*(pDest++) = 0; // Alpha
pSource+=3;
}
return QPixmap::fromImage(dest);
}
This did the trick for me. It's a little dodgy, has terrible performance (as pointed out in the comments), but works with all color formats I have thrown at it so far, and it is also very simple to do.
The procedure is as follows:
cv::Mat image = //...some image you want to display
// 1. Save the cv::Mat to some temporary file
cv::imwrite("../Images/tmp.jpg",image);
// 2. Load the image you just saved as a QImage
QImage img;
img.load("../Images/tmp.jpg");
Done!
If you, say, want to display it in a QLabel, then continue with:
// Set QImage as content of MyImageQLabel
ui-> MyImageQLabel->setPixmap(QPixmap::fromImage(img, Qt::AutoColor));
I personally use this for a simple image editor.
I have been able to display an image in a label in Qt using something like the following:
transformPixels(0,0,1,imheight,imwidth,1);//sets unsigned char** imageData
unsigned char* fullCharArray = new unsigned char[imheight * imwidth];
for (int i = 0 ; i < imheight ; i++)
for (int j = 0 ; j < imwidth ; j++)
fullCharArray[(i*imwidth)+j] = imageData[i][j];
QImage *qi = new QImage(fullCharArray, imwidth, imheight, QImage::Format_RGB32);
ui->viewLabel->setPixmap(QPixmap::fromImage(*qi,Qt::AutoColor));
So fullCharArray is an array of unsigned chars that have been mapped from the 2D array imageData, in other words, it is imheight * imwidth bytes.
The problem is, it seems like only a portion of my image is showing in the label. The image is very large. I would like to display the full image, scaled down to fit in the label, with the aspect ratio preserved.
Also, that QImage format was the only one I could find that seemed to give me a close representation of the image I am wanting to display, is that what I should expect? I am only using one byte per pixel (unsigned char - values from 0 to 255), and it seems liek RGB32 doesnt make much sense for that data type, but none of the other ones displayed anything remotely correct
edit:
Following dan gallaghers advice, I implemented this code:
QImage *qi = new QImage(fullCharArray, imwidth, imheight, QImage::Format_RGB32);
int labelWidth = ui->viewLabel->width();
int labelHeight = ui->viewLabel->height();
QImage small = qi->scaled(labelWidth, labelHeight,Qt::KeepAspectRatio);
ui->viewLabel->setPixmap(QPixmap::fromImage(small,Qt::AutoColor));
But this causes my program to "unexpectedly finish" with code 0
Qt doesn't support grayscale image construction directly. You need to use 8-bit indexed color image:
QImage * qi = new QImage(imageData, imwidth, imheight, QImage::Format_Indexed8);
for(int i=0;i<256;++i) {
qi->setColor(i, qRgb(i,i,i));
}
QImage has a scaled member. So you want to change your setPixmap call to something like:
QImage small = qi->scaled(labelWidth, labelHeight, Qt::KeepAspectRatio);
ui->viewLabel->setPixmap(QPixmap::fromImage(small, Qt::AutoColor);
Note that scaled does not modify the original image qi; it returns a new QImage that is a scaled copy of the original.
Re-Edit:
To convert from 1-byte grayscale to 4-byte RGB grayscale:
QImage qi = new QImage(imwidth, imheight, QImage::Format_RGB32);
for (int i = 0; i < imheight; i++)
{
for (int j = 0; j < imwidth; j++)
{
qi->setPixel(i, j, QRgb(imageData[i][j], imageData[i][j], imageData[i][j]));
}
}
Then scale qi and use the scaled copy as the pixmap for viewLabel.
I've also faced similar problem - QImage::scaled returned black images. The quick work-around which worked in my case was to convert QImage to QPixmap, scale and convert back then. Like this:
QImage resultImg = QPixmap::fromImage(image)
.scaled( 400, 400, Qt::KeepAspectRatio )
.toImage();
where "image" is the original image.
I was not aware of format-problem, before reading this thread - but indeed, my images are 1-Bit black-white.
Regards,
Valentin Heinitz