颜色配置文件转换 - 图像不正确

Color profile conversion - image not correct

我有嵌入了 ICC 配置文件的 jpg 图像。我已经从文件中提取了 ICC 配置文件,现在我需要将解压缩的图像转换为 sRGB。

我的公式基于http://www.brucelindbloom.com/index.html?Math.html

我使用这个算法:

  1. 从 RGB 转换图像 -> XYZ
  2. 在 XYZ 上进行色彩调整
  3. 转换 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。