将QImage转换为灰度图像

14

我有一张 QImage,需要将其转换为灰度图像,然后再以彩色绘制。我发现了一个 allGray()isGrayScale() 函数,用于检查图像是否已经是灰度图像,但没有 toGrayScale() 或类似命名的函数。

目前,我正在使用以下代码,但性能不是很好:

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());
    }
}

如何在性能方面最好地将QImage转换为灰度图像?


1
虽然这仍然不是最好的方法,但尝试切换您的for循环(因此首先迭代ii,其次是jj)。根据内存布局,这可能会导致更好的缓存一致性并使代码更快。 - Daerst
@Daerst,是个好建议,但如果我找到更好的解决方案,那么优化一个变通方法也没有意义。如果没有其他解决方案,那或许可以考虑。 - sashoalm
4个回答

17

从Qt 5.5开始,您可以调用QImage::convertToFormat()方法将QImage转换为灰度图像,如下所示:

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

1
这应该是被接受的答案。对我有用 - 谢谢 :) - schlenger
这个不处理透明度。 - HiFile.app - best file manager

11

使用QImage::scanline方法访问图像数据而不是使用较慢的函数QImage::pixelQImage::setPixel。扫描(水平线)上的像素是连续的。假设您有一个32bpp图像,可以使用QRgb来遍历扫描。最后,始终将x坐标放在内部循环中。总结如下:

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();
    }
}

使用一张3585 x 2386的图像进行快速测试,结果如下:

********* 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 *********

源代码: 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

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)

个人资料:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = QImageTest
TEMPLATE = app

CONFIG  += qtestlib

SOURCES += testimage.cpp

HEADERS += testimage.h

重要提示:

  • 通过反转循环,您已经获得了重要的性能提升。在这个测试案例中,它是~90ms
  • 您可以使用其他库(如opencv)来进行灰度转换,然后从opencv缓冲区构建Qimage。我期望会有更好的性能提升。

4
我将发布一个稍微修改过的 @UmNyobe 代码版本。我只是通过增加一个指针来扫描每行,而不是通过索引计算每个像素。
// 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();
    }
}

0

内部qt类QPixmapColorizeFilter使用函数grayscale来解决类似主题的问题。

我从中派生了下面的函数,应该可以解决这个问题。

重要的部分是将图像转换为32位格式,这样您可以将每个像素视为32位值,并且您不需要关注位对齐。

您也可以直接使用bits函数并迭代所有像素,而不是迭代行和列。通过这种技巧,可以避免在scanLine函数中执行的乘法。

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;
  }

当您保证在两行之间有连续的像素时,位操作可以正常工作。我使用扫描线更为保守。例如,使用QImage::QImage(uchar * data, int width, int height, int bytesPerLine, Format format)创建的图像可能具有bytesPerLine > width*sizeofpixel() - UmNyobe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接