Преобразовать 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 в оттенки серого с точки зрения производительности?
4 ответа
Вместо того, чтобы использовать медленные функции QImage::pixel
а также QImage::setPixel
использовать QImage::scanline
для доступа к данным. Пиксели на скане (горизонтальная линия) являются последовательными. Предполагая, что у вас есть изображение в 32 bpp, вы можете использовать QRgb для итерации по сканированию. Наконец, всегда ставьте координату х во внутреннем цикле. Который дает:
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, чтобы выполнить преобразование в градациях серого, а затем собрать Qimage из буфера opencv. Я ожидаю еще лучшего улучшения производительности.
Начиная с Qt 5.5, вы можете вызывать QImage::convertToFormat() для преобразования QImage в градации серого следующим образом:
QImage image = ...;
image.convertToFormat(QImage::Format_Grayscale8);
Я выложу слегка измененную версию кода @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();
}
}
Внутренний класс 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;
}