web-dev-qa-db-ja.com

QImageをグレースケールに変換する

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をグレースケールに変換するためのパフォーマンス面での最良の方法は何でしょうか?

10
sashoalm

低速関数QImage::pixelおよびQImage::setPixelを使用するのではなく、QImage::scanlineを使用してデータにアクセスします。スキャンのピクセル(水平線)は連続しています。 32 bppの画像があるとすると、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を構築することができます。さらにパフォーマンスが向上することを期待しています。
10
UmNyobe

Qt 5.5以降、次のように QImage :: convertToFormat() を呼び出してQImageをグレースケールに変換できます。

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

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

内部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;
  }
0