Convert a QImage to grayscale

别说谁变了你拦得住时间么 提交于 2019-11-27 04:42:07

问题


I have a QImage and I need to convert it to grayscale, then later paint over that with colors. I found an allGray() and isGrayScale() function to check if an image is already grayscale, but no toGrayScale() or similarly-named function.

Right now I'm using this code, but it's does not have a 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, performance-wise, to convert a QImage to grayscale?


回答1:


Rather than using the slow functions QImage::pixel and QImage::setPixel, use QImage::scanline to access the data. Pixels on a scan (horizontal line ) are consecutive. Assuming you have a 32 bpp image, you can use QRgb to iterate over the scan. Finally always put the x coordinate in the inner loop. Which 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 3585 x 2386 image 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: testimage.h file:

#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

testimage.cpp file:

#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 just by inverting the loops. In this test case it was ~90ms.
  • You may use other libraries like opencv to make the grayscale conversion and then build the Qimage from an opencv buffer. I expect an even better performance improvement.



回答2:


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

QImage image = ...;
image.convertToFormat(QImage::Format_Grayscale8);



回答3:


I'll post a slightly modified version of @UmNyobe's code. I just increment a pointer for the scan lines instead of calculating each pixel via 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:


Internal qt class QPixmapColorizeFilter uses function grayscale that solves similar topic.

I derived following function from it, that should solve the problem.

Important part is converting image to 32-bit format, so you can consider each pixel as 32-bit value and you do not need to concern about bit alignment.

You can also use bits function directly and iterate over all pixels instead of iterating over lines and columns. With this trick you avoid multiplication performed in 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;
  }


来源:https://stackoverflow.com/questions/27949569/convert-a-qimage-to-grayscale

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!