Quelqu'un connaît-il une formule pour convertir une fréquence lumineuse en une valeur RVB?
Voici une explication détaillée de l'ensemble du processus de conversion: http://www.fourmilab.ch/documents/specrend/ . Code source inclus!
Pour les gars paresseux (comme moi), voici une implémentation en Java du code trouvé dans la réponse de @ user151323 (c'est-à-dire juste une simple traduction du code Pascal trouvé dans Rapport Spectra Lab ):
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;
}
Au fait, cela fonctionne bien pour moi.
Idée générale:
Les étapes 1 et 2 peuvent varier.
Il existe plusieurs fonctions de correspondance des couleurs, disponibles sous la forme tableaux ou sous forme d'approximations analytiques (suggérées par @Tarc et @Haochen Xie). Les tableaux sont meilleurs si vous avez besoin d'un résultat précis et précis.
Il n'y a pas d'espace colorimétrique RVB unique. Plusieurs matrices de transformation et différents types de correction gamma peuvent être utilisés.
Ci-dessous se trouve le code C # que j'ai trouvé récemment. Il utilise une interpolation linéaire sur la table "Observateur standard CIE 1964" et matrice sRGB + correction gamma .
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;
}
}
Résultat pour la gamme 400-700 nm:
Bien que cette question soit ancienne et qu'elle reçoive déjà quelques bonnes réponses, lorsque j'ai essayé d'implémenter une telle fonctionnalité de conversion dans mon application, je n'étais pas satisfait des algorithmes déjà répertoriés ici et j'ai fait mes propres recherches, ce qui m'a donné de bons résultats. Je vais donc poster une nouvelle réponse.
Après quelques recherches, je suis tombé sur cet article, Simple Analytic Approximations to the CIE XYZ Color Matching Functions , et j'ai essayé d'adopter l'algorithme d'ajustement gaussien par morceaux multi-lobes introduit dans mon application. Le document ne décrivait que les fonctions pour convertir une longueur d'onde en valeurs XYZ , j'ai donc implémenté une fonction pour convertir XYZ en RVB dans l'espace colorimétrique sRGB et les ai combinées. Le résultat est fantastique et mérite d'être partagé:
/**
* 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 };
}
mon code est écrit en Java 8, mais il ne devrait pas être difficile de le porter vers des versions inférieures de Java et d'autres langages.
Vous parlez de convertir de la longueur d'onde en une valeur RVB.
Regardez ici, répondra probablement à votre question. Ils ont un utilitaire pour le faire avec le code source ainsi que quelques explications.
Je suppose que je ferais aussi bien de suivre mon commentaire par une réponse formelle. La meilleure option consiste à utiliser espace colorimétrique HSV - bien que la teinte représente la longueur d'onde, il ne s'agit pas d'une comparaison un à un.
J'ai fait un ajustement linéaire des valeurs et des fréquences de teinte connues (abandonnant le rouge et le violet parce qu'elles s'étendent si loin dans les valeurs de fréquence qu'elles biaisent un peu les choses) et j'ai obtenu une équation de conversion approximative.
Ça va comme
fréquence (en THz) = 474 + (3/4) (Angle de teinte (en degrés))
J'ai essayé de regarder autour de moi et de voir si quelqu'un avait trouvé cette équation, mais je n'ai rien trouvé en mai 2010.
Méthode 1
Ceci est un peu nettoyé et testé la version C++ 11 de @ haochen-xie. J'ai également ajouté une fonction qui convertit la valeur 0 en 1 en une longueur d'onde dans le spectre visible utilisable avec cette méthode. Vous pouvez simplement mettre ci-dessous dans un fichier d'en-tête et l'utiliser sans aucune dépendance. Cette version sera maintenue ici .
#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
L'intrigue des couleurs de 375 nm à 725 nm ressemble à ci-dessous:
Un problème avec cette méthode est le fait qu'elle ne fonctionne qu'entre 400 et 700 nm et en dehors de cela, elle tombe fortement au noir. Un autre problème est le bleu plus étroit.
Pour comparaison, voici les couleurs de Vision FAQ sur maxmax.com:
Je l'ai utilisé pour visualiser la carte de profondeur où chaque pixel représente la valeur de profondeur en mètres et cela ressemble à ci-dessous:
Méthode 2
Ceci est implémenté dans le cadre de la bibliothèque bitmap_image en-tête de fichier unique par 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;
}
Le tracé de la longueur d'onde de 375 à 725 nm ressemble à ci-dessous:
C'est donc plus utilisable en 400-725nm. Lorsque je visualise la même carte de profondeur que dans la méthode 1, j'obtiens ci-dessous. Il y a un problème évident de ces lignes noires qui, je pense, indique un bug mineur dans ce code que je n'ai pas examiné plus en profondeur. Les violettes sont également un peu plus étroites dans cette méthode, ce qui provoque moins de contraste pour les objets éloignés.