c++ - 如何将着色表应用于我的灰度 8 位图像并在 Qt 中正确转换它?

标签 c++ image qt image-processing

我想将我的 grayscale_8 QImage 转换为带有着色表的 Indexed_8。我发现已经找到并测试了可以准确生成颜色图 (MATLAB jet) 的函数——这是我的代码:

for (double i = 0; i < 256; ++i) {

    double hi = 255;
    double lo = 0;
    double r = -4.0 * std::abs(i - 255.0 * 3.0 / 4) + 255.0 * 3.0 / 2;
    double g = -4.0 * std::abs(i - 255.0 * 2.0 / 4) + 255.0 * 3.0 / 2;
    double b = -4.0 * std::abs(i - 255.0 * 1.0 / 4) + 255.0 * 3.0 / 2;


    CP->colortable.append(qRgb(r < lo ? lo : r > hi ? hi : r, g < lo ? lo : g > hi ? hi : g, b < lo ? lo : b > hi ? hi : b));


}

r、g、b double 表示 MATLAB 的 jet 颜色图,给定从 0 到 255 的输入和范围 (0,255) 内的输出。三元运算符等同于 std::clamp,并将值限制在 0 到 255 之间。

然后我将图像转换如下:

q = QImage((uchar*)p->GetBuffer(), p->GetWidth(), p->GetHeight(), QImage::Format_Grayscale8);


QImage c = q.convertToFormat(QImage::Format_Indexed8, colortable);

ui->imageLabel->setPixmap(QPixmap::fromImage(c));

你可以看到这张图片中没有红色

尽管我知道青色部分中的值的像素值为 255。看起来蓝色被加回去代替了红色。这是一个简单的格式错误吗?

这是规范化后的 MATLAB jet 颜色图

我的图片应该是这样的

.

最佳答案

首先,我检查了 OP 公开的颜色表算法是否提供了合理的值。为此,我围绕 coliru 上公开的代码编写了一个小样本。并通过眼睛检查生成的值。输出看起来合理(根据 MatLab 提供的快照)——我认为这不是问题所在。

接下来我调查

q = QImage((uchar*)p->GetBuffer(), p->GetWidth(), p->GetHeight(), QImage::Format_Grayscale8);


QImage c = q.convertToFormat(QImage::Format_Indexed8, colortable);

ui->imageLabel->setPixmap(QPixmap::fromImage(c));

文档。使用的函数 QImage::convertToFormat() :

QImage QImage::convertToFormat(QImage::Format format, const QVector &colorTable, Qt::ImageConversionFlags flags = Qt::AutoColor) const

This is an overloaded function.

Returns a copy of the image converted to the given format, using the specified colorTable.

Conversion from RGB formats to indexed formats is a slow operation and will use a straightforward nearest color approach, with no dithering.

没有真正的帮助。 最直接的最近颜色方法的最后一句话让我有些怀疑。

因此,我怀疑它是否只是将数据值重新用作颜色表的索引。但这正是 OP 实际上打算做的。

实际上,我想要的是图像的“重新诠释”之类的东西。

所以,我想到了这个方法:

  1. 创建一个具有原始图像宽度和高度的QImage,以及QImage::Format_Indexed8
  2. 按照OP的建议设置这张图片的颜色表
  3. 将原始图像的数据复制到此图像中。

示例testQImageGrayToRGB.cc:

#include <vector>

#include <QtWidgets>

typedef unsigned char uchar;

QImage buildSample(const int w = 256, const int h = 32)
{
  QImage qImg(w, h, QImage::Format_Grayscale8);
  const int bpl = qImg.bytesPerLine();
  for (int y = 0; y < h; ++y) {
    uchar *row = qImg.bits() + y * bpl;
    for (int x = 0; x < w; ++x) {
      row[x] = (uchar)(x * 255 / (w - 1));
    }
  }
  return qImg;
}

const QVector<QRgb> makeColorTable()
{
  QVector<QRgb> qColTbl;
  const double hi = 255;
  const double lo = 0;
  for (double i = 0; i < 256; ++i) {
    double r = -4.0 * std::abs(i - 255.0 * 3.0 / 4) + 255.0 * 3.0 / 2;
    double g = -4.0 * std::abs(i - 255.0 * 2.0 / 4) + 255.0 * 3.0 / 2;
    double b = -4.0 * std::abs(i - 255.0 * 1.0 / 4) + 255.0 * 3.0 / 2;
    qColTbl.append(
      qRgb(
        r < lo ? lo : r > hi ? hi : r,
        g < lo ? lo : g > hi ? hi : g,
        b < lo ? lo : b > hi ? hi : b));
  }
  return qColTbl;
}

QImage colorize(const QImage &qImg, const QVector<QRgb> &qColTbl)
{
  const int w = qImg.width(), h = qImg.height();
  QImage qImgDst(w, h, QImage::Format_Indexed8);
  qImgDst.setColorTable(qColTbl);
  const int bpl = qImg.bytesPerLine();
  const int bplDst = qImgDst.bytesPerLine();
  for (int y = 0; y < h; ++y) {
    const uchar *row = qImg.bits() + y * bpl;
    uchar *rowDst = qImgDst.bits() + y * bplDst;
    std::copy(row, row + w, rowDst);
  }
  return qImgDst;
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build contents
  const QImage qImgGray = buildSample();
  const QVector<QRgb> qColTbl = makeColorTable();
  const QImage qImgColorOP
    = qImgGray.convertToFormat(QImage::Format_Indexed8, qColTbl);
  const QImage qImgColorDS
    = colorize(qImgGray, qColTbl);
  // build some GUI
  QWidget win;
  QVBoxLayout qVBox;
  QLabel qLblGray(
    QString::fromUtf8("QImage Source: (QImage::Format_Grayscale8)"));
  qVBox.addWidget(&qLblGray);
  QLabel qLblImgGray;
  qLblImgGray.setPixmap(QPixmap::fromImage(qImgGray));
  qVBox.addWidget(&qLblImgGray);
  QLabel qLblColorOP(
    QString::fromUtf8("QImage Colorized: (Attempt of OP)"));
  qVBox.addWidget(&qLblColorOP);
  QLabel qLblImgColorOP;
  qLblImgColorOP.setPixmap(QPixmap::fromImage(qImgColorOP));
  qVBox.addWidget(&qLblImgColorOP);
  QLabel qLblColorDS(
    QString::fromUtf8("QImage Colorized: (Attempt of DS)"));
  qVBox.addWidget(&qLblColorDS);
  QLabel qLblImgColorDS;
  qLblImgColorDS.setPixmap(QPixmap::fromImage(qImgColorDS));
  qVBox.addWidget(&qLblImgColorDS);
  win.setLayout(&qVBox);
  win.show();
  // exec. application
  return app.exec();
}

一个最小的 Qt 项目文件testQImageGrayToRGB.pro:

SOURCES = testQImageGrayToRGB.cc

QT += widgets

输出(VisualStudio 2013,Qt 5.9.2):

testQImageGrayToRGB.exe (built with VS 2013)

糟糕。中间的图像(应该类似于 OP 的方法)看起来与 OP 示例中的预期有点不同。

但是,通过我的“重新解释-转换”方法获得的底部图像看起来确实符合预期。

为了好玩,我在 cygwin64 中再次构建了示例:

$ qmake-qt5 testQImageGrayToRGB.pro

$ make && ./testQImageGrayToRGB 
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQImageGrayToRGB.o testQImageGrayToRGB.cc
g++  -o testQImageGrayToRGB.exe testQImageGrayToRGB.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
Qt Version: 5.9.4

输出(cygwin64、g++ (GCC) 7.4.0、Qt 5.9.4):

testQImageGrayToRGB.exe (built with g++ in cygwin)

啊哈!现在,中间图像的颜色类似于 OP 所描述的。


注意:

实际上,灰度和索引图像可以共享相同的数据。这将使逐行复制过时。但是,在这种情况下,必须仔细考虑拥有数据的 QImage 和共享数据的 QImage 的生命周期。

关于c++ - 如何将着色表应用于我的灰度 8 位图像并在 Qt 中正确转换它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58666021/

相关文章:

c++ - Qt ASCII转IP函数

c++ - undefined reference std::pair

html - Bootstrap 3 背景图像多个部分

javascript - 带有 PYQT QWebView 的谷歌地图 API

c++ - Windows 10 中 Qt 桌面应用程序的不当缩放

Java - 我需要帮助理解 UI 缩放到不同的屏幕尺寸

image - VBA - Powerpoint 2010 ppShp.Fill.UserPicture 不更新图片

c++ - 获取QListWidgetItem x y的坐标,我要达到如图的效果

qt - 如何在Qt中自定义 ListView

c++ - 如何存储循环缓冲区迭代器的中间值?