Ubah frekuensi cahaya ke RGB?

Jawaban:

44

Berikut penjelasan rinci dari keseluruhan proses konversi: http://www.fourmilab.ch/documents/specrend/ . Kode sumber disertakan!

Stephen Mesa
sumber
5
Dan artikel Fourmilab membuat poin penting bahwa beberapa warna tidak dapat direpresentasikan dalam RGB (oranye terang adalah contoh yang baik) karena Anda tidak dapat "membuat" warna sembarang cahaya dengan menambahkan tiga warna primer bersama-sama, apa pun yang mungkin dikatakan oleh guru fisika kami ( baik milikku). Sayang sekali, tapi dalam praktiknya biasanya tidak fatal.
Francis Davey
1
Selain itu: en.wikipedia.org/wiki/Srgb Artikel ini ditulis sebelum standar sRGB diadopsi secara luas. Perhatikan juga frasa "Kalkulasi mengasumsikan pengamat kolorimetri standar 2 °", yang berarti tabel CIE 1931 yang terdapat dalam sumber yang menyertai makalah harus digunakan dan bukan CIE 1964.
GrayFace
Akan sangat bagus untuk memberikan beberapa contoh bagaimana menggunakan kode. Ini membutuhkan fungsi sebagai argumen, menggunakan suhu untuk menghitung warna dan semacamnya. Seseorang akan senang mengetahui apa yang harus dihapus dan diubah agar berfungsi.
Tomáš Zato - Kembalikan Monica
2
Perlu dicatat bahwa hanya sebagian kecil dari semua panjang gelombang yang mungkin terlihat dapat secara tepat direpresentasikan dalam ruang warna RGB. Proses konversi cukup rumit dan ambigu. Lihat physics.stackexchange.com/a/94446/5089 dan physics.stackexchange.com/a/419628/5089
Violet Giraffe
28

Untuk orang-orang yang malas (seperti saya), berikut adalah implementasi di java dari kode yang ditemukan di jawaban @ user151323 (yaitu, hanya terjemahan sederhana dari kode pascal yang ditemukan di 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
sumber
3
Sepertinya ada bug dalam kode Anda. Jika panjang gelombangnya misalnya 439,5, fungsi Anda menghasilkan warna hitam. Kode asli di situs bekerja dengan bilangan bulat, saya yakin (saya tidak tahu pascal sama sekali). Saya menyarankan untuk pindah Wavelength<=439ke Wavelength<440.
Hassedev
2
Kamu benar! Terima kasih telah menunjukkan hal ini kepada saya :) Sudah diperbaiki.
Tarc
Apakah diharapkan RFB berulang ke beberapa frekuensi? (MERAH): 652 - rgb (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - rgb (255, 0, 0) | 700 - rgb (255, 0, 0) | ...
Rodrigo Borba
14

Ide umum:

  1. Gunakan fungsi pencocokan warna CEI untuk mengubah panjang gelombang menjadi warna XYZ .
  2. Ubah XYZ ke RGB
  3. Gunting komponen ke [0..1] dan kalikan dengan 255 agar sesuai dengan rentang byte unsigned.

Langkah 1 dan 2 mungkin berbeda.

Ada beberapa fungsi pencocokan warna, tersedia sebagai tabel atau sebagai perkiraan analitik (disarankan oleh @Tarc dan @Haochen Xie). Tabel adalah pilihan terbaik jika Anda membutuhkan hasil presisi yang halus.

Tidak ada satu pun ruang warna RGB. Beberapa matriks transformasi dan berbagai jenis koreksi gamma dapat digunakan.

Di bawah ini adalah kode C # yang saya dapatkan baru-baru ini. Ini menggunakan interpolasi linier di atas tabel "pengamat standar CIE 1964" dan matriks sRGB + koreksi 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;
    }
}

Hasil untuk kisaran 400-700 nm:

masukkan deskripsi gambar di sini

amartynov.dll
sumber
Ini sangat menarik bagi saya. Saya punya ide untuk menggunakan sesuatu seperti ini untuk memberikan respons normal, tetapi gunakan respons WXYZ untuk meniru respons tetrakromat yang memiliki kerucut keempat yang merespons frekuensi cukup jauh dari salah satu dari tiga jenis kerucut normal lainnya. Itu memungkinkan saya mengambil gambar sumber dan menyimpulkan perbedaan yang mereka lihat. NB mereka tidak melihat warna baru, itu adalah cahaya yang menyatu, (jumlah), misalnya, dengan kuning tertentu tampaknya identik dengan kuning pada frekuensi tertentu bagi kebanyakan dari kita, tetapi bagi mereka, cahayanya tidak akan menyatu menjadi kuning itu sama sekali.
phorgan1
Tentu saja, untuk warna RGB tertentu, itu bisa didapatkan dengan banyak cara. Warna hijau pada daun dapat berasal dari penyaringan semua hal kecuali hijau, atau hijau dapat disaring, tetapi karakteristik nano dapat menyebabkan biru dan kuning memantulkan dan terlihat identik dengan hijau. Diberikan gambar daripada cahaya, adakah cara saya bisa membedakan?
phorgan1
10

Meskipun ini adalah pertanyaan lama dan sudah mendapatkan beberapa jawaban bagus, ketika saya mencoba menerapkan fungsi konversi seperti itu di aplikasi saya, saya tidak puas dengan algoritme yang sudah tercantum di sini dan melakukan penelitian sendiri, yang memberi saya hasil yang baik. Jadi saya akan memposting jawaban baru.

Setelah beberapa penelitian, saya menemukan makalah ini, Pendekatan Analitik Sederhana untuk Fungsi Pencocokan Warna CIE XYZ , dan mencoba mengadopsi algoritma fit Gaussian multi-lobus yang diperkenalkan dalam aplikasi saya. Makalah ini hanya menjelaskan fungsi untuk mengubah panjang gelombang ke nilai XYZ yang sesuai , jadi saya menerapkan fungsi untuk mengubah XYZ ke RGB dalam ruang warna sRGB dan menggabungkannya. Hasilnya luar biasa dan layak dibagikan:

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

kode saya ditulis di Java 8, tetapi seharusnya tidak sulit untuk mem-port-nya ke versi Java yang lebih rendah dan bahasa lain.

Haochen Xie
sumber
1
@ Baddack, Anda benar: ini hanya cara yang bagus untuk membuat beberapa transformasi lebih lanjut pada nilai yang dihitung. Saya tidak dapat mengingat dengan tepat, tetapi saya pikir ini pertama-tama menerapkan koreksi gamma, kemudian memotong nilai rentang. Mungkin saya harus melakukannya dalam metode terpisah, tetapi saya tidak benar-benar berpikir untuk membagikan kode saat menulisnya, dan itu adalah proyek mainan di mana saya membutuhkan konversi ini.
Haochen Xie
1
@Baddack Saya telah menggali proyek yang saya butuhkan untuk konversi ini, dan menulis ulang bagian ini tanpa menggunakan java 8 lambda sehingga kodenya lebih jelas. Sebenarnya saya salah ingat tentang apa yang dilakukan transferDoubleUnaryOperator (jadi penjelasan di komentar saya sebelumnya tidak benar), jadi silakan periksa kode baru.
Haochen Xie
1
@ Baddack saya senang bahwa kode membantu Anda. dan jika Anda tidak keberatan, dapatkah Anda memberi suara positif sehingga berpotensi membantu lebih banyak orang?
Haochen Xie
1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2.4), yaitu naikkan c ke pangkat 1 / 2.4; 1.hanya 1 tetapi jenis akan doublebukannyaint
Haochen Xie
3
@Ruslan karena algoritme ini adalah kesesuaian analitis dari pengamat standar CIE (yang dapat dianggap sebagai model "tepat"), ada kesalahan. Tetapi dari kertas, jika Anda melihat Gambar 1 di halaman 7 (bandingkan (d) dengan (f)), metode ini memberikan perkiraan yang cukup dekat. Terutama jika Anda melihat ke (f), Anda dapat melihat bahwa ada juga garis kebiruan bahkan pada model standar. Selain itu, persepsi warna sumber cahaya murni berbeda-beda, jadi tingkat kesalahan ini mungkin dapat diabaikan.
Haochen Xie
7

Anda sedang berbicara tentang mengubah dari panjang gelombang ke nilai RGB.

Lihat di sini, mungkin akan menjawab pertanyaan Anda. Anda memiliki utilitas untuk melakukan ini dengan kode sumber serta beberapa penjelasan.

WaveLengthToRGB


sumber
1
Hanya membaca halaman yang sama "Tidak ada pemetaan satu-ke-satu yang unik antara panjang gelombang dan nilai RGB" - jadi Anda terjebak dengan tabel pencarian dan heuristik. Sebagai potongan pertama saya akan melihat konversi HSV ke RGB karena "Hue" berkisar dari biru ke merah. Dengan kemungkinan sedikit perubahan karena dalam domain RGB merah + biru = violet dan violet memiliki panjang gelombang tampak terpendek.
whatnick
3
bukankah secara praktis sama? frekuensi = c / panjang gelombang
Mauricio Scheffer
1
@Mauricio Scheffer Ya itu PERSIS sama.
Joseph Gordon
Algoritme Bruton ini lebih estetis daripada realistis
mykhal
8
@ Joseph Gordon - Sangat tidak setuju. Bayangkan sinar kehijauan 400nm yang dipancarkan di udara menghantam permukaan air dan kemudian merambat di air. Koefisien refraksi air, katakanlah, 1,33, jadi panjang gelombang sinar di air sekarang 300nm, yang jelas tidak mengubah warnanya. Hal yang "mewarnai" sinar adalah frekuensi, bukan panjang gelombang. Dalam zat yang sama (vakum, udara, air) frekuensi (warna) dipetakan ke panjang gelombang yang sama. Di media yang berbeda - tidak.
mbaitoff
3

Saya rasa saya sebaiknya menindaklanjuti komentar saya dengan jawaban formal. Pilihan terbaik adalah menggunakan ruang warna HSV - meskipun rona mewakili panjang gelombang, ini bukan perbandingan satu-ke-satu.

whatnick
sumber
1
Tautan Anda sudah mati.
Ruslan
3

Saya melakukan kesesuaian linier dari nilai rona dan frekuensi yang diketahui (menghilangkan warna merah dan ungu karena mereka meluas sejauh ini dalam nilai frekuensi sehingga mereka sedikit condong) dan saya mendapatkan persamaan konversi kasar.

Ini berjalan seperti
frekuensi (dalam THz) = 474 + (3/4) (Hue Angle (dalam derajat))

Saya telah mencoba melihat-lihat dan melihat apakah ada yang menemukan persamaan ini, tetapi saya belum menemukan apa pun hingga Mei 2010.

David Elm
sumber
2

Metode 1

Ini sedikit dibersihkan dan diuji versi C ++ 11 dari @ haochen-xie. Saya juga menambahkan fungsi yang mengubah nilai 0 ke 1 menjadi panjang gelombang dalam spektrum terlihat yang dapat digunakan dengan metode ini. Anda bisa meletakkan di bawah ini dalam satu file header dan menggunakannya tanpa ketergantungan apa pun. Versi ini akan dipertahankan di sini .

#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

Plot warna dari 375nm hingga 725nm terlihat seperti di bawah ini:

masukkan deskripsi gambar di sini

Satu masalah dengan metode ini adalah kenyataan bahwa ia hanya bekerja antara 400-700nm dan di luar itu ia secara tajam jatuh ke hitam. Masalah lainnya adalah biru yang lebih sempit.

Sebagai perbandingan, di bawah ini adalah warna dari Vision FAQ di maxmax.com:

masukkan deskripsi gambar di sini

Saya menggunakan ini untuk memvisualisasikan peta kedalaman di mana setiap piksel mewakili nilai kedalaman dalam meter dan ini terlihat seperti di bawah ini:

masukkan deskripsi gambar di sini

Metode 2

Ini diimplementasikan sebagai bagian dari pustaka file tunggal header-only bitmap_image oleh 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;
}

Plot panjang gelombang dari 375-725nm terlihat seperti di bawah ini:

masukkan deskripsi gambar di sini

Jadi ini lebih bisa digunakan dalam 400-725nm. Ketika saya memvisualisasikan peta kedalaman yang sama seperti pada metode 1, saya mendapatkan di bawah. Ada masalah yang jelas dari garis hitam yang menurut saya menunjukkan bug minor dalam kode ini yang belum saya lihat lebih dalam. Juga violet sedikit lebih sempit dalam metode ini yang menyebabkan kontras yang lebih sedikit untuk objek yang jauh.

masukkan deskripsi gambar di sini

Shital Shah
sumber
0

Proyeksikan CIExy panjang gelombang ke arah putih D65 ke gamut 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
sumber