最近看《计算机视觉——算法与应用》一书,看到CIE色度图时,便突发奇想——用Qt将色度图绘制出来,于是有了这篇博客的内容。不过书中只是提到XYZ的计算公式,并没有说马蹄形的轮廓是怎么来的。于是在网上找了CIE1931XYZ标准数据,结合书中给出的公式(如下所示)
x = X X + Y + Z , y = Y X + Y + Z , z = y = Z X + Y + Z x=\frac{X}{X+Y+Z}, y=\frac{Y}{X+Y+Z}, z=y=\frac{Z}{X+Y+Z} x=X+Y+ZX,y=X+Y+ZY,z=y=X+Y+ZZ
用matlab进行了绘制,以便确认是否为色度图马蹄形轮廓数据,得到的结果如下图(a)所示。
此处请注意下方的棕色线条,不然会与本人一样,多走不少弯路。由于刚开始没有注意到下方的棕色线条,所以用Qt绘制出来的结果如上图(b)所示,存在很明显的问题。而图(a)中的棕色线条是因为此部分没有数据,matlab为了封闭图形绘制出来的。因此,在用Qt绘制色品图之前,我们需要将棕色线条部分的数据插值出来。不难看出棕色线条是一条直线,可以用 y = k x + b y=kx+b y=kx+b计算出来。插值完数据后,重新用Qt绘制出来的CIE色度图如图(c)所示。
其实图(c)所示的色度图依旧有很大的问题,比如有很明显的线条感,色彩过渡不够自然,数据密集的地方有摩尔纹等诸多需要优化的地方。这些问题可以在马蹄形轨迹数据上下功夫,让每个数据点的间隔更均匀,从而让绘制出的色品图更加好看。
Qt绘制色品图的步骤如下:
0.创建工程
用Qt绘制色品图采用VS2013+Qt5.8开发环境简单创建一个QtDrawCIE1931工程即可。
1.准备xyz数据
将网上搜到的CIE1931XYZ标准数据,用matlab转换插值后得到的数据,放到ciestandardxyz.h文件内,便于后续绘图时,直接读取数据。由于数据较多,在此只简单的贴了几个数据,有需要的联系我获取完整数据。
#ifndef _CIESTANDARD_H
#define _CIESTANDARD_H
#define CIEDATALEN 3
#define CIEDATANUM 501
// 存储matlab处理后的CIE xyz数据
const float ciedata[CIEDATANUM][CIEDATALEN] = {
{ 0.17555952, 0.005297870, 0.8191426 },
{ 0.17543226, 0.005282220, 0.8192855 },
{ 0.17540666, 0.005279333, 0.8193140 },
...
{ 0.21281756, 0.022649340, 0.7645331 },
{ 0.19418854, 0.013973605, 0.7918379 },
{ 0.17555952, 0.005297870, 0.8191426 },
};
#endif
2.写xyz2rgb函数,计算xy对应坐标的RGB值
#pragma once
#include
#include "ui_QtDrawCIE1931.h"
#include "ciestandardxyz.h"
class QtDrawCIE1931 : public QMainWindow
{
Q_OBJECT
public:
QtDrawCIE1931(QWidget *parent = Q_NULLPTR);
~QtDrawCIE1931();
protected:
void paintEvent(QPaintEvent *event);
private:
Ui::QtDrawCIE1931Class *ui;
void xyz2rgb(int &r, int &g, int &b, float x, float y, float z);
};
xyz2rgb函数声明在QtDrawCIE1931.h文件内,具体实现过程在QtDrawCIE1931.cpp文件内。主要是将xy坐标点对应的RGB值计算出来,以便于绘制色度图时填充色彩。
void QtDrawCIE1931::xyz2rgb(int &r, int &g, int &b, float x, float y, float z)
{
// 利用xyz数据计算RGB数据
double dr = 0.4185 * x - 0.1587 * y - 0.0828 * z;
double dg = -0.0912 * x + 0.2524 * y + 0.0157 * z;
double db = 0.0009 * x - 0.0025 * y + 0.1786 * z;
double max = 0.0;
max = dr > dg ? dr : dg;
max = max > db ? max : db;
// 将数据转换为int型,便于显示使用
r = (int)((dr / max) * 255 + 0.5);
g = (int)((dg / max) * 255 + 0.5);
b = (int)((db / max) * 255 + 0.5);
// 限制数据所属范围
r = r > 255 ? 255 : r;
g = g > 255 ? 255 : g;
b = b > 255 ? 255 : b;
r = r < 0 ? 0 : r;
g = g < 0 ? 0 : g;
b = b < 0 ? 0 : b;
}
3.重写窗体的paintEvent函数,绘制CIE色品图
有一定Qt基础的人知道,QPainter提供绘制封闭图形方法drawPolygon,QBrush有渐变填充方法QLinearGradient,通过将两种方法有机结合便可绘制出色度图。具体实现过程如下。
void QtDrawCIE1931::paintEvent(QPaintEvent *event)
{
// 基本设置
QPainter painter(this);
painter.fillRect(this->rect(), Qt::white);
painter.setRenderHint(QPainter::Antialiasing);
painter.scale(this->width(), this->height());
QLinearGradient linearGradient;
QPointF writePoint(1.0 / 3, 1.0 - 1.0 / 3); // 注意坐标系变化
for (int i = 0; i < CIEDATANUM; i++)
{
// 准备封闭三角形数据
float x0 = ciedata[i][0];
float y0 = ciedata[i][1];
float z0 = ciedata[i][2];
int next = i + 1;
next = next < CIEDATANUM ? next : 0;
float x1 = ciedata[next][0];
float y1 = ciedata[next][1];
QPointF point[] = {
writePoint,
QPointF(x0, 1.0 - y0),
QPointF(x1, 1.0 - y1)
};
// 计算xy坐标对应的rgb数据
int r = 0;
int g = 0;
int b = 0;
xyz2rgb(r, g, b, x0, y0, z0);
// 设置渐变填充的起始点和起始颜色
linearGradient.setStart(writePoint);
linearGradient.setFinalStop(QPointF(x0, 1.0 - y0));
linearGradient.setColorAt(0.0, QColor(235, 235, 235));
linearGradient.setColorAt(1.0, QColor(r, g, b));
// 绘制封闭三角形
painter.setPen(QPen(QColor(r, g, b, 0), 1));
painter.setBrush(QBrush(linearGradient));
painter.drawPolygon(point, 3);
}
QMainWindow::paintEvent(event);
}
加上构造函数和析构函数,就是完整绘制程序。根据实际需要,也可以将色度图绘制在Graphics View控件上,便于增加坐标轴。需要注意的是,QWidget的坐标系统和我们平常用的坐标系不一致,Y轴方向和我们平常用的坐标系刚好相反,因此在绘制数据的时候,纵坐标做了相应处理。绘制完成后,在网络上又看到一篇不错的博客,在此安利给看此篇博客的人——《如何绘制CIE1931xy色度图》
参考文献:
[1] 王维波,2018. Qt 5.9 C++开发指南[M]. 北京:人民邮电出版社
个人声明:
以上内容,纯属个人观点,不喜勿喷。未经本人同意,不得私自转载。博客中出现的代码仅供学习参考,不得有其他用途。若文中存在纰漏,或读者有更好的建议,欢迎留言探讨。也可邮箱联系:[email protected]