Convert QImage to Grayscale

I have a QImage and I need to convert it to shades of gray and then color it using colors. I found the allGray() and isGrayScale() functions to check if the image is already in grayscale but not toGrayScale() or a similarly named function.

I am using this code now, but it does not have very good performance:

 for (int ii = 0; ii < image.width(); ii++) { for (int jj = 0; jj < image.height(); jj++) { int gray = qGray(image.pixel(ii, jj)); image.setPixel(ii, jj, QColor(gray, gray, gray).rgb()); } } 

What would be the best way, in terms of performance, to convert QImage to grayscale?

+6
source share
4 answers

Instead of using the slow functions QImage::pixel and QImage::setPixel use QImage::scanline to access the data. The pixels on the scan (horizontal line) are sequential. Assuming you have a 32-bit image, you can use QRgb to repeat the scan. Finally, always put the x coordinate in the inner loop. What gives:

 for (int ii = 0; ii < image.height(); ii++) { uchar* scan = image.scanLine(ii); int depth =4; for (int jj = 0; jj < image.width(); jj++) { QRgb* rgbpixel = reinterpret_cast<QRgb*>(scan + jj*depth); int gray = qGray(*rgbpixel); *rgbpixel = QColor(gray, gray, gray).rgba(); } } 

A quick test with an image of 3585 x 2386 gave

 ********* Start testing of TestImage ********* Config: Using QTest library 4.7.4, Qt 4.7.4 PASS : TestImage::initTestCase() RESULT : TestImage::grayscaleOp(): 390 msecs per iteration (total: 390, iterations: 1) PASS : TestImage::grayscaleOp() RESULT : TestImage::grayscaleFast(): 125 msecs per iteration (total: 125, iterations: 1) PASS : TestImage::grayscaleFast() PASS : TestImage::cleanupTestCase() Totals: 4 passed, 0 failed, 0 skipped ********* Finished testing of TestImage ********* 

Source code: File testimage.h:

 #ifndef TESTIMAGE_H #define TESTIMAGE_H #include <QtTest/QtTest> #include <QObject> #include <QImage> class TestImage : public QObject { Q_OBJECT public: explicit TestImage(QObject *parent = 0); signals: private slots: void grayscaleOp(); void grayscaleFast(); private: QImage imgop; QImage imgfast; }; #endif // TESTIMAGE_H 

file testimage.cpp:

 #include "testimage.h" TestImage::TestImage(QObject *parent) : QObject(parent) , imgop("path_to_test_image.png") , imgfast("path_to_test_image.png") { } void TestImage::grayscaleOp() { QBENCHMARK { QImage& image = imgop; for (int ii = 0; ii < image.width(); ii++) { for (int jj = 0; jj < image.height(); jj++) { int gray = qGray(image.pixel(ii, jj)); image.setPixel(ii, jj, QColor(gray, gray, gray).rgb()); } } } } void TestImage::grayscaleFast() { QBENCHMARK { QImage& image = imgfast; for (int ii = 0; ii < image.height(); ii++) { uchar* scan = image.scanLine(ii); int depth =4; for (int jj = 0; jj < image.width(); jj++) { QRgb* rgbpixel = reinterpret_cast<QRgb*>(scan + jj*depth); int gray = qGray(*rgbpixel); *rgbpixel = QColor(gray, gray, gray).rgba(); } } } } QTEST_MAIN(TestImage) 

pro file:

 QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QImageTest TEMPLATE = app CONFIG += qtestlib SOURCES += testimage.cpp HEADERS += testimage.h 

Important Note:

  • You already get an important performance boost only by inverting loops. In this case, it was ~90ms .
  • You can use other libraries like opencv to convert grayscale and then build Qimage from the opencv buffer. I expect even better performance improvements.
+8
source

I will post a slightly modified version of @UmNyobe code. I just increase the pointer to the scan lines instead of calculating each pixel with an index.

 // We assume the format to be RGB32!!! Q_ASSERT(image.format() == QImage::Format_RGB32); for (int ii = 0; ii < image.height(); ii++) { QRgb *pixel = reinterpret_cast<QRgb*>(image.scanLine(ii)); QRgb *end = pixel + image.width(); for (; pixel != end; pixel++) { int gray = qGray(*pixel); *pixel = QColor(gray, gray, gray).rgb(); } } 
+4
source

Since Qt 5.5, you can call QImage :: convertToFormat () to convert QImage to grayscale as follows:

 QImage image = ...; image.convertToFormat(QImage::Format_Grayscale8); 
+1
source

The qt QPixmapColorizeFilter inner class uses the grayscale function, which solves a similar topic.

I deduced from it the following function, which should solve the problem.

The important part is converting the image to 32-bit format, so you can consider each pixel as a 32-bit value, and you do not need to worry about aligning the bits.

You can also use the bits function directly and iterate over all the pixels instead of iterating through the rows and columns. With this trick, you avoid the multiplication performed by the scanLine function.

 QImage convertToGrayScale(const QImage &srcImage) { // Convert to 32bit pixel format QImage dstImage = srcImage.convertToFormat(srcImage.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); unsigned int *data = (unsigned int*)dstImage.bits(); int pixelCount = dstImage.width() * dstImage.height(); // Convert each pixel to grayscale for(int i = 0; i < pixelCount; ++i) { int val = qGray(*data); *data = qRgba(val, val, val, qAlpha(*data)); ++data; } return dstImage; } 
0
source

Source: https://habr.com/ru/post/981011/


All Articles