Преобразование частоты света в RGB?
кто-нибудь знает какую-либо формулу для преобразования частоты света в значение RGB?
8 ответов:
вот подробное объяснение всего процесса преобразования:http://www.fourmilab.ch/documents/specrend/. исходный код включен!
для ленивых парней (как и я), вот реализация на java кода, найденного в @user151323 's answer (то есть, просто простой перевод из кода Паскаля, найденного в Отчет Лаборатории Спектров):
static private double Gamma = 0.80; static private 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; }
кстати, это прекрасно работает для меня.
общая идея:
- использовать функции подбора цветов CEI для преобразования длины волны к цвет XYZ.
- конвертировать XYZ в RGB
- закрепить компоненты на [0..1] и умножьте на 255, чтобы вписаться в диапазон байтов без знака.
шаги 1 и 2 могут различаться.
есть несколько функций подбора цветов, доступных как таблицы или как аналитические приближения (предложенные @Tarc и @Haochen Се). Таблицы лучше всего, если вам нужен гладкий точный результат.
нет единого цветового пространства RGB. несколько матриц преобразования и различные виды коррекции гаммы могут быть использованы.
Ниже приведен код C#, который я придумал недавно. Он использует линейную интерполяцию по таблице "Cie 1964 standard observer" и 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 Нм диапазон:
хотя это старый вопрос и уже получает несколько хороших ответов, когда я попытался реализовать такую функциональность преобразования в своем приложении, я не был удовлетворен алгоритмами, уже перечисленными здесь, и сделал свое собственное исследование, которое дало мне хороший результат. Поэтому я собираюсь опубликовать новый ответ.
после некоторых исследований я наткнулся на эту бумагу, Простые Аналитические Аппроксимации к функциям подбора цветов 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 и других языков.
вы говорите о конверсии длина волны к значению RGB.
смотрите сюда, вероятно, ответит на ваш вопрос. У вас есть утилита для этого с исходным кодом, а также некоторые объяснения.
Я думаю, что я мог бы также следить за моим комментарием с формальным ответом. Лучший вариант-использовать ВПГ цветовое пространство - хотя оттенок представляет длину волны, это не сравнение один к одному.
Я сделал линейную подгонку известных значений оттенков и частот (выпадая красный и фиолетовый, потому что они простираются так далеко в значениях частоты, что они немного искажают вещи), и я получил грубое уравнение преобразования.
звучит как
частота (в ТГц)=474+(3/4) (угол оттенка (в градусах))Я пытался посмотреть вокруг и посмотреть, если кто-то придумал это уравнение, но я ничего не нашел по состоянию на май 2010 года.
Способ 1
это немного очищено и протестировано C++11 версия @haochen-xie. Я также добавил функцию, которая преобразует значение 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
участок цветов от 375nm до 725nm выглядит так ниже:
одна проблема с этим методом заключается в том, что он работает только между 400-700nm и вне этого он резко падает до Черного. Другая проблема-более узкий синий цвет.
для сравнения, ниже приведены цвета из Vision FAQ по адресу 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, я получаю ниже. Существует очевидная проблема этих черных строки, которые я думаю, указывает на незначительную ошибку в этом коде, который я не смотрел более глубоко. Также Фиалки немного уже в этом методе, который вызывает меньше контраста для далеких объектов.