颜色配置文件转换 - 图像不正确
Color profile conversion - image not correct
我有嵌入了 ICC 配置文件的 jpg 图像。我已经从文件中提取了 ICC 配置文件,现在我需要将解压缩的图像转换为 sRGB。
我的公式基于http://www.brucelindbloom.com/index.html?Math.html
我使用这个算法:
- 从 RGB 转换图像 -> XYZ
- 在 XYZ 上进行色彩调整
- 转换 XYZ -> sRGB
对于任务,我有这个代码:
Image2d<float> ColorSpace::ConvertRgbToXyz(const Image2d<uint8_t> & input,
const IccProfile & inputIcc) {
size_t len = input.GetPixelsCount();
std::vector<float> data;
data.resize(len * 3);
for (size_t i = 0; i < len; i++) {
const uint8_t * rgb = input.GetPixelStart(i);
float r = (rgb[0] / 255.0f);
float g = (rgb[1] / 255.0f);
float b = (rgb[2] / 255.0f);
r = (r <= 0) ? 0 : pow(r, inputIcc.rGamma);
g = (g <= 0) ? 0 : pow(g, inputIcc.gGamma);
b = (b <= 0) ? 0 : pow(b, inputIcc.bGamma);
//sRGB
float x = inputIcc.rXYZ[0] * r + inputIcc.gXYZ[0] * g + inputIcc.bXYZ[0] * b;
float y = inputIcc.rXYZ[1] * r + inputIcc.gXYZ[1] * g + inputIcc.bXYZ[1] * b;
float z = inputIcc.rXYZ[2] * r + inputIcc.gXYZ[2] * g + inputIcc.bXYZ[2] * b;
data[i * 3 + 0] = x;
data[i * 3 + 1] = y;
data[i * 3 + 2] = z;
}
return Image2d<float>(input.GetWidth(),
input.GetHeight(),
std::move(data),
PixelFormat::XYZ);
}
从白点 [Xws, Yws, Zws] 到目标白点 [Xwd, Ywd, Zwd] 的色度转换(D65 使用白色 => Xwd = 0.95047f,Ywd = 1.0f,Zwd = 1.08883f)
void ColorSpace::ChromaticAdaptation(Image2d<float> & input,
float Xws, float Yws, float Zws,
float Xwd, float Ywd, float Zwd,
ChromaticMethod method) {
size_t len = input.GetPixelsCount();
Matrix3x3 m = Matrix3x3(
(Xwd / Xws), 0, 0,
0, (Ywd / Yws), 0,
0, 0, (Zwd / Zws)
);
if (method == ChromaticMethod::Bradford) {
const Matrix3x3 mA = Matrix3x3(
0.8951000, 0.2664000, -0.1614000,
-0.7502000, 1.7135000, 0.0367000,
0.0389000, -0.0685000, 1.0296000
);
const Matrix3x3 mAInv = Matrix3x3(
0.9869929, -0.1470543, 0.1599627,
0.4323053, 0.5183603, 0.0492912,
-0.0085287, 0.0400428, 0.9684867
);
float xs = mA.M[0][0] * Xws + mA.M[0][1] * Yws + mA.M[0][2] * Zws;
float ys = mA.M[1][0] * Xws + mA.M[1][1] * Yws + mA.M[1][2] * Zws;
float zs = mA.M[2][0] * Xws + mA.M[2][1] * Yws + mA.M[2][2] * Zws;
float xd = mA.M[0][0] * Xwd + mA.M[0][1] * Ywd + mA.M[0][2] * Zwd;
float yd = mA.M[1][0] * Xwd + mA.M[1][1] * Ywd + mA.M[1][2] * Zwd;
float zd = mA.M[2][0] * Xwd + mA.M[2][1] * Ywd + mA.M[2][2] * Zwd;
m = Matrix3x3(
(xd / xs), 0, 0,
0, (yd / ys), 0,
0, 0, (zd / zs)
);
Matrix3x3 res = mAInv;
res *= m;
res *= mA;
m = res;
}
for (size_t i = 0; i < len; i++) {
float * xyz = input.GetPixelStart(i);
float x = xyz[0];
float y = xyz[1];
float z = xyz[2];
float xd = m.M[0][0] * x + m.M[0][1] * y + m.M[0][2] * z;
float yd = m.M[1][0] * x + m.M[1][1] * y + m.M[1][2] * z;
float zd = m.M[2][0] * x + m.M[2][1] * y + m.M[2][2] * z;
xyz[0] = xd;
xyz[1] = yd;
xyz[2] = zd;
}
}
最后
Image2d<uint8_t> ColorSpace::ConvertXyzToSRgb_D65(const Image2d<float> & input) {
size_t len = input.GetPixelsCount();
std::vector<uint8_t> data;
data.resize(len * 3);
for (size_t i = 0; i < len; i++) {
const float * xyz = input.GetPixelStart(i);
float x = xyz[0];
float y = xyz[1];
float z = xyz[2];
//sRGB
float r = 3.2404542f * x + -1.5371385f * y + -0.4985314f * z;
float g = -0.9692660f * x + 1.8760108f * y + 0.0415560f * z;
float b = 0.0556434f * x + -0.2040259f * y + 1.0572252f * z;
//color companding
//for sRGB
r = (r > 0.0031308f) ? 1.055f * std::pow(r, 1 / 2.4f) - 0.055f : 12.92f * r;
g = (g > 0.0031308f) ? 1.055f * std::pow(g, 1 / 2.4f) - 0.055f : 12.92f * g;
b = (b > 0.0031308f) ? 1.055f * std::pow(b, 1 / 2.4f) - 0.055f : 12.92f * b;
data[i * 3 + 0] = static_cast<uint8_t>(255.0f * std::clamp(r, 0.0f, 1.0f));
data[i * 3 + 1] = static_cast<uint8_t>(255.0f * std::clamp(g, 0.0f, 1.0f));
data[i * 3 + 2] = static_cast<uint8_t>(255.0f * std::clamp(b, 0.0f, 1.0f));
}
return Image2d<uint8_t>(input.GetWidth(),
input.GetHeight(),
std::move(data),
PixelFormat::RGB);
}
但是,与在图像查看器(启用了 ICC 配置文件的 IrfanView、Firefox 或 Photoshop)中打开的图像相比,结果图像不正确。
左边是它的外观。右边是我的输出。
没有任何颜色转换,图像加载为:
所以我的版本更接近 "how it should look",但不完全一样。
我不确定ConvertRgbToXyz
是否正确。我根据文档 http://www.color.org/specification/ICC1v43_2010-12.pdf - rXYZ 的第 9.2.44 节 -
直接从 ICC 获取 rXYZ / gXYZ / bXYZ 值
This tag contains the first column in the matrix, which is used in matrix/TRC transforms.
我的 ICC 配置文件是这样的:
Header:
size = 25908 bytes
CMM = 'argl'
Version = 2.2.0
Device Class = Input
Color Space = RGB
Conn. Space = XYZ
Date, Time = 10 Mar 2017, 13:05:37
Platform = Microsoft
Flags = Not Embedded Profile, Use anywhere
Dev. Mnfctr. = 0x0
Dev. Model = 0x0
Dev. Attrbts = Reflective, Glossy, Positive, Color
Rndrng Intnt = Perceptual
Illuminant = 0.964203, 1.000000, 0.824905 [Lab 100.000000, 0.000498, -0.000436]
Creator = 'argl'
'desc': CoCa 10.3.2017
'cprt': Copyright_free
'dmnd': proserv 600i
'dmdd': Model
'wtpt': 0.768753, 0.822220, 0.665100 [Lab 92.672746, -4.781618, 1.218722]
'bkpt': 0.003693, 0.004395, 0.003387 [Lab 3.969564, -2.199059, 0.448583]
'rXYZ': 0.956482, 0.326324, 0.010391 [Lab 63.862187, 154.428754, 91.157829]
'gXYZ': 0.070267, 0.995911, -0.271652 [Lab 99.841662, -290.469249, 685.018939]
'bXYZ': 0.136230, -0.105103, 1.321716 [Lab -94.938734, 600.671989, -370.133480]
'rTRC': Curve is gamma of 1.234375
'gTRC': Curve is gamma of 1.296875
'bTRC': Curve is gamma of 1.312500
我找到问题了。
我使用错误的参考白色进行色彩调整。我使用的是来自 wtpt
的值,但应该使用 Illuminant
作为输入白色。
输出白色仍设置为 D65。
我有嵌入了 ICC 配置文件的 jpg 图像。我已经从文件中提取了 ICC 配置文件,现在我需要将解压缩的图像转换为 sRGB。
我的公式基于http://www.brucelindbloom.com/index.html?Math.html
我使用这个算法:
- 从 RGB 转换图像 -> XYZ
- 在 XYZ 上进行色彩调整
- 转换 XYZ -> sRGB
对于任务,我有这个代码:
Image2d<float> ColorSpace::ConvertRgbToXyz(const Image2d<uint8_t> & input,
const IccProfile & inputIcc) {
size_t len = input.GetPixelsCount();
std::vector<float> data;
data.resize(len * 3);
for (size_t i = 0; i < len; i++) {
const uint8_t * rgb = input.GetPixelStart(i);
float r = (rgb[0] / 255.0f);
float g = (rgb[1] / 255.0f);
float b = (rgb[2] / 255.0f);
r = (r <= 0) ? 0 : pow(r, inputIcc.rGamma);
g = (g <= 0) ? 0 : pow(g, inputIcc.gGamma);
b = (b <= 0) ? 0 : pow(b, inputIcc.bGamma);
//sRGB
float x = inputIcc.rXYZ[0] * r + inputIcc.gXYZ[0] * g + inputIcc.bXYZ[0] * b;
float y = inputIcc.rXYZ[1] * r + inputIcc.gXYZ[1] * g + inputIcc.bXYZ[1] * b;
float z = inputIcc.rXYZ[2] * r + inputIcc.gXYZ[2] * g + inputIcc.bXYZ[2] * b;
data[i * 3 + 0] = x;
data[i * 3 + 1] = y;
data[i * 3 + 2] = z;
}
return Image2d<float>(input.GetWidth(),
input.GetHeight(),
std::move(data),
PixelFormat::XYZ);
}
从白点 [Xws, Yws, Zws] 到目标白点 [Xwd, Ywd, Zwd] 的色度转换(D65 使用白色 => Xwd = 0.95047f,Ywd = 1.0f,Zwd = 1.08883f)
void ColorSpace::ChromaticAdaptation(Image2d<float> & input,
float Xws, float Yws, float Zws,
float Xwd, float Ywd, float Zwd,
ChromaticMethod method) {
size_t len = input.GetPixelsCount();
Matrix3x3 m = Matrix3x3(
(Xwd / Xws), 0, 0,
0, (Ywd / Yws), 0,
0, 0, (Zwd / Zws)
);
if (method == ChromaticMethod::Bradford) {
const Matrix3x3 mA = Matrix3x3(
0.8951000, 0.2664000, -0.1614000,
-0.7502000, 1.7135000, 0.0367000,
0.0389000, -0.0685000, 1.0296000
);
const Matrix3x3 mAInv = Matrix3x3(
0.9869929, -0.1470543, 0.1599627,
0.4323053, 0.5183603, 0.0492912,
-0.0085287, 0.0400428, 0.9684867
);
float xs = mA.M[0][0] * Xws + mA.M[0][1] * Yws + mA.M[0][2] * Zws;
float ys = mA.M[1][0] * Xws + mA.M[1][1] * Yws + mA.M[1][2] * Zws;
float zs = mA.M[2][0] * Xws + mA.M[2][1] * Yws + mA.M[2][2] * Zws;
float xd = mA.M[0][0] * Xwd + mA.M[0][1] * Ywd + mA.M[0][2] * Zwd;
float yd = mA.M[1][0] * Xwd + mA.M[1][1] * Ywd + mA.M[1][2] * Zwd;
float zd = mA.M[2][0] * Xwd + mA.M[2][1] * Ywd + mA.M[2][2] * Zwd;
m = Matrix3x3(
(xd / xs), 0, 0,
0, (yd / ys), 0,
0, 0, (zd / zs)
);
Matrix3x3 res = mAInv;
res *= m;
res *= mA;
m = res;
}
for (size_t i = 0; i < len; i++) {
float * xyz = input.GetPixelStart(i);
float x = xyz[0];
float y = xyz[1];
float z = xyz[2];
float xd = m.M[0][0] * x + m.M[0][1] * y + m.M[0][2] * z;
float yd = m.M[1][0] * x + m.M[1][1] * y + m.M[1][2] * z;
float zd = m.M[2][0] * x + m.M[2][1] * y + m.M[2][2] * z;
xyz[0] = xd;
xyz[1] = yd;
xyz[2] = zd;
}
}
最后
Image2d<uint8_t> ColorSpace::ConvertXyzToSRgb_D65(const Image2d<float> & input) {
size_t len = input.GetPixelsCount();
std::vector<uint8_t> data;
data.resize(len * 3);
for (size_t i = 0; i < len; i++) {
const float * xyz = input.GetPixelStart(i);
float x = xyz[0];
float y = xyz[1];
float z = xyz[2];
//sRGB
float r = 3.2404542f * x + -1.5371385f * y + -0.4985314f * z;
float g = -0.9692660f * x + 1.8760108f * y + 0.0415560f * z;
float b = 0.0556434f * x + -0.2040259f * y + 1.0572252f * z;
//color companding
//for sRGB
r = (r > 0.0031308f) ? 1.055f * std::pow(r, 1 / 2.4f) - 0.055f : 12.92f * r;
g = (g > 0.0031308f) ? 1.055f * std::pow(g, 1 / 2.4f) - 0.055f : 12.92f * g;
b = (b > 0.0031308f) ? 1.055f * std::pow(b, 1 / 2.4f) - 0.055f : 12.92f * b;
data[i * 3 + 0] = static_cast<uint8_t>(255.0f * std::clamp(r, 0.0f, 1.0f));
data[i * 3 + 1] = static_cast<uint8_t>(255.0f * std::clamp(g, 0.0f, 1.0f));
data[i * 3 + 2] = static_cast<uint8_t>(255.0f * std::clamp(b, 0.0f, 1.0f));
}
return Image2d<uint8_t>(input.GetWidth(),
input.GetHeight(),
std::move(data),
PixelFormat::RGB);
}
但是,与在图像查看器(启用了 ICC 配置文件的 IrfanView、Firefox 或 Photoshop)中打开的图像相比,结果图像不正确。
左边是它的外观。右边是我的输出。
没有任何颜色转换,图像加载为:
所以我的版本更接近 "how it should look",但不完全一样。
我不确定ConvertRgbToXyz
是否正确。我根据文档 http://www.color.org/specification/ICC1v43_2010-12.pdf - rXYZ 的第 9.2.44 节 -
This tag contains the first column in the matrix, which is used in matrix/TRC transforms.
我的 ICC 配置文件是这样的:
Header:
size = 25908 bytes
CMM = 'argl'
Version = 2.2.0
Device Class = Input
Color Space = RGB
Conn. Space = XYZ
Date, Time = 10 Mar 2017, 13:05:37
Platform = Microsoft
Flags = Not Embedded Profile, Use anywhere
Dev. Mnfctr. = 0x0
Dev. Model = 0x0
Dev. Attrbts = Reflective, Glossy, Positive, Color
Rndrng Intnt = Perceptual
Illuminant = 0.964203, 1.000000, 0.824905 [Lab 100.000000, 0.000498, -0.000436]
Creator = 'argl'
'desc': CoCa 10.3.2017
'cprt': Copyright_free
'dmnd': proserv 600i
'dmdd': Model
'wtpt': 0.768753, 0.822220, 0.665100 [Lab 92.672746, -4.781618, 1.218722]
'bkpt': 0.003693, 0.004395, 0.003387 [Lab 3.969564, -2.199059, 0.448583]
'rXYZ': 0.956482, 0.326324, 0.010391 [Lab 63.862187, 154.428754, 91.157829]
'gXYZ': 0.070267, 0.995911, -0.271652 [Lab 99.841662, -290.469249, 685.018939]
'bXYZ': 0.136230, -0.105103, 1.321716 [Lab -94.938734, 600.671989, -370.133480]
'rTRC': Curve is gamma of 1.234375
'gTRC': Curve is gamma of 1.296875
'bTRC': Curve is gamma of 1.312500
我找到问题了。
我使用错误的参考白色进行色彩调整。我使用的是来自 wtpt
的值,但应该使用 Illuminant
作为输入白色。
输出白色仍设置为 D65。