Convertire la frequenza luminosa in RGB?

Qualcuno sa di una formula per convertire una frequenza luminosa in un valore RGB?

Ecco una spiegazione dettagliata dell’intero processo di conversione: http://www.fourmilab.ch/documents/specrend/ . Codice sorgente incluso!

Per i ragazzi pigri (come me), ecco un’implementazione in Java del codice trovato nella risposta di @ user151323 (ovvero, una semplice traduzione dal codice pascal trovato in Spectra Lab Report ):

static private double Gamma = 0.80; static private double IntensityMax = 255; /** Taken from Earl F. Glynn's web page: * Spectra Lab Report * */ 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; } 

A proposito, questo funziona bene per me.

Idea generale:

  1. Usa le funzioni di corrispondenza dei colors CEI per convertire la lunghezza d’onda in colore XYZ .
  2. Converti XYZ in RGB
  3. Agganciare i componenti a [0..1] e moltiplicarli per 255 per adattarli all’intervallo di byte senza segno.

I passaggi 1 e 2 possono variare.

Esistono diverse funzioni di corrispondenza dei colors, disponibili come tabelle o come approssimazioni analitiche (suggerite da @Tarc e @Haochen Xie). Le tabelle sono le migliori se hai bisogno di risultati precisi regolari.

Non esiste uno spazio cromatico RGB singolo. Possono essere utilizzate matrici di trasformazione multiple e diversi tipi di correzione gamma.

Di seguito è riportato il codice C # che ho trovato di recente. Utilizza l’interpolazione lineare sulla tabella “osservatore standard CIE 1964” e matrice sRGB + correzione 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; } } 

Risultato per la gamma 400-700 nm:

inserisci la descrizione dell'immagine qui

Anche se questa è una vecchia domanda e già riceve una buona risposta, quando ho provato ad implementare tali funzionalità di conversione nella mia applicazione non ero soddisfatto degli algoritmi già elencati qui e ho fatto la mia ricerca, che mi ha dato un buon risultato. Quindi inserirò una nuova risposta.

Dopo alcune ricerche mi sono imbattuto in questo documento, Simple Approach Approximations to the CIE XYZ Colour Matching Functions , e ho provato ad adottare l’algoritmo Gaussian Fit multi-lobe introdotto nella mia applicazione. La carta descriveva solo le funzioni per convertire una lunghezza d’onda ai corrispondenti valori XYZ , quindi ho implementato una funzione per convertire XYZ in RGB nello spazio colore sRGB e combinarle. Il risultato è fantastico e vale la pena condividere:

 /** * 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 * 

* 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. *

* 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 }; }

il mio codice è scritto in Java 8, ma non dovrebbe essere difficile portarlo su versioni inferiori di Java e altri linguaggi.

Stai parlando della conversione da lunghezza d’onda a valore RGB.

Guarda qui, probabilmente risponderò alla tua domanda. Hanno un’utilità per fare ciò con il codice sorgente e qualche spiegazione.

WaveLengthToRGB

Immagino di poter seguire il mio commento con una risposta formale. L’opzione migliore è usare lo spazio colore HSV – sebbene la tonalità rappresenti la lunghezza d’onda, non è un confronto uno a uno.

Ho fatto un adattamento lineare di valori e frequenze di tonalità note (eliminando il rosso e il viola perché si estendono fino ad ora in valori di frequenza che distorcono un po ‘le cose) e ho ottenuto un’equazione di conversione approssimativa.

Va come
frequency (in THz) = 474 + (3/4) (Hue Angle (in gradi))

Ho cercato di guardarmi intorno e vedere se qualcuno ha elaborato questa equazione, ma non ho trovato nulla a partire da maggio 2010.

Metodo 1

Questo è un po ‘ripulito e testato versione C ++ 11 di @ haochen-xie. Ho anche aggiunto una funzione che converte il valore da 0 a 1 in una lunghezza d’onda nello spettro visibile che è utilizzabile con questo metodo. Puoi semplicemente mettere sotto un file di intestazione e usarlo senza alcuna dipendenza. Questa versione verrà mantenuta qui .

 #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(static_cast(dr * 0xFF) & 0xFF); g = static_cast(static_cast(dg * 0xFF) & 0xFF); b = static_cast(static_cast(db * 0xFF) & 0xFF); } /** * Convert XYZ to RGB in the sRGB color space * 

* 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. *

* 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

La trama di colors da 375 nm a 725 nm è la seguente:

inserisci la descrizione dell'immagine qui

Un problema con questo metodo è il fatto che funziona solo tra i 400-700 nm e al di fuori di esso cade bruscamente verso il nero. Un altro problema è il blu più stretto.

Per il confronto, di seguito sono riportati i colors di Vision FAQ su maxmax.com:

inserisci la descrizione dell'immagine qui

L'ho usato per visualizzare la mappa di profondità in cui ogni pixel rappresenta il valore di profondità in metri e questo è il seguente:

inserisci la descrizione dell'immagine qui

Metodo 2

Questo è implementato come parte della libreria bitmap_image single-header di 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((red == 0.0) ? red : round(intensity_max * std::pow(red * factor, gamma))); result.green = static_cast((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma))); result.blue = static_cast((blue == 0.0) ? blue : round(intensity_max * std::pow(blue * factor, gamma))); #undef round return result; } 

Il grafico della lunghezza d'onda da 375 a 725 nm è il seguente:

inserisci la descrizione dell'immagine qui

Quindi questo è più utilizzabile in 400-725 nm. Quando visualizzo la stessa mappa di profondità come nel metodo 1, ottengo qui sotto. C'è un ovvio problema di quelle linee nere che penso indichi un bug minore in questo codice che non ho guardato più profondamente. Anche le violette sono un po 'più strette in questo metodo che causa meno contrasto per oggetti lontani.

inserisci la descrizione dell'immagine qui