Konwertować częstotliwość światła na RGB?

Odpowiedzi:

44

Oto szczegółowe wyjaśnienie całego procesu konwersji: http://www.fourmilab.ch/documents/specrend/ . Zawiera kod źródłowy!

Stephen Mesa
źródło
5
A artykuł Fourmilab zwraca uwagę na to, że niektórych kolorów nie da się przedstawić w RGB (dobrym przykładem są jasne pomarańcze), ponieważ nie można „stworzyć” dowolnych kolorów światła, dodając razem trzy kolory podstawowe, bez względu na to, co powiedzieli nam nasi nauczyciele fizyki ( dobrze mój). Szkoda, ale w praktyce zwykle nie kończy się to śmiercią.
Francis Davey
1
Oprócz tego: en.wikipedia.org/wiki/Srgb Artykuł został napisany zanim standard sRGB został powszechnie przyjęty. Zwróć również uwagę na frazę „Obliczenia zakładają standardowy obserwator kolorymetryczny 2 °”, co oznacza, że ​​należy użyć tabeli CIE 1931 znajdującej się w źródle dołączonym do artykułu, a nie CIE 1964.
GrayFace
Byłoby miło podać przykład korzystania z kodu. Wymaga funkcji jako argumentu, używa temperatury do obliczania kolorów i tym podobnych rzeczy. Byłoby szczęśliwy, gdybyś wiedział, co usunąć i zmienić, aby działało.
Tomáš Zato - Przywróć Monikę
2
Warto zauważyć, że tylko niewielki podzbiór wszystkich możliwych widzialnych długości fal można dokładnie odwzorować w przestrzeni kolorów RGB. Proces konwersji jest dość zawiły i niejednoznaczny. Zobacz physics.stackexchange.com/a/94446/5089 i physics.stackexchange.com/a/419628/5089
Violet Giraffe
28

Dla leniwych facetów (takich jak ja), oto implementacja w Javie kodu znalezionego w odpowiedzi @ user151323 (czyli po prostu proste tłumaczenie kodu pascala znalezionego w raporcie Spectra Lab ):

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;
}
Tarc
źródło
3
Wygląda na to, że w Twoim kodzie jest błąd. Jeśli długość fali wynosi na przykład 439,5, funkcja zwraca czarny. Wydaje mi się, że oryginalny kod na stronie działał z liczbami całkowitymi (w ogóle nie znam pascala). Proponuję zmienić Wavelength<=439na Wavelength<440.
Hassedev,
2
Masz rację! Dziękuję za zwrócenie mi uwagi :) Już poprawione.
Tarc
Czy oczekuje się, że będzie miał powtarzający się RFB na niektórych częstotliwościach? (CZERWONY): 652 - rgb (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - rgb (255, 0, 0) | 700 - rgb (255, 0, 0) | ...
Rodrigo Borba
14

Główny pomysł:

  1. Użyj funkcji dopasowania kolorów CEI, aby przekonwertować długość fali na kolor XYZ .
  2. Konwertuj XYZ na RGB
  3. Przytnij komponenty do [0..1] i pomnóż przez 255, aby dopasować je do zakresu bajtów bez znaku.

Kroki 1 i 2 mogą się różnić.

Istnieje kilka funkcji dopasowywania kolorów, dostępnych jako tabele lub jako przybliżenia analityczne (sugerowane przez @Tarc i @Haochen Xie). Tabele są najlepsze, jeśli potrzebujesz precyzyjnego wyniku.

Nie ma jednej przestrzeni kolorów RGB. Można zastosować wiele macierzy transformacji i różne rodzaje korekcji gamma.

Poniżej znajduje się kod C #, który niedawno wymyśliłem. Wykorzystuje interpolację liniową w tabeli „Standard obserwator CIE 1964” i macierz sRGB + korekcja 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;
    }
}

Wynik dla zakresu 400-700 nm:

wprowadź opis obrazu tutaj

amartynov
źródło
To jest dla mnie naprawdę interesujące. Mam pomysł, aby użyć czegoś takiego, aby dać normalną odpowiedź, ale użyj odpowiedzi WXYZ, aby naśladować odpowiedź tetrachromatów, które mają czwarty stożek, który reaguje na częstotliwość wystarczająco daleko od któregokolwiek z pozostałych trzech normalnych typów stożków. To może pozwolić mi pobrać obrazy źródłowe i wywnioskować różnice, które widzą. Uwaga: nie widzą nowych kolorów, to dlatego, że światła, które zlewają się, na przykład, (suma) do określonego żółtego wydają się być identyczne z żółtym o określonej częstotliwości dla większości z nas, ale dla nich światło się nie zlewa do tego żółtego w ogóle.
phorgan1
Oczywiście w przypadku konkretnego koloru RGB można było to osiągnąć na wiele sposobów. Zieleń liścia może pochodzić z odfiltrowania wszystkiego oprócz zielonego lub zieleń mogła zostać odfiltrowana, ale cechy nano mogą powodować, że niebieski i żółty odbijają się i wyglądają identycznie jak zielony. Biorąc pod uwagę raczej obraz niż światło, czy jest jakiś sposób, żebym mógł to odróżnić?
phorgan 1
10

Chociaż jest to stare pytanie i już dostaje kilka dobrych odpowiedzi, kiedy próbowałem zaimplementować taką funkcjonalność konwersji w mojej aplikacji, nie byłem zadowolony z wymienionych tutaj algorytmów i przeprowadziłem własne badania, które dały mi dobry wynik. Więc zamierzam opublikować nową odpowiedź.

Po kilku badaniach natknąłem się na ten artykuł, Simple Analytic Approximations to the CIE XYZ Color Matching Functions , i próbowałem zastosować w mojej aplikacji wprowadzony wielopłatkowy algorytm Gaussa. W artykule opisano tylko funkcje konwersji długości fali na odpowiednie wartości XYZ , więc zaimplementowałem funkcję konwersji XYZ na RGB w przestrzeni kolorów sRGB i połączyłem je. Wynik jest fantastyczny i warto się nim podzielić:

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

mój kod jest napisany w Javie 8, ale przeniesienie go na niższe wersje Javy i innych języków nie powinno być trudne.

Haochen Xie
źródło
1
@Baddack, masz rację: to po prostu fantazyjny sposób na dokonanie dalszej transformacji obliczonych wartości. Nie pamiętam dokładnie, ale myślę, że najpierw stosuje korekcję gamma, a potem odcina wartości poza zakresem. Może powinienem zrobić to osobną metodą, ale tak naprawdę nie myślałem o udostępnieniu kodu podczas pisania, a był to projekt zabawki, w którym potrzebowałem tej konwersji.
Haochen Xie
1
@Baddack Wykopałem projekt, który potrzebowałem tej konwersji, i przepisałem tę część bez używania java 8 lambda, aby kod był bardziej przejrzysty. Właściwie niepoprawnie pamiętałem, co transferrobił DoubleUnaryOperator (stąd wyjaśnienie w moim poprzednim komentarzu nie jest poprawne), więc proszę sprawdzić nowy kod.
Haochen Xie
1
@Baddack Cieszę się, że kod ci pomaga. a jeśli nie masz nic przeciwko, czy mógłbyś zagłosować za nim, aby potencjalnie pomóc większej liczbie osób?
Haochen Xie
1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2,4), tzn. Podnieś c do potęgi 1 / 2,4; 1.jest tylko 1, ale typ będzie doublezamiastint
Haochen Xie
3
@Ruslan, ponieważ ten algorytm jest analitycznym dopasowaniem standardowego obserwatora CIE (który można uznać za model „precyzyjny”), występują błędy. Ale z artykułu, jeśli spojrzysz na rysunek 1 na stronie 7 (porównaj (d) z (f)), ta metoda zapewnia dość bliskie przybliżenie. Zwłaszcza jeśli spojrzeć na (f), można było zauważyć, że nawet w standardowym modelu występuje niebieskawa linia. Również percepcja kolorów czystego źródła światła różni się osobiście, więc ten poziom błędu jest prawdopodobnie nieistotny.
Haochen Xie
7

Mówisz o konwersji długości fali na wartość RGB.

Spójrz tutaj, prawdopodobnie odpowie na twoje pytanie. Masz narzędzie do robienia tego z kodem źródłowym, a także pewne wyjaśnienia.

WaveLengthToRGB


źródło
1
Wystarczy przeczytać tę samą stronę „Nie ma unikalnego odwzorowania jeden do jednego między długością fali a wartościami RGB” - tak dobrze, że utknąłeś z tabelą przeglądową i heurystyką. W pierwszej kolejności przyjrzałbym się konwersji HSV na RGB, ponieważ „Hue” waha się od niebieskiego do czerwonego. Prawdopodobnie z niewielkim przesunięciem, ponieważ w domenie RGB czerwony + niebieski = fiolet, a fiolet ma najkrótszą widzialną długość fali.
whatnick
3
czy to nie jest praktycznie to samo? freq = c / wavelength
Mauricio Scheffer
1
@Mauricio Scheffer Tak, jest DOKŁADNIE to samo.
Joseph Gordon
ten algorytm Brutona jest raczej estetyczny niż realistyczny
mykhal
8
@ Joseph Gordon - Zdecydowanie się nie zgadzam. Rozważmy zielonkawy promień 400nm emitowany w powietrzu, który uderza w powierzchnię wody, a następnie rozprzestrzenia się w wodzie. Współczynnik załamania wody wynosi, powiedzmy, 1,33, więc długość fali promienia w wodzie wynosi teraz 300 nm, co oczywiście nie zmienia jej koloru. Materią, która „koloryzuje” promienie, jest częstotliwość, a nie długość fali. W tej samej substancji (próżnia, powietrze, woda) częstotliwości (kolory) odpowiadają tym samym długościom fal. W innych mediach - nie.
mbaitoff
3

Myślę, że równie dobrze mógłbym uzupełnić swój komentarz formalną odpowiedzią. Najlepszą opcją jest użycie przestrzeni kolorów HSV - chociaż odcień reprezentuje długość fali, nie jest to porównanie jeden do jednego.

whatnick
źródło
1
Twój link nie działa.
Ruslan,
3

Zrobiłem liniowe dopasowanie znanych wartości odcieni i częstotliwości (pomijając czerwony i fioletowy, ponieważ rozciągają się one tak daleko w wartościach częstotliwości, że nieco wypaczają) i otrzymałem przybliżone równanie konwersji.

To idzie jak
częstotliwość (w THz) = 474 + (3/4) (Kąt odcienia (w stopniach))

Próbowałem się rozejrzeć i sprawdzić, czy ktoś nie wymyślił tego równania, ale nie znalazłem nic do maja 2010 roku.

David Elm
źródło
2

Metoda 1

Jest to nieco oczyszczone i przetestowane w C ++ 11 wersji @ haochen-xie. Dodałem również funkcję, która konwertuje wartość 0 do 1 na długość fali w widmie widzialnym, która jest użyteczna w tej metodzie. Możesz po prostu umieścić poniżej w jednym pliku nagłówkowym i używać go bez żadnych zależności. Ta wersja będzie tutaj utrzymywana .

#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

Wykres kolorów od 375nm do 725nm wygląda jak poniżej:

wprowadź opis obrazu tutaj

Problem z tą metodą polega na tym, że działa ona tylko między 400-700nm, a poza tym gwałtownie spada do czerni. Kolejną kwestią jest węższy niebieski.

Dla porównania poniżej znajdują się kolory z Vision FAQ na maxmax.com:

wprowadź opis obrazu tutaj

Użyłem tego do wizualizacji mapy głębi, gdzie każdy piksel reprezentuje wartość głębokości w metrach i wygląda to jak poniżej:

wprowadź opis obrazu tutaj

Metoda 2

Jest to zaimplementowane jako część biblioteki bitmap_image z pojedynczym nagłówkiem pliku przez 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;
}

Wykres długości fali 375-725nm wygląda jak poniżej:

wprowadź opis obrazu tutaj

Więc jest to bardziej użyteczne w 400-725nm. Kiedy wizualizuję tę samą mapę głębokości, co w metodzie 1, otrzymuję poniżej. Istnieje oczywisty problem z tymi czarnymi liniami, które, jak sądzę, wskazują na drobny błąd w tym kodzie, któremu nie przyjrzałem się dokładniej. Również fiołki są nieco węższe w tej metodzie, co powoduje mniejszy kontrast dla odległych obiektów.

wprowadź opis obrazu tutaj

Shital Shah
źródło
0

Projektuj CIExy długości fali w kierunku bieli D65 na gamę 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
Roman Czyborra
źródło