Преобразовать частоту света в RGB?

Ответы:

44

Вот подробное объяснение всего процесса преобразования: http://www.fourmilab.ch/documents/specrend/ . Исходный код включен!

Стивен Меса
источник
5
В статье Fourmilab подчеркивается, что некоторые цвета не могут быть представлены в RGB (яркий оранжевый - хороший пример), потому что вы не можете «создать» произвольные цвета света, сложив вместе три основных цвета, что бы ни говорили нам наши учителя физики ( ну мой сделал). Жалко, но на практике обычно не смертельно.
Фрэнсис Дэйви,
1
В дополнение к этому: en.wikipedia.org/wiki/Srgb Статья была написана до того, как стандарт sRGB получил широкое распространение. Также обратите внимание на фразу «Расчеты предполагают использование стандартного колориметрического наблюдателя 2 °», что означает, что следует использовать таблицу CIE 1931, содержащуюся в сопроводительном источнике к статье, а не CIE 1964.
GrayFace
Было бы неплохо привести пример использования кода. Он требует функции в качестве аргумента, использует температуру для вычисления цветов и тому подобное. Было бы приятно узнать, что удалить и изменить, чтобы это заработало.
Tomáš Zato - Reinstate Monica
2
Стоит отметить, что только небольшое подмножество всех возможных видимых длин волн может быть точно представлено в цветовом пространстве RGB. Процесс конвертации довольно сложен и неоднозначен. См. Physics.stackexchange.com/a/94446/5089 и Physics.stackexchange.com/a/419628/5089
Violet Giraffe
28

Для ленивых парней (вроде меня) вот реализация на java кода, найденного в ответе @ user151323 (то есть просто простой перевод кода паскала, найденного в Spectra Lab Report ):

static private final double Gamma = 0.80;
static private final double IntensityMax = 255;

/**
 * Taken from Earl F. Glynn's web page:
 * <a href="http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm">Spectra Lab Report</a>
 */
