Lichtfrequenz in RGB umwandeln?


Antworten:


44

Hier finden Sie eine detaillierte Erläuterung des gesamten Konvertierungsprozesses: http://www.fourmilab.ch/documents/specrend/ . Quellcode enthalten!


5
Und der Fourmilab-Artikel macht den wichtigen Punkt deutlich, dass einige Farben in RGB nicht darstellbar sind (helle Orangen sind ein gutes Beispiel), weil Sie keine beliebigen Lichtfarben "erzeugen" können, indem Sie drei Primärfarben addieren, was auch immer unsere Physiklehrer uns gesagt haben mögen ( gut meins hat). Schade, aber in der Praxis normalerweise nicht tödlich.
Francis Davey

1
Darüber hinaus: en.wikipedia.org/wiki/Srgb Der Artikel wurde geschrieben, bevor der sRGB-Standard weit verbreitet war. Beachten Sie auch den Satz "Berechnungen gehen von einem kolorimetrischen 2 ° -Standardbeobachter aus".
Dies

Es wäre schön, ein Beispiel für die Verwendung des Codes zu geben. Es erfordert Funktion als Argument, verwendet die Temperatur, um Farben und dergleichen zu berechnen. Man würde gerne wissen, was zu löschen und zu ändern ist, damit es funktioniert.
Tomáš Zato - Wiedereinsetzung Monica

2
Es ist anzumerken, dass nur eine kleine Teilmenge aller möglichen sichtbaren Wellenlängen im RGB-Farbraum exakt dargestellt werden kann. Der Konvertierungsprozess ist ziemlich kompliziert und mehrdeutig. Siehe physik.stackexchange.com/a/94446/5089 und physik.stackexchange.com/a/419628/5089
Violette Giraffe

28

Für faule Leute (wie mich) gibt es hier eine Implementierung des Codes in Java, der in der Antwort von @ user151323 enthalten ist (dh nur eine einfache Übersetzung des Pascal-Codes aus 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
Es scheint einen Fehler in Ihrem Code zu geben. Wenn die Wellenlänge beispielsweise 439,5 beträgt, gibt Ihre Funktion Schwarz zurück. Ich glaube, der ursprüngliche Code auf der Site hat mit ganzen Zahlen gearbeitet (ich kenne Pascal überhaupt nicht). Ich schlage vor , sich ändern Wavelength<=439zu Wavelength<440.
Hassedev

2
Du hast recht! Vielen Dank, dass Sie mich darauf hingewiesen haben :) Bereits korrigiert.
Tarc

Wird erwartet, dass RFB auf einigen Frequenzen wiederholt wird? (ROT): 652 - RGB (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - rgb (255, 0, 0) | 700 - rgb (255, 0, 0) | ...
Rodrigo Borba

14

Grund Idee:

  1. Verwenden Sie CEI-Farbanpassungsfunktionen , um die Wellenlänge in XYZ-Farbe umzuwandeln .
  2. Konvertieren Sie XYZ in RGB
  3. Schneiden Sie die Komponenten auf [0..1] und multiplizieren Sie sie mit 255, um in den vorzeichenlosen Bytebereich zu passen.

Die Schritte 1 und 2 können variieren.

Es gibt verschiedene Farbanpassungsfunktionen, die als Tabellen oder als analytische Näherungswerte verfügbar sind (vorgeschlagen von @Tarc und @Haochen Xie). Tabellen sind am besten geeignet, wenn Sie ein glattes, präzises Ergebnis benötigen.

Es gibt keinen einzelnen RGB-Farbraum. Es können mehrere Transformationsmatrizen und verschiedene Arten der Gammakorrektur verwendet werden.

Unten ist der C # -Code, den ich mir kürzlich ausgedacht habe. Es verwendet eine lineare Interpolation über die Tabelle "CIE 1964 Standard Observer" und eine sRGB-Matrix + Gammakorrektur .

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

Ergebnis für den Bereich von 400-700 nm:

Geben Sie hier die Bildbeschreibung ein


Das ist wirklich interessant für mich. Ich habe die Idee, so etwas zu verwenden, um eine normale Reaktion zu erzielen, aber ich verwende eine WXYZ-Reaktion, um die Reaktion von Tetrachromaten nachzuahmen, die einen vierten Kegel haben, der auf eine Frequenz reagiert, die weit genug von den anderen normalen drei Zapfentypen entfernt ist. Das könnte mich dazu bringen, Quellbilder aufzunehmen und auf die Unterschiede zu schließen, die sie sehen. Hinweis: Sie sehen keine neuen Farben. Es ist so, dass Lichter, die sich beispielsweise zu einem bestimmten Gelb mischen (Summe), für die meisten von uns mit einem Gelb einer bestimmten Frequenz identisch erscheinen, aber für sie würde sich das Licht nicht mischen zu diesem Gelb überhaupt.
Phorgan1

Natürlich hätte eine bestimmte RGB-Farbe auf viele Arten erreicht werden können. Das Grün eines Blattes könnte durch Herausfiltern von allem außer Grün entstehen, oder das Grün könnte herausgefiltert worden sein, aber die Nanoeigenschaften könnten dazu führen, dass Blau und Gelb reflektiert werden und mit dem Grün identisch aussehen. Gibt es eine Möglichkeit, anhand eines Bildes und nicht des Lichts zu unterscheiden?
Phorgan1

10

Obwohl dies eine alte Frage ist und bereits eine Handvoll guter Antworten erhält, war ich beim Versuch, eine solche Konvertierungsfunktion in meiner Anwendung zu implementieren, mit den hier bereits aufgeführten Algorithmen nicht zufrieden und habe meine eigenen Untersuchungen durchgeführt, die mir einige gute Ergebnisse gebracht haben. Also werde ich eine neue Antwort posten.

Nach einigen Recherchen stieß ich auf dieses Papier, Einfache analytische Annäherungen an die CIE XYZ-Farbanpassungsfunktionen , und versuchte, den eingeführten stückweisen Gaußschen Anpassungsalgorithmus mit mehreren Lappen in meine Anwendung zu übernehmen. In diesem Artikel wurden nur die Funktionen zum Konvertieren einer Wellenlänge in die entsprechenden XYZ-Werte beschrieben . Daher habe ich eine Funktion zum Konvertieren von XYZ in RGB im sRGB-Farbraum implementiert und kombiniert. Das Ergebnis ist fantastisch und es lohnt sich, es zu teilen:

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

Mein Code ist in Java 8 geschrieben, aber es sollte nicht schwierig sein, ihn auf niedrigere Versionen von Java und anderen Sprachen zu portieren.


1
@Baddack, Sie haben Recht: Es ist nur eine ausgefallene Möglichkeit, die berechneten Werte weiter zu transformieren. Ich kann mich nicht genau erinnern, aber ich denke, es wird zuerst eine Gammakorrektur angewendet und dann Werte außerhalb des Bereichs abgeschnitten. Vielleicht hätte ich es in einer separaten Methode machen lassen sollen, aber ich dachte nicht daran, den Code beim Schreiben zu teilen, und es war ein Spielzeugprojekt, in dem ich diese Konvertierung brauchte.
Haochen Xie

1
@Baddack Ich habe das Projekt ausgegraben, für das ich diese Konvertierung benötigt habe, und diesen Teil ohne Verwendung von Java 8 Lambda neu geschrieben, damit der Code klarer wird. Ich habe mich tatsächlich falsch daran erinnert, was der transferDoubleUnaryOperator getan hat (daher sind die Erklärungen in meinem vorherigen Kommentar nicht korrekt). Überprüfen Sie daher den neuen Code.
Haochen Xie

1
@Baddack Ich bin froh, dass der Code Ihnen hilft. und wenn es Ihnen nichts ausmacht, können Sie es bitte positiv bewerten, damit es möglicherweise mehr Menschen hilft?
Haochen Xie

1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2.4), dh c auf die Potenz von 1 / 2.4 erhöhen; 1.ist nur 1, aber der Typ wird doubleanstelle vonint
Haochen Xie

3
@ Ruslan Da dieser Algorithmus eine analytische Anpassung des CIE-Standardbeobachters ist (der als "genaues" Modell angesehen werden könnte), gibt es Fehler. Wenn Sie sich jedoch die Abbildung 1 auf Seite 7 ansehen (vergleiche (d) mit (f)), liefert diese Methode eine ziemlich genaue Annäherung. Besonders wenn Sie sich (f) ansehen, können Sie sehen, dass es auch im Standardmodell eine bläuliche Linie gibt. Auch die Farbwahrnehmung der reinen Lichtquelle variiert persönlich, so dass diese Fehlerquote wahrscheinlich vernachlässigbar ist.
Haochen Xie

7

Sie sprechen über die Konvertierung von Wellenlänge in einen RGB-Wert.

Schauen Sie hier, wird wahrscheinlich Ihre Frage beantworten. Sie haben ein Dienstprogramm, um dies mit dem Quellcode zu tun, sowie einige Erklärungen.

WaveLengthToRGB


1
Lesen Sie einfach dieselbe Seite "Es gibt keine eindeutige Eins-zu-Eins-Zuordnung zwischen Wellenlängen- und RGB-Werten" - Sie bleiben also bei einer Nachschlagetabelle und Heuristiken. Als ersten Schnitt würde ich mir die Konvertierung von HSV in RGB ansehen, da der "Farbton" von blau bis rot reicht. Mit möglicherweise geringfügiger Verschiebung, da im RGB-Bereich Rot + Blau = Violett und Violett die kürzeste sichtbare Wellenlänge haben.
Whatnick

3
ist es nicht praktisch das gleiche? freq = c / Wellenlänge
Mauricio Scheffer

1
@ Mauricio Scheffer Ja, es ist genau das gleiche.
Joseph Gordon

Dieser Bruton-Algorithmus ist eher ästhetisch als realistisch
Mykhal

8
@ Joseph Gordon - stimme überhaupt nicht zu. Stellen Sie sich einen grünlichen Strahl von 400 nm vor, der in Luft emittiert wird und auf die Wasseroberfläche trifft und sich dann im Wasser ausbreitet. Der Brechungskoeffizient von Wasser beträgt beispielsweise 1,33, sodass eine Strahlwellenlänge in Wasser jetzt 300 nm beträgt, was offensichtlich seine Farbe nicht ändert. Die Materie, die die Strahlen "koloriert", ist die Frequenz, nicht die Wellenlänge. In derselben Substanz (Vakuum, Luft, Wasser) werden Frequenzen (Farben) denselben Wellenlängen zugeordnet. In verschiedenen Medien - nicht.
Mbaitoff

3

Ich denke, ich könnte meinem Kommentar genauso gut eine formelle Antwort geben. Die beste Option ist die Verwendung des HSV-Farbraums. Obwohl der Farbton die Wellenlänge darstellt, handelt es sich nicht um einen Eins-zu-Eins-Vergleich.


1
Dein Link ist tot.
Ruslan

3

Ich habe eine bekannte Anpassung bekannter Farbtonwerte und Frequenzen durchgeführt (Rot und Violett fallen gelassen, weil sie sich in Frequenzwerten so weit erstrecken, dass sie die Dinge ein wenig verzerren), und ich habe eine grobe Umrechnungsgleichung erhalten.

Es geht wie
Frequenz (in THz) = 474 + (3/4) (Farbtonwinkel (in Grad))

Ich habe versucht, mich umzuschauen und festzustellen, ob sich jemand diese Gleichung ausgedacht hat, aber bis Mai 2010 habe ich nichts gefunden.


2

Methode 1

Dies ist etwas bereinigt und getestet C ++ 11-Version von @ haochen-xie. Ich habe auch eine Funktion hinzugefügt, die den Wert 0 in 1 in eine Wellenlänge im sichtbaren Spektrum umwandelt, die mit dieser Methode verwendet werden kann. Sie können einfach unten in eine Header-Datei einfügen und diese ohne Abhängigkeiten verwenden. Diese Version wird beibehalten hier .

#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

Die Darstellung der Farben von 375 nm bis 725 nm sieht wie folgt aus:

Geben Sie hier die Bildbeschreibung ein

Ein Problem bei dieser Methode ist die Tatsache, dass sie nur zwischen 400 und 700 nm funktioniert und außerhalb davon scharf auf Schwarz abfällt. Ein weiteres Problem ist das schmalere Blau.

Zum Vergleich sind unten die Farben aus den Vision-FAQ auf maxmax.com aufgeführt:

Geben Sie hier die Bildbeschreibung ein

Ich habe dies verwendet, um eine Tiefenkarte zu visualisieren, bei der jedes Pixel den Tiefenwert in Metern darstellt. Dies sieht wie folgt aus:

Geben Sie hier die Bildbeschreibung ein

Methode 2

Dies wird als Teil der Nur- Datei-Header-Bibliothek bitmap_image von Aeash Partow implementiert:

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

Die Darstellung der Wellenlänge von 375-725 nm sieht wie folgt aus:

Geben Sie hier die Bildbeschreibung ein

Dies ist also in 400-725nm besser verwendbar. Wenn ich dieselbe Tiefenkarte wie in Methode 1 visualisiere, komme ich unten. Es gibt ein offensichtliches Problem mit diesen schwarzen Linien, von denen ich denke, dass sie auf einen kleinen Fehler in diesem Code hinweisen, den ich nicht genauer untersucht habe. Auch Veilchen sind bei dieser Methode etwas schmaler, was für weit entfernte Objekte weniger Kontrast verursacht.

Geben Sie hier die Bildbeschreibung ein


0

Projizieren Sie den CIExy der Wellenlänge in Richtung des D65-Weiß auf den sRGB-Farbumfang

#!/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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.