Convertir la fréquence lumineuse en RVB?

Réponses:

44

Voici une explication détaillée de l'ensemble du processus de conversion: http://www.fourmilab.ch/documents/specrend/ . Code source inclus!

Stephen Mesa
la source
5
Et l'article de Fourmilab souligne le fait que certaines couleurs ne sont pas représentables en RVB (les oranges brillantes étant un bon exemple) car vous ne pouvez pas «créer» des couleurs de lumière arbitraires en ajoutant trois couleurs primaires ensemble, quoi que nos professeurs de physique nous aient dit ( bien le mien l'a fait). Dommage, mais en pratique généralement pas fatal.
Francis Davey
1
En plus de cela: en.wikipedia.org/wiki/Srgb L'article a été écrit avant que la norme sRGB ne soit largement adoptée. Notez également la phrase «Les calculs supposent l'observateur colorimétrique standard de 2 °», ce qui signifie que le tableau CIE 1931 trouvé dans la source jointe à l'article doit être utilisé et non CIE 1964.
GrayFace
Ce serait bien de donner un exemple sur la façon d'utiliser le code. Il nécessite une fonction comme argument, utilise la température pour calculer les couleurs et autres. On serait heureux de savoir ce qu'il faut supprimer et modifier pour le faire fonctionner.
Tomáš Zato - Réintégrer Monica
2
Il convient de noter que seul un petit sous-ensemble de toutes les longueurs d'onde visibles possibles peut être exactement représenté dans l'espace colorimétrique RVB. Le processus de conversion est assez complexe et ambigu. Voir physics.stackexchange.com/a/94446/5089 et physics.stackexchange.com/a/419628/5089
Violet Giraffe
28

Pour les 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 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;
}
Tarc
la source
3
Il semble y avoir un bogue dans votre code. Si la longueur d'onde est par exemple de 439,5, votre fonction renvoie du noir. Le code original du site fonctionnait avec des entiers, je crois (je ne connais pas du tout Pascal). Je suggère de changer Wavelength<=439pour Wavelength<440.
Hassedev
2
Vous avez raison! Merci de me l'avoir signalé :) Déjà corrigé.
Tarc
Doit-il avoir répété RFB à certaines fréquences? (ROUGE): 652 - rgb (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - rgb (255, 0, 0) | 700 - rgb (255, 0, 0) | ...
Rodrigo Borba
14

Idée générale:

  1. Utilisez les fonctions de correspondance des couleurs CEI pour convertir la longueur d'onde en couleur XYZ .
  2. Convertir XYZ en RVB
  3. Découpez les composants sur [0..1] et multipliez-les par 255 pour tenir dans la plage d'octets non signés.

Les étapes 1 et 2 peuvent varier.

Il existe plusieurs fonctions de correspondance des couleurs, disponibles sous forme de tableaux ou d'approximations analytiques (suggérées par @Tarc et @Haochen Xie). Les tableaux sont les 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. De multiples matrices de transformation et différents types de correction gamma peuvent être utilisés.

Vous trouverez ci-dessous le code C # que j'ai trouvé récemment. Il utilise l'interpolation linéaire sur la table "CIE 1964 standard observer" et la 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:

entrez la description de l'image ici

Amartynov
la source
C'est vraiment intéressant pour moi. J'ai une idée d'utiliser quelque chose comme celui-ci pour donner une réponse normale, mais utilisez une réponse WXYZ pour imiter la réponse des tétrachromates qui ont un quatrième cône qui répond à une fréquence suffisamment éloignée de l'un des trois autres types de cônes normaux. Cela pourrait me permettre de prendre des images sources et de déduire les différences qu'elles constatent. NB ils ne voient pas de nouvelles couleurs, c'est que les lumières qui se mélangent, (somme), par exemple, à un jaune particulier semblent identiques à un jaune d'une fréquence particulière pour la plupart d'entre nous, mais pour eux, la lumière ne se mélangerait pas à ce jaune du tout.
phorgan1
Bien sûr, pour une couleur RVB particulière, cela aurait pu être obtenu de nombreuses manières. Le vert d'une feuille pourrait provenir du filtrage de tout sauf du vert, ou le vert aurait pu être filtré, mais les caractéristiques nano pourraient faire réfléchir le bleu et le jaune et sembler identiques au vert. Étant donné une image plutôt que la lumière, y a-t-il un moyen de différencier?
phorgan1
10

Bien que ce soit une vieille question et que j'obtienne déjà une poignée de 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à énumérés ici et j'ai fait mes propres recherches, ce qui m'a donné de bons résultats. Je vais donc publier une nouvelle réponse.

Après quelques recherches, je suis tombé sur cet article, Approximations analytiques simples des fonctions de correspondance des couleurs CIE XYZ , et j'ai essayé d'adopter l'algorithme d'ajustement gaussien par morceaux à plusieurs lobes dans mon application. Le document décrivait uniquement les fonctions permettant de convertir une longueur d'onde en valeurs XYZ correspondantes , 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.

Haochen Xie
la source
1
@Baddack, vous avez raison: c'est juste un moyen sophistiqué de faire une transformation supplémentaire sur les valeurs calculées. Je ne me souviens pas exactement, mais je pense qu'il applique d'abord une correction gamma, puis coupe les valeurs hors plage. J'aurais peut-être dû le faire dans une méthode distincte, mais je ne pensais pas vraiment à partager le code en l'écrivant, et c'était un projet jouet dans lequel j'avais besoin de cette conversion.
Haochen Xie
1
@Baddack J'ai extrait le projet dont j'avais besoin pour cette conversion et réécrit cette partie sans utiliser java 8 lambda pour que le code soit plus clair. En fait, je me suis mal souvenu de ce transferque faisait DoubleUnaryOperator (donc les explications de mon commentaire précédent ne sont pas correctes), veuillez donc vérifier le nouveau code.
Haochen Xie
1
@Baddack je suis heureux que le code vous aide. et si cela ne vous dérange pas, pourriez-vous s'il vous plaît voter pour qu'il puisse potentiellement aider plus de gens?
Haochen Xie
1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2.4), c'est-à-dire élever c à la puissance 1 / 2,4; 1.est juste 1 mais le type sera à la doubleplace deint
Haochen Xie
3
@Ruslan puisque cet algorithme est un ajustement analytique de l'observateur standard CIE (qui pourrait être considéré comme le modèle "précis"), il y a des erreurs. Mais d'après l'article, si vous regardez la figure 1 à la page 7 (comparez (d) à (f)), cette méthode fournit une approximation assez proche. Surtout si vous regardez (f), vous pouvez voir qu'il y a aussi une ligne bleuâtre même dans le modèle standard. En outre, la perception des couleurs de la source de lumière pure varie personnellement, ce niveau d'erreur est donc probablement négligeable.
Haochen Xie
7

Vous parlez de la conversion d' une longueur d'onde en une valeur RVB.

Regardez ici, répondra probablement à votre question. Ils ont un utilitaire pour faire cela avec le code source ainsi que quelques explications.

WaveLengthToRGB


la source
1
Juste en lisant la même page "Il n'y a pas de mappage univoque unique entre la longueur d'onde et les valeurs RVB" - si bien que vous êtes coincé avec une table de recherche et des heuristiques. Dans un premier temps, je regarderais les conversions HSV en RVB puisque la «teinte» va du bleu au rouge. Avec peut-être un léger décalage car dans le domaine RVB, le rouge + bleu = violet et violet a la longueur d'onde visible la plus courte.
whatnick
3
n'est-ce pas pratiquement la même chose? freq = c / wavelength
Mauricio Scheffer
1
@Mauricio Scheffer Oui, c'est exactement la même chose.
Joseph Gordon
cet algorithme de Bruton est plutôt esthétique que réaliste
mykhal
8
@ Joseph Gordon - Pas du tout d'accord. Considérons un rayon verdâtre de 400 nm émis dans l'air qui atteint la surface de l'eau puis se propage dans l'eau. Le coefficient de réfraction de l'eau est, disons, de 1,33, donc une longueur d'onde de rayon dans l'eau est maintenant de 300 nm, ce qui ne change évidemment pas sa couleur. La matière qui «colore» les rayons est la fréquence, pas la longueur d'onde. Dans la même substance (vide, air, eau), les fréquences (couleurs) correspondent aux mêmes longueurs d'onde. Dans différents médias - pas.
mbaitoff le
3

Je suppose que je pourrais aussi bien faire suivre mon commentaire d'une réponse formelle. La meilleure option est d'utiliser l' espace colorimétrique HSV - bien que la teinte représente la longueur d'onde, ce n'est pas une comparaison un à un.

whatnick
la source
1
Votre lien est mort.
Ruslan
3

J'ai fait un ajustement linéaire des valeurs de teinte et des fréquences connues (en supprimant le rouge et le violet parce qu'ils s'étendent si loin dans les valeurs de fréquence qu'ils faussent un peu les choses) et j'ai obtenu une équation de conversion approximative.

Cela va comme la
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 a trouvé cette équation, mais je n'ai rien trouvé en mai 2010.

David Elm
la source
2

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 qui est 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

Le tracé des couleurs de 375 nm à 725 nm ressemble à ci-dessous:

entrez la description de l'image ici

Un problème avec cette méthode est le fait qu'elle ne fonctionne qu'entre 400-700 nm et en dehors de cela, elle tombe brusquement au noir. Un autre problème est le bleu plus étroit.

À titre de comparaison, voici les couleurs de la FAQ Vision sur maxmax.com:

entrez la description de l'image ici

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:

entrez la description de l'image ici

Méthode 2

Ceci est implémenté dans le cadre de la bibliothèque d' en-tête de fichier unique bitmap_image 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 graphique de longueur d'onde de 375 à 725 nm ressemble à ci-dessous:

entrez la description de l'image ici

Donc, c'est 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, indiquent un bogue mineur dans ce code que je n'ai pas examiné plus profondément. Les violettes sont également un peu plus étroites dans cette méthode, ce qui entraîne moins de contraste pour les objets éloignés.

entrez la description de l'image ici

Shital Shah
la source
0

Projetez le CIExy de la longueur d'onde vers le blanc D65 sur la gamme 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
Czyborra romain
la source