public static int[] waveLengthToRGB(double Wavelength) {
    double factor;
    double Red, Green, Blue;

    if((Wavelength >= 380) && (Wavelength < 440)) {
        Red = -(Wavelength - 440) / (440 - 380);
        Green = 0.0;
        Blue = 1.0;
    } else if((Wavelength >= 440) && (Wavelength < 490)) {
        Red = 0.0;
        Green = (Wavelength - 440) / (490 - 440);
        Blue = 1.0;
    } else if((Wavelength >= 490) && (Wavelength < 510)) {
        Red = 0.0;
        Green = 1.0;
        Blue = -(Wavelength - 510) / (510 - 490);
    } else if((Wavelength >= 510) && (Wavelength < 580)) {
        Red = (Wavelength - 510) / (580 - 510);
        Green = 1.0;
        Blue = 0.0;
    } else if((Wavelength >= 580) && (Wavelength < 645)) {
        Red = 1.0;
        Green = -(Wavelength - 645) / (645 - 580);
        Blue = 0.0;
    } else if((Wavelength >= 645) && (Wavelength < 781)) {
        Red = 1.0;
        Green = 0.0;
        Blue = 0.0;
    } else {
        Red = 0.0;
        Green = 0.0;
        Blue = 0.0;
    }

    // Let the intensity fall off near the vision limits

    if((Wavelength >= 380) && (Wavelength < 420)) {
        factor = 0.3 + 0.7 * (Wavelength - 380) / (420 - 380);
    } else if((Wavelength >= 420) && (Wavelength < 701)) {
        factor = 1.0;
    } else if((Wavelength >= 701) && (Wavelength < 781)) {
        factor = 0.3 + 0.7 * (780 - Wavelength) / (780 - 700);
    } else {
        factor = 0.0;
    }


    int[] rgb = new int[3];

    // Don't want 0^x = 1 for x <> 0
    rgb[0] = Red == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Red * factor, Gamma));
    rgb[1] = Green == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Green * factor, Gamma));
    rgb[2] = Blue == 0.0 ? 0 : (int)Math.round(IntensityMax * Math.pow(Blue * factor, Gamma));

    return rgb;
}
ТАКП
источник
3
Похоже, в вашем коде есть ошибка. Если длина волны, например, 439,5, ваша функция вернет черный цвет. Исходный код на сайте, я полагаю, работал с целыми числами (я вообще не знаю паскаль). Предлагаю поменять Wavelength<=439на Wavelength<440.
Hassedev
2
Ты прав! Спасибо, что указали мне на это :) Уже исправили.
Tarc 02
Ожидается ли повторный RFB на некоторых частотах? (КРАСНЫЙ): 652 - rgb (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - RGB (255, 0, 0) | 700 - RGB (255, 0, 0) | ...
Родриго Борба
14

Главная идея:

  1. Используйте функции согласования цветов CEI для преобразования длины волны в цвет XYZ .
  2. Преобразовать XYZ в RGB
  3. Обрезать компоненты до [0..1] и умножить на 255, чтобы соответствовать диапазону байтов без знака.

Шаги 1 и 2 могут отличаться.

Существует несколько функций сопоставления цветов, доступных в виде таблиц или аналитических приближений (предложенных @Tarc и @Haochen Xie). Таблицы лучше всего, если вам нужен точный результат.

Единого цветового пространства RGB не существует. Могут использоваться множественные матрицы преобразования и различные виды гамма-коррекции.

Ниже приведен код C #, который я недавно придумал. Он использует линейную интерполяцию по таблице «Стандартный наблюдатель CIE 1964» и матрицу sRGB + гамма-коррекцию .

static class RgbCalculator {

    const int
         LEN_MIN = 380,
         LEN_MAX = 780,
         LEN_STEP = 5;

    static readonly double[]
        X = {
                0.000160, 0.000662, 0.002362, 0.007242, 0.019110, 0.043400, 0.084736, 0.140638, 0.204492, 0.264737,
                0.314679, 0.357719, 0.383734, 0.386726, 0.370702, 0.342957, 0.302273, 0.254085, 0.195618, 0.132349,
                0.080507, 0.041072, 0.016172, 0.005132, 0.003816, 0.015444, 0.037465, 0.071358, 0.117749, 0.172953,
                0.236491, 0.304213, 0.376772, 0.451584, 0.529826, 0.616053, 0.705224, 0.793832, 0.878655, 0.951162,
                1.014160, 1.074300, 1.118520, 1.134300, 1.123990, 1.089100, 1.030480, 0.950740, 0.856297, 0.754930,
                0.647467, 0.535110, 0.431567, 0.343690, 0.268329, 0.204300, 0.152568, 0.112210, 0.081261, 0.057930,
                0.040851, 0.028623, 0.019941, 0.013842, 0.009577, 0.006605, 0.004553, 0.003145, 0.002175, 0.001506,
                0.001045, 0.000727, 0.000508, 0.000356, 0.000251, 0.000178, 0.000126, 0.000090, 0.000065, 0.000046,
                0.000033
            },

        Y = {
                0.000017, 0.000072, 0.000253, 0.000769, 0.002004, 0.004509, 0.008756, 0.014456, 0.021391, 0.029497,
                0.038676, 0.049602, 0.062077, 0.074704, 0.089456, 0.106256, 0.128201, 0.152761, 0.185190, 0.219940,
                0.253589, 0.297665, 0.339133, 0.395379, 0.460777, 0.531360, 0.606741, 0.685660, 0.761757, 0.823330,
                0.875211, 0.923810, 0.961988, 0.982200, 0.991761, 0.999110, 0.997340, 0.982380, 0.955552, 0.915175,
                0.868934, 0.825623, 0.777405, 0.720353, 0.658341, 0.593878, 0.527963, 0.461834, 0.398057, 0.339554,
                0.283493, 0.228254, 0.179828, 0.140211, 0.107633, 0.081187, 0.060281, 0.044096, 0.031800, 0.022602,
                0.015905, 0.011130, 0.007749, 0.005375, 0.003718, 0.002565, 0.001768, 0.001222, 0.000846, 0.000586,
                0.000407, 0.000284, 0.000199, 0.000140, 0.000098, 0.000070, 0.000050, 0.000036, 0.000025, 0.000018,
                0.000013
            },

        Z = {
                0.000705, 0.002928, 0.010482, 0.032344, 0.086011, 0.197120, 0.389366, 0.656760, 0.972542, 1.282500,
                1.553480, 1.798500, 1.967280, 2.027300, 1.994800, 1.900700, 1.745370, 1.554900, 1.317560, 1.030200,
                0.772125, 0.570060, 0.415254, 0.302356, 0.218502, 0.159249, 0.112044, 0.082248, 0.060709, 0.043050,
                0.030451, 0.020584, 0.013676, 0.007918, 0.003988, 0.001091, 0.000000, 0.000000, 0.000000, 0.000000,
                0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
                0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
                0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
                0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
                0.000000
            };

    static readonly double[]
        MATRIX_SRGB_D65 = {
             3.2404542, -1.5371385, -0.4985314,
            -0.9692660,  1.8760108,  0.0415560,
             0.0556434, -0.2040259,  1.0572252
        };

    public static byte[] Calc(double len) {
        if(len < LEN_MIN || len > LEN_MAX)
            return new byte[3];

        len -= LEN_MIN;
        var index = (int)Math.Floor(len / LEN_STEP);
        var offset = len - LEN_STEP * index;

        var x = Interpolate(X, index, offset);
        var y = Interpolate(Y, index, offset);
        var z = Interpolate(Z, index, offset);

        var m = MATRIX_SRGB_D65;

        var r = m[0] * x + m[1] * y + m[2] * z;
        var g = m[3] * x + m[4] * y + m[5] * z;
        var b = m[6] * x + m[7] * y + m[8] * z;

        r = Clip(GammaCorrect_sRGB(r));
        g = Clip(GammaCorrect_sRGB(g));
        b = Clip(GammaCorrect_sRGB(b));

        return new[] { 
            (byte)(255 * r),
            (byte)(255 * g),
            (byte)(255 * b)
        };
    }

    static double Interpolate(double[] values, int index, double offset) {
        if(offset == 0)
            return values[index];

        var x0 = index * LEN_STEP;
        var x1 = x0 + LEN_STEP;
        var y0 = values[index];
        var y1 = values[1 + index];

        return y0 + offset * (y1 - y0) / (x1 - x0);
    }

    static double GammaCorrect_sRGB(double c) {
        if(c <= 0.0031308)
            return 12.92 * c;

        var a = 0.055;
        return (1 + a) * Math.Pow(c, 1 / 2.4) - a;
    }

    static double Clip(double c) {
        if(c < 0)
            return 0;
        if(c > 1)
            return 1;
        return c;
    }
}

Результат для диапазона 400-700 нм:

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

amartynov
источник
Мне это действительно интересно. У меня есть идея использовать что-то подобное, чтобы дать нормальный ответ, но использовать ответ WXYZ, чтобы имитировать реакцию тетрахроматов, у которых есть четвертый конус, который реагирует на частоту, достаточно далекую от любого из других трех типов конусов. Это может позволить мне взять исходные изображения и сделать вывод о различиях, которые они видят. NB, они не видят новых цветов, это то, что свет, который смешивается, (сумма), например, с определенным желтым, кажется большинству из нас идентичным желтому с определенной частотой, но для них свет не смешивается к тому желтому вообще.
phorgan1
Конечно, для определенного цвета RGB это можно было получить разными способами. Зеленый цвет листа может быть результатом фильтрации всего, кроме зеленого, или зеленый может быть отфильтрован, но нано-характеристики могут привести к тому, что синий и желтый будут отражаться и выглядеть идентично зеленому. Могу ли я как-то отличить изображение, а не свет?
phorgan1
10

Хотя это старый вопрос и уже получил несколько хороших ответов, когда я попытался реализовать такую ​​функцию преобразования в своем приложении, меня не удовлетворили алгоритмы, уже перечисленные здесь, и я провел собственное исследование, которое дало мне хороший результат. Итак, я отправлю новый ответ.

После некоторых исследований я наткнулся на эту статью « Простые аналитические приближения к функциям согласования цветов CIE XYZ» и попытался применить представленный алгоритм кусочно-гауссовой подгонки с несколькими лепестками в своем приложении. В документе описаны только функции преобразования длины волны в соответствующие значения XYZ , поэтому я реализовал функцию преобразования XYZ в RGB в цветовом пространстве sRGB и объединил их. Результат фантастический, и стоит поделиться:

/**
 * Convert a wavelength in the visible light spectrum to a RGB color value that is suitable to be displayed on a
 * monitor
 *
 * @param wavelength wavelength in nm
 * @return RGB color encoded in int. each color is represented with 8 bits and has a layout of
 * 00000000RRRRRRRRGGGGGGGGBBBBBBBB where MSB is at the leftmost
 */
public static int wavelengthToRGB(double wavelength){
    double[] xyz = cie1931WavelengthToXYZFit(wavelength);
    double[] rgb = srgbXYZ2RGB(xyz);

    int c = 0;
    c |= (((int) (rgb[0] * 0xFF)) & 0xFF) << 16;
    c |= (((int) (rgb[1] * 0xFF)) & 0xFF) << 8;
    c |= (((int) (rgb[2] * 0xFF)) & 0xFF) << 0;

    return c;
}

/**
 * Convert XYZ to RGB in the sRGB color space
 * <p>
 * The conversion matrix and color component transfer function is taken from http://www.color.org/srgb.pdf, which
 * follows the International Electrotechnical Commission standard IEC 61966-2-1 "Multimedia systems and equipment -
 * Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB"
 *
 * @param xyz XYZ values in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
 * @return RGB values in a double array, in the order of R, G, B. each value in the range of [0.0, 1.0]
 */
public static double[] srgbXYZ2RGB(double[] xyz) {
    double x = xyz[0];
    double y = xyz[1];
    double z = xyz[2];

    double rl =  3.2406255 * x + -1.537208  * y + -0.4986286 * z;
    double gl = -0.9689307 * x +  1.8757561 * y +  0.0415175 * z;
    double bl =  0.0557101 * x + -0.2040211 * y +  1.0569959 * z;

    return new double[] {
            srgbXYZ2RGBPostprocess(rl),
            srgbXYZ2RGBPostprocess(gl),
            srgbXYZ2RGBPostprocess(bl)
    };
}

/**
 * helper function for {@link #srgbXYZ2RGB(double[])}
 */
private static double srgbXYZ2RGBPostprocess(double c) {
    // clip if c is out of range
    c = c > 1 ? 1 : (c < 0 ? 0 : c);

    // apply the color component transfer function
    c = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1. / 2.4) - 0.055;

    return c;
}

/**
 * A multi-lobe, piecewise Gaussian fit of CIE 1931 XYZ Color Matching Functions by Wyman el al. from Nvidia. The
 * code here is adopted from the Listing 1 of the paper authored by Wyman et al.
 * <p>
 * Reference: Chris Wyman, Peter-Pike Sloan, and Peter Shirley, Simple Analytic Approximations to the CIE XYZ Color
 * Matching Functions, Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 1-11, 2013.
 *
 * @param wavelength wavelength in nm
 * @return XYZ in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
 */
public static double[] cie1931WavelengthToXYZFit(double wavelength) {
    double wave = wavelength;

    double x;
    {
        double t1 = (wave - 442.0) * ((wave < 442.0) ? 0.0624 : 0.0374);
        double t2 = (wave - 599.8) * ((wave < 599.8) ? 0.0264 : 0.0323);
        double t3 = (wave - 501.1) * ((wave < 501.1) ? 0.0490 : 0.0382);

        x =   0.362 * Math.exp(-0.5 * t1 * t1)
            + 1.056 * Math.exp(-0.5 * t2 * t2)
            - 0.065 * Math.exp(-0.5 * t3 * t3);
    }

    double y;
    {
        double t1 = (wave - 568.8) * ((wave < 568.8) ? 0.0213 : 0.0247);
        double t2 = (wave - 530.9) * ((wave < 530.9) ? 0.0613 : 0.0322);

        y =   0.821 * Math.exp(-0.5 * t1 * t1)
            + 0.286 * Math.exp(-0.5 * t2 * t2);
    }

    double z;
    {
        double t1 = (wave - 437.0) * ((wave < 437.0) ? 0.0845 : 0.0278);
        double t2 = (wave - 459.0) * ((wave < 459.0) ? 0.0385 : 0.0725);

        z =   1.217 * Math.exp(-0.5 * t1 * t1)
            + 0.681 * Math.exp(-0.5 * t2 * t2);
    }

    return new double[] { x, y, z };
}

мой код написан на Java 8, но его несложно перенести на более ранние версии Java и другие языки.

Haochen Xie
источник
1
@Baddack, вы правы: это просто замечательный способ сделать некоторые дальнейшие преобразования вычисленных значений. Я не могу вспомнить точно, но я думаю, что сначала применяется гамма-коррекция, а затем значения вырезаются за пределы диапазона. Возможно, мне следовало бы сделать это отдельным методом, но на самом деле я не думал о том, чтобы делиться кодом во время его написания, и это был игрушечный проект, в котором мне было нужно это преобразование.
Haochen Xie
1
@Baddack Я откопал проект, в котором мне было нужно это преобразование, и переписал эту часть без использования лямбда-выражения java 8, чтобы код был более понятным. На самом деле я неправильно вспомнил о том, что transferделал DoubleUnaryOperator (поэтому объяснение в моем предыдущем комментарии неверно), поэтому проверьте новый код.
Haochen Xie
1
@Baddack, я рад, что код тебе помогает. и если вы не возражаете, не могли бы вы проголосовать за него, чтобы он потенциально мог помочь большему количеству людей?
Haochen Xie
1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2.4), т.е. возвести c в степень 1 / 2.4; 1.всего 1, но тип будет doubleвместоint
Haochen Xie
3
@Ruslan, поскольку этот алгоритм является аналитической подгонкой стандартного наблюдателя CIE (который можно рассматривать как "точную" модель), есть ошибки. Но из статьи, если вы посмотрите на рисунок 1 на странице 7 (сравните (d) с (f)), этот метод дает довольно близкое приближение. Особенно если вы посмотрите на (f), вы увидите, что даже в стандартной модели есть голубоватая линия. Кроме того, восприятие цвета от чистого источника света индивидуально, поэтому этот уровень ошибки, вероятно, незначителен.
Haochen Xie
7

Вы говорите о преобразовании длины волны в значение RGB.

Посмотрите сюда, наверняка ответите на ваш вопрос. У вас есть утилита для этого с исходным кодом, а также некоторые пояснения.

WaveLengthToRGB


источник
1
Просто прочитав ту же страницу «Не существует однозначного однозначного соответствия между длиной волны и значениями RGB» - так хорошо, что вы застряли с таблицей поиска и эвристикой. В качестве первого варианта я бы посмотрел на преобразование HSV в RGB, поскольку оттенок варьируется от синего до красного. С возможным небольшим сдвигом, поскольку в области RGB красный + синий = фиолетовый, а фиолетовый имеет самую короткую видимую длину волны.
whatnick
3
разве это не то же самое? freq = c / wavelength
Маурисио Шеффер,
1
@Mauricio Scheffer Да, это ТОЧНО то же самое.
Джозеф Гордон
этот алгоритм Брутона скорее эстетичен, чем реалистичен
михал
8
@ Джозеф Гордон - Совершенно не согласен. Представьте, что зеленоватый луч 400 нм, испускаемый в воздухе, ударяется о поверхность воды и затем распространяется в воде. Коэффициент преломления воды составляет, скажем, 1,33, поэтому длина волны луча в воде теперь составляет 300 нм, что, очевидно, не меняет ее цвет. "Раскрашивает" лучи частота, а не длина волны. В одном и том же веществе (вакуум, воздух, вода) частоты (цвета) соответствуют одной и той же длине волны. В разных СМИ - нет.
mbaitoff
3

Думаю, я мог бы дополнить свой комментарий официальным ответом. Лучший вариант - использовать цветовое пространство HSV - хотя оттенок представляет длину волны, это не однозначное сравнение.

whatnick
источник
1
Ваша ссылка мертва.
Руслан
3

Я провел линейную аппроксимацию известных значений оттенков и частот (исключив красный и фиолетовый, потому что они настолько сильно расширяются в значениях частоты, что немного искажают вещи), и я получил грубое уравнение преобразования.

Это похоже на
частоту (в ТГц) = 474 + (3/4) (Угол оттенка (в градусах))

Я попытался осмотреться и посмотреть, придумал ли кто-нибудь это уравнение, но по состоянию на май 2010 года ничего не нашел.

Дэвид Эльм
источник
2

Способ 1

Это немного очищенная и протестированная версия @ haochen-xie для C ++ 11. Я также добавил функцию, которая преобразует значение 0 в 1 в длину волны в видимом спектре, которую можно использовать с этим методом. Вы можете просто поместить ниже в один файл заголовка и использовать его без каких-либо зависимостей. Эта версия будет поддерживаться здесь .

#ifndef common_utils_OnlineStats_hpp
#define common_utils_OnlineStats_hpp

namespace common_utils {

class ColorUtils {
public:

    static void valToRGB(double val0To1, unsigned char& r, unsigned char& g, unsigned char& b)
    {
        //actual visible spectrum is 375 to 725 but outside of 400-700 things become too dark
        wavelengthToRGB(val0To1 * (700 - 400) + 400, r, g, b);
    }

    /**
    * Convert a wavelength in the visible light spectrum to a RGB color value that is suitable to be displayed on a
    * monitor
    *
    * @param wavelength wavelength in nm
    * @return RGB color encoded in int. each color is represented with 8 bits and has a layout of
    * 00000000RRRRRRRRGGGGGGGGBBBBBBBB where MSB is at the leftmost
    */
    static void wavelengthToRGB(double wavelength, unsigned char& r, unsigned char& g, unsigned char& b) {
        double x, y, z;
        cie1931WavelengthToXYZFit(wavelength, x, y, z);
        double dr, dg, db;
        srgbXYZ2RGB(x, y, z, dr, dg, db);

        r = static_cast<unsigned char>(static_cast<int>(dr * 0xFF) & 0xFF);
        g = static_cast<unsigned char>(static_cast<int>(dg * 0xFF) & 0xFF);
        b = static_cast<unsigned char>(static_cast<int>(db * 0xFF) & 0xFF);
    }

    /**
    * Convert XYZ to RGB in the sRGB color space
    * <p>
    * The conversion matrix and color component transfer function is taken from http://www.color.org/srgb.pdf, which
    * follows the International Electrotechnical Commission standard IEC 61966-2-1 "Multimedia systems and equipment -
    * Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB"
    *
    * @param xyz XYZ values in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
    * @return RGB values in a double array, in the order of R, G, B. each value in the range of [0.0, 1.0]
    */
    static void srgbXYZ2RGB(double x, double y, double z, double& r, double& g, double& b) {
        double rl = 3.2406255 * x + -1.537208  * y + -0.4986286 * z;
        double gl = -0.9689307 * x + 1.8757561 * y + 0.0415175 * z;
        double bl = 0.0557101 * x + -0.2040211 * y + 1.0569959 * z;

        r = srgbXYZ2RGBPostprocess(rl);
        g = srgbXYZ2RGBPostprocess(gl);
        b = srgbXYZ2RGBPostprocess(bl);
    }

    /**
    * helper function for {@link #srgbXYZ2RGB(double[])}
    */
    static double srgbXYZ2RGBPostprocess(double c) {
        // clip if c is out of range
        c = c > 1 ? 1 : (c < 0 ? 0 : c);

        // apply the color component transfer function
        c = c <= 0.0031308 ? c * 12.92 : 1.055 * std::pow(c, 1. / 2.4) - 0.055;

        return c;
    }

    /**
    * A multi-lobe, piecewise Gaussian fit of CIE 1931 XYZ Color Matching Functions by Wyman el al. from Nvidia. The
    * code here is adopted from the Listing 1 of the paper authored by Wyman et al.
    * <p>
    * Reference: Chris Wyman, Peter-Pike Sloan, and Peter Shirley, Simple Analytic Approximations to the CIE XYZ Color
    * Matching Functions, Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 1-11, 2013.
    *
    * @param wavelength wavelength in nm
    * @return XYZ in a double array in the order of X, Y, Z. each value in the range of [0.0, 1.0]
    */
    static void cie1931WavelengthToXYZFit(double wavelength, double& x, double& y, double& z) {
        double wave = wavelength;

        {
            double t1 = (wave - 442.0) * ((wave < 442.0) ? 0.0624 : 0.0374);
            double t2 = (wave - 599.8) * ((wave < 599.8) ? 0.0264 : 0.0323);
            double t3 = (wave - 501.1) * ((wave < 501.1) ? 0.0490 : 0.0382);

            x = 0.362 * std::exp(-0.5 * t1 * t1)
                + 1.056 * std::exp(-0.5 * t2 * t2)
                - 0.065 * std::exp(-0.5 * t3 * t3);
        }

        {
            double t1 = (wave - 568.8) * ((wave < 568.8) ? 0.0213 : 0.0247);
            double t2 = (wave - 530.9) * ((wave < 530.9) ? 0.0613 : 0.0322);

            y = 0.821 * std::exp(-0.5 * t1 * t1)
                + 0.286 * std::exp(-0.5 * t2 * t2);
        }

        {
            double t1 = (wave - 437.0) * ((wave < 437.0) ? 0.0845 : 0.0278);
            double t2 = (wave - 459.0) * ((wave < 459.0) ? 0.0385 : 0.0725);

            z = 1.217 * std::exp(-0.5 * t1 * t1)
                + 0.681 * std::exp(-0.5 * t2 * t2);
        }
    }

};

} //namespace

#endif

График цветов от 375 до 725 нм выглядит следующим образом:

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

Одна из проблем этого метода заключается в том, что он работает только в диапазоне 400-700 нм, а за пределами этого диапазона резко падает до черного. Другой вопрос - более узкий синий.

Для сравнения ниже приведены цвета из FAQ по Vision на maxmax.com:

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

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

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

Способ 2

Это реализовано как часть библиотеки одного файла bitmap_image только для заголовков от Aeash Partow:

inline rgb_t convert_wave_length_nm_to_rgb(const double wave_length_nm)
{
   // Credits: Dan Bruton http://www.physics.sfasu.edu/astro/color.html
   double red   = 0.0;
   double green = 0.0;
   double blue  = 0.0;

   if ((380.0 <= wave_length_nm) && (wave_length_nm <= 439.0))
   {
      red   = -(wave_length_nm - 440.0) / (440.0 - 380.0);
      green = 0.0;
      blue  = 1.0;
   }
   else if ((440.0 <= wave_length_nm) && (wave_length_nm <= 489.0))
   {
      red   = 0.0;
      green = (wave_length_nm - 440.0) / (490.0 - 440.0);
      blue  = 1.0;
   }
   else if ((490.0 <= wave_length_nm) && (wave_length_nm <= 509.0))
   {
      red   = 0.0;
      green = 1.0;
      blue  = -(wave_length_nm - 510.0) / (510.0 - 490.0);
   }
   else if ((510.0 <= wave_length_nm) && (wave_length_nm <= 579.0))
   {
      red   = (wave_length_nm - 510.0) / (580.0 - 510.0);
      green = 1.0;
      blue  = 0.0;
   }
   else if ((580.0 <= wave_length_nm) && (wave_length_nm <= 644.0))
   {
      red   = 1.0;
      green = -(wave_length_nm - 645.0) / (645.0 - 580.0);
      blue  = 0.0;
   }
   else if ((645.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
   {
      red   = 1.0;
      green = 0.0;
      blue  = 0.0;
   }

   double factor = 0.0;

   if ((380.0 <= wave_length_nm) && (wave_length_nm <= 419.0))
      factor = 0.3 + 0.7 * (wave_length_nm - 380.0) / (420.0 - 380.0);
   else if ((420.0 <= wave_length_nm) && (wave_length_nm <= 700.0))
      factor = 1.0;
   else if ((701.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
      factor = 0.3 + 0.7 * (780.0 - wave_length_nm) / (780.0 - 700.0);
   else
      factor = 0.0;

   rgb_t result;

   const double gamma         =   0.8;
   const double intensity_max = 255.0;

   #define round(d) std::floor(d + 0.5)

   result.red   = static_cast<unsigned char>((red   == 0.0) ? red   : round(intensity_max * std::pow(red   * factor, gamma)));
   result.green = static_cast<unsigned char>((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma)));
   result.blue  = static_cast<unsigned char>((blue  == 0.0) ? blue  : round(intensity_max * std::pow(blue  * factor, gamma)));

   #undef round

   return result;
}

График длины волны 375-725 нм выглядит следующим образом:

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

Так что это более удобно при 400-725 нм. Когда я визуализирую ту же карту глубины, что и в методе 1, я попадаю ниже. Есть очевидная проблема с этими черными линиями, которые, как мне кажется, указывают на незначительную ошибку в этом коде, которую я не рассматривал более глубоко. Кроме того, в этом методе фиолетовый цвет немного уже, что снижает контраст для удаленных объектов.

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

Шитал Шах
источник
0

Спроецируйте CIExy длины волны в сторону белого D65 на гамму sRGB

#!/usr/bin/ghci
ångstrømsfromTHz terahertz = 2997924.58 / terahertz
tristimulusXYZfromÅngstrøms å=map(sum.map(stimulus))[
 [[1056,5998,379,310],[362,4420,160,267],[-65,5011,204,262]],
 [[821,5688,469,405],[286,5309,163,311]],
 [[1217,4370,118,360],[681,4590,260,138]]]
 where stimulus[ω,μ,ς,σ]=ω/1000*exp(-((å-μ)/if å<μ then ς else σ)^2/2)

standardRGBfromTristimulusXYZ xyz=
 map(gamma.sum.zipWith(*)(gamutConfine xyz))[
 [3.2406,-1.5372,-0.4986],[-0.9689,1.8758,0.0415],[0.0557,-0.2040,1.057]]
gamma u=if u<=0.0031308 then 12.92*u else (u**(5/12)*211-11)/200
[red,green,blue,black]=
 [[0.64,0.33],[0.3,0.6],[0.15,0.06],[0.3127,0.3290,0]]
ciexyYfromXYZ xyz=if xyz!!1==0 then black else map(/sum xyz)xyz
cieXYZfromxyY[x,y,l]=if y==0 then black else[x*l/y,l,(1-x-y)*l/y]
gamutConfine xyz=last$xyz:[cieXYZfromxyY[x0+t*(x1-x0),y0+t*(y1-y0),xyz!!1]|
 x0:y0:_<-[black],x1:y1:_<-[ciexyYfromXYZ xyz],i<-[0..2],
 [x2,y2]:[x3,y3]:_<-[drop i[red,green,blue,red]],
 det<-[(x0-x1)*(y2-y3)-(y0-y1)*(x2-x3)],
 t <-[((x0-x2)*(y2-y3)-(y0-y2)*(x2-x3))/det|det/=0],0<=t,t<=1]

sRGBfromÅ=standardRGBfromTristimulusXYZ.tristimulusXYZfromÅngstrøms
x s rgb=concat["\ESC[48;2;",
               intercalate";"$map(show.(17*).round.(15*).max 0.min 1)rgb,
               "m",s,"\ESC[49m"]
spectrum=concatMap(x" ".sRGBfromÅ)$takeWhile(<7000)$iterate(+60)4000
main=putStrLn spectrum
Роман Чиборра
источник