Преобразование цветового профиля - неправильное изображение

У меня есть изображение в формате jpg со встроенным профилем ICC. Я извлек 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);
}

Однако результирующее изображение некорректно по сравнению с изображением, открытым в средстве просмотра изображений (IrfanView с включенным профилем ICC, Firefox или Photoshop).

Слева, как это должно выглядеть. Правильно мой вывод.

введите здесь описание изображения

Без преобразования цвета изображение загружается как: введите описание изображения здесь

Так что мой вариант ближе к тому, "как это должно выглядеть", но не совсем туда.

Я не уверен, правильно ли ConvertRgbToXyz. Я беру значения rXYZ / gXYZ / bXYZ непосредственно из ICC на основе документации http://www.color.org/specification/ICC1v43_2010-12.pdf — раздел 9.2.44 для rXYZ —

Этот тег содержит первый столбец в матрице, который используется в преобразованиях матрицы/TRC.

Мой профиль 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

person Martin Perry    schedule 13.05.2020    source источник


Ответы (1)


Я нашел проблему.

Я использовал неправильный эталонный белый цвет для хроматической адаптации. Я использовал значение из wtpt, но вместо него следует использовать Illuminant в качестве входного белого.

Выходной белый по-прежнему установлен на D65.

person Martin Perry    schedule 14.05.2020