แปลงความถี่แสงเป็น RGB?


119

มีใครรู้สูตรการแปลงความถี่แสงเป็นค่า RGB บ้างไหม?


2
คำถามทางเทคนิคมากในแง่ของฟิสิกส์และการเขียนโปรแกรม +1
whatnick

คำตอบ:


44

นี่คือคำอธิบายรายละเอียดของขั้นตอนการแปลงทั้งหมด: http://www.fourmilab.ch/documents/specrend/ รวมรหัสที่มา!


5
และบทความ Fourmilab ให้ประเด็นสำคัญว่าสีบางสีไม่สามารถแสดงใน RGB ได้ (ส้มสว่างเป็นตัวอย่างที่ดี) เนื่องจากคุณไม่สามารถ "สร้าง" สีของแสงโดยการเพิ่มสีหลักสามสีเข้าด้วยกันไม่ว่าครูฟิสิกส์ของเราจะบอกอะไรก็ตาม ( ของฉันทำได้ดี) เสียดาย แต่ในทางปฏิบัติมักไม่ถึงแก่ชีวิต
Francis Davey

1
นอกจากนี้: en.wikipedia.org/wiki/Srgb บทความนี้เขียนขึ้นก่อนที่มาตรฐาน sRGB จะถูกนำมาใช้อย่างกว้างขวาง นอกจากนี้โปรดสังเกตว่าวลี "การคำนวณสมมติว่าผู้สังเกตการณ์สีมาตรฐาน 2 °" ซึ่งหมายความว่าตาราง CIE 1931 ที่พบในแหล่งที่มาของกระดาษควรใช้ไม่ใช่ CIE 1964
GrayFace

จะเป็นการดีที่จะให้ตัวอย่างวิธีการใช้โค้ด มันต้องใช้ฟังก์ชันเป็นอาร์กิวเมนต์ใช้อุณหภูมิในการคำนวณสีและสิ่งต่างๆ เรายินดีที่จะทราบว่าจะลบและเปลี่ยนแปลงอะไรเพื่อให้ใช้งานได้
Tomáš Zato - คืนสถานะ Monica

2
เป็นที่น่าสังเกตว่ามีเพียงส่วนเล็ก ๆ ของความยาวคลื่นที่มองเห็นได้ทั้งหมดเท่านั้นที่สามารถแสดงได้อย่างแน่นอนในพื้นที่สี RGB กระบวนการแปลงค่อนข้างซับซ้อนและคลุมเครือ ดูPhysics.stackexchange.com/a/94446/5089และPhysics.stackexchange.com/a/419628/5089
ยีราฟสีม่วง

28

สำหรับคนขี้เกียจ (เช่นฉัน) นี่คือการนำไปใช้ใน java ของโค้ดที่พบในคำตอบของ @ user151323 (นั่นคือการแปลง่ายๆจากรหัสปาสคาลที่พบใน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
ดูเหมือนว่าจะมีข้อบกพร่องในโค้ดของคุณ หากความยาวคลื่นเป็นตัวอย่างเช่น 439.5 ฟังก์ชันของคุณจะคืนค่าเป็นสีดำ รหัสเดิมบนไซต์ทำงานกับจำนวนเต็มฉันเชื่อว่า (ฉันไม่รู้จัก pascal เลย) ผมขอแนะนำให้มีการเปลี่ยนแปลงไปWavelength<=439 Wavelength<440
Hassedev

2
คุณถูก! ขอบคุณที่ชี้เรื่องนี้ให้ฉัน :) แก้ไขเรียบร้อยแล้ว
Tarc

คาดว่าจะมี RFB ซ้ำในบางความถี่หรือไม่? (สีแดง): 652 - rgb (255, 0, 0) | 660 - rgb (255, 0, 0) | 692 - rgb (255, 0, 0) | 700 - rgb (255, 0, 0) | ...
Rodrigo Borba

14

แนวคิดทั่วไป:

  1. ใช้CEI ฟังก์ชั่นการจับคู่สีที่จะแปลงความยาวคลื่นสี XYZ
  2. แปลง XYZ เป็น RGB
  3. คอมโพเนนต์ของคลิปเป็น [0..1] และคูณด้วย 255 เพื่อให้พอดีกับช่วงไบต์ที่ไม่ได้ลงชื่อ

ขั้นตอนที่ 1 และ 2 อาจแตกต่างกันไป

มีฟังก์ชั่นการจับคู่สีหลายแบบโดยใช้เป็นตารางหรือเป็นค่าประมาณเชิงวิเคราะห์ (แนะนำโดย @Tarc และ @Haochen Xie) ตารางจะดีที่สุดหากคุณต้องการผลการปรับแต่งที่ราบรื่น

ไม่มีพื้นที่สี RGB แม้แต่สีเดียว อาจใช้เมทริกซ์การแปลงหลายรูปแบบและการแก้ไขแกมมาประเภทต่างๆ

ด้านล่างนี้คือรหัส C # ที่ฉันสร้างขึ้นเมื่อเร็ว ๆ นี้ มันใช้สอดแทรกเชิงเส้นในช่วง "CIE 1964 มาตรฐานสังเกตการณ์" ตารางและsRGB เมทริกซ์ + แก้ไขแกมมา

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

ผลลัพธ์สำหรับช่วง 400-700 นาโนเมตร:

ใส่คำอธิบายภาพที่นี่


นี่เป็นเรื่องที่น่าสนใจสำหรับฉันจริงๆ ฉันมีความคิดที่จะใช้บางอย่างเช่นนี้เพื่อตอบสนองตามปกติ แต่ใช้การตอบสนอง WXYZ เพื่อเลียนแบบการตอบสนองของ tetrachromats ที่มีกรวยที่สี่ซึ่งตอบสนองต่อความถี่ที่ไกลพอจากกรวยสามประเภทอื่น ๆ ทั่วไป นั่นอาจทำให้ฉันใช้ภาพต้นฉบับและสรุปความแตกต่างที่พวกเขาเห็น หมายเหตุพวกเขาไม่เห็นสีใหม่นั่นคือแสงที่ผสมผสานตัวอย่างเช่น (ผลรวม) กับสีเหลืองที่เฉพาะเจาะจงดูเหมือนกับสีเหลืองของความถี่เฉพาะสำหรับพวกเราส่วนใหญ่ แต่สำหรับพวกเขาแสงจะไม่ผสมผสาน เป็นสีเหลืองเลย
phorgan1

แน่นอนว่าสำหรับสี RGB โดยเฉพาะอาจมีหลายวิธี สีเขียวของใบไม้อาจมาจากการกรองออกจากทุกสิ่งยกเว้นสีเขียวหรือสีเขียวอาจถูกกรองออกไป แต่ลักษณะของนาโนอาจทำให้สีน้ำเงินและสีเหลืองสะท้อนและมีลักษณะเหมือนกับสีเขียว ให้ภาพมากกว่าแสงมีวิธีใดบ้างที่ฉันสามารถแยกความแตกต่างได้?
phorgan1

10

แม้ว่านี่จะเป็นคำถามเก่าและได้รับคำตอบที่ดีอยู่แล้ว แต่เมื่อฉันพยายามใช้ฟังก์ชันการแปลงดังกล่าวในแอปพลิเคชันของฉันฉันไม่พอใจกับอัลกอริทึมที่ระบุไว้ที่นี่และได้ทำการวิจัยของฉันเองซึ่งทำให้ฉันได้ผลลัพธ์ที่ดี ผมจะโพสต์คำตอบใหม่

หลังจากการวิจัยบางชิ้นฉันได้พบกับบทความนี้การประมาณค่าการวิเคราะห์อย่างง่ายสำหรับฟังก์ชันการจับคู่สี CIE XYZและพยายามนำอัลกอริธึม Gaussian fit แบบหลายกลีบมาใช้ในแอปพลิเคชันของฉัน กระดาษอธิบายเฉพาะฟังก์ชันในการแปลงความยาวคลื่นเป็นค่า XYZ ที่สอดคล้องกันดังนั้นฉันจึงใช้ฟังก์ชันในการแปลง XYZ เป็น RGB ในพื้นที่สี sRGB และรวมเข้าด้วยกัน ผลลัพธ์ที่ได้คือยอดเยี่ยมและควรค่าแก่การแบ่งปัน:

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

โค้ดของฉันเขียนด้วย Java 8 แต่ไม่ควรยากที่จะพอร์ตไปยัง Java เวอร์ชันต่ำกว่าและภาษาอื่น ๆ


1
@ แบดแบ็คคุณพูดถูก: มันเป็นเพียงวิธีแฟนซีในการเปลี่ยนแปลงค่าที่คำนวณได้ ฉันจำไม่ได้แน่ชัด แต่ฉันคิดว่าก่อนอื่นจะใช้การแก้ไขแกมมาจากนั้นจึงตัดค่าออกจากช่วง บางทีฉันควรทำด้วยวิธีแยกต่างหาก แต่จริงๆแล้วฉันไม่ได้คิดที่จะแชร์โค้ดในขณะที่เขียนมันและเป็นโครงการของเล่นที่ฉันต้องการการแปลงนี้
Haochen Xie

1
@Baddack ฉันขุดโครงการที่ฉันต้องการการแปลงนี้และเขียนส่วนนี้ใหม่โดยไม่ใช้ java 8 lambda เพื่อให้โค้ดมีความชัดเจนมากขึ้น ฉันจำไม่ถูกต้องเกี่ยวกับสิ่งที่transferDoubleUnaryOperator กำลังทำอยู่ (ดังนั้นคำอธิบายในความคิดเห็นก่อนหน้าของฉันจึงไม่ถูกต้อง) ดังนั้นโปรดตรวจสอบรหัสใหม่
Haochen Xie

1
@Baddack ฉันดีใจที่รหัสช่วยคุณได้ และหากคุณไม่ทราบคุณช่วยเพิ่มคะแนนได้ไหมเพื่อที่อาจช่วยคนได้มากขึ้น
Haochen Xie

1
@Baddack Math.pow (c, 1. / 2.4) = c ^ (1 / 2.4) คือยก c ยกกำลัง 1 / 2.4; 1.เป็นเพียง 1 แต่ประเภทจะเป็นdoubleแทนint
Haochen Xie

3
@Ruslan เนื่องจากอัลกอริทึมนี้เหมาะกับการวิเคราะห์ของผู้สังเกตการณ์มาตรฐาน CIE (ซึ่งอาจถือได้ว่าเป็นโมเดลที่ "แม่นยำ") จึงมีข้อผิดพลาด แต่จากกระดาษถ้าคุณดูรูปที่ 1 ในหน้า 7 (เปรียบเทียบ (d) กับ (f)) วิธีนี้จะให้ค่าประมาณที่ค่อนข้างใกล้เคียง โดยเฉพาะอย่างยิ่งถ้าคุณดูที่ (f) คุณจะเห็นว่ามีเส้นสีน้ำเงินอยู่ในรุ่นมาตรฐานด้วย นอกจากนี้การรับรู้สีของแหล่งกำเนิดแสงบริสุทธิ์ยังแตกต่างกันไปดังนั้นข้อผิดพลาดระดับนี้จึงอาจเล็กน้อย
Haochen Xie

7

คุณกำลังพูดถึงการแปลงจากความยาวคลื่นเป็นค่า RGB

ดูที่นี่อาจจะตอบคำถามของคุณ คุณมียูทิลิตี้สำหรับทำสิ่งนี้กับซอร์สโค้ดรวมถึงคำอธิบายบางอย่าง

WaveLengthToRGB


1
เพียงแค่อ่านหน้าเดียวกัน "ไม่มีการแมปแบบตัวต่อตัวที่ไม่ซ้ำกันระหว่างค่าความยาวคลื่นและ RGB" ดังนั้นคุณจะต้องติดอยู่กับตารางการค้นหาและการวิเคราะห์พฤติกรรม ในการตัดครั้งแรกฉันจะดูการแปลง HSV เป็น RGB เนื่องจาก "Hue" มีตั้งแต่สีน้ำเงินไปจนถึงสีแดง อาจมีการเปลี่ยนแปลงเล็กน้อยเนื่องจากในโดเมน RGB สีแดง + น้ำเงิน = สีม่วงและสีม่วงมีความยาวคลื่นที่สั้นที่สุดที่มองเห็นได้
whatnick

3
มันไม่เหมือนกันจริงหรือ? freq = c / wavelength
Mauricio Scheffer

1
@Mauricio Scheffer ใช่มันเหมือนกันทุกประการ
Joseph Gordon

อัลกอริทึมของ Bruton นี้ค่อนข้างสวยงามมากกว่าความเป็นจริง
mykhal

8
@ โจเซฟกอร์ดอน - ไม่เห็นด้วยอย่างยิ่ง พิจารณารังสีสีเขียว 400 นาโนเมตรที่ปล่อยออกมาในอากาศกระทบผิวน้ำแล้วแพร่กระจายในน้ำ ค่าสัมประสิทธิ์การหักเหของน้ำคือ 1.33 ดังนั้นความยาวคลื่นของรังสีในน้ำตอนนี้คือ 300 นาโนเมตรซึ่งเห็นได้ชัดว่ามันไม่เปลี่ยนสี สสารที่ทำให้รังสีเป็นสีความถี่ไม่ใช่ความยาวคลื่น ในสสารเดียวกัน (สูญญากาศอากาศน้ำ) ความถี่ (สี) จะจับคู่กับความยาวคลื่นเดียวกัน ในสื่อต่างๆ - ไม่ใช่
mbaitoff

3

ฉันเดาว่าฉันอาจติดตามความคิดเห็นของฉันด้วยคำตอบที่เป็นทางการ ตัวเลือกที่ดีที่สุดคือการใช้พื้นที่สี HSVแม้ว่าเฉดสีจะแสดงถึงความยาวคลื่น แต่ก็ไม่ใช่การเปรียบเทียบแบบตัวต่อตัว


1
ลิงก์ของคุณหมดแล้ว
Ruslan

3

ฉันได้ค่าความพอดีเชิงเส้นของค่าสีและความถี่ที่ทราบแล้ว (เอาสีแดงและสีม่วงออกไปเพราะมันขยายออกไปในค่าความถี่มากจนทำให้สิ่งต่าง ๆ เอียงไปเล็กน้อย) และฉันได้สมการการแปลงคร่าวๆ

มันเหมือนกับ
ความถี่ (ใน THz) = 474 + (3/4) (มุมเว้ (เป็นองศา))

ฉันพยายามมองไปรอบ ๆ และดูว่ามีใครคิดสมการนี้ขึ้นมาบ้าง แต่ฉันไม่พบอะไรเลย ณ เดือนพฤษภาคม 2010


2

วิธีที่ 1

นี่เป็นการล้างข้อมูลเล็กน้อยและทดสอบเวอร์ชัน C ++ 11 ของ @ haochen-xie ฉันยังเพิ่มฟังก์ชั่นที่แปลงค่า 0 ถึง 1 เป็นความยาวคลื่นในสเปกตรัมที่มองเห็นได้ซึ่งสามารถใช้ได้กับวิธีนี้ คุณสามารถใส่ด้านล่างในไฟล์ส่วนหัวเดียวและใช้งานได้โดยไม่ต้องพึ่งพาใด ๆ รุ่นนี้จะถูกเก็บรักษาไว้ที่นี่

#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

พล็อตของสีจาก 375nm ถึง 725nm มีลักษณะดังนี้:

ใส่คำอธิบายภาพที่นี่

ปัญหาหนึ่งของวิธีนี้คือความจริงที่ว่ามันใช้งานได้ระหว่าง 400-700 นาโนเมตรและนอกนั้นตกลงไปเป็นสีดำอย่างรวดเร็ว อีกประเด็นหนึ่งคือสีน้ำเงินที่แคบกว่า

สำหรับการเปรียบเทียบด้านล่างนี้เป็นสีจาก Vision FAQ ที่ maxmax.com:

ใส่คำอธิบายภาพที่นี่

ฉันใช้สิ่งนี้เพื่อแสดงภาพแผนที่ความลึกโดยแต่ละพิกเซลแสดงค่าความลึกเป็นเมตรและมีลักษณะดังนี้:

ใส่คำอธิบายภาพที่นี่

วิธีที่ 2

สิ่งนี้ถูกนำไปใช้เป็นส่วนหนึ่งของไลบรารีส่วนหัวไฟล์เดียวของbitmap_imageโดย 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;
}

พล็อตความยาวคลื่น 375-725 นาโนเมตรมีลักษณะดังนี้:

ใส่คำอธิบายภาพที่นี่

จึงใช้งานได้มากกว่าใน 400-725 นาโนเมตร เมื่อฉันเห็นภาพแผนที่ความลึกเช่นเดียวกับวิธีที่ 1 ฉันจะได้รับด้านล่าง มีปัญหาที่ชัดเจนเกี่ยวกับเส้นสีดำเหล่านั้นซึ่งฉันคิดว่าบ่งชี้ข้อผิดพลาดเล็กน้อยในรหัสนี้ซึ่งฉันไม่ได้มองลึกไปกว่านี้ สีม่วงยังแคบกว่าเล็กน้อยในวิธีนี้ซึ่งทำให้เกิดความเปรียบต่างน้อยลงสำหรับวัตถุที่อยู่ห่างไกล

ใส่คำอธิบายภาพที่นี่


0

ฉาย CIExy ของความยาวคลื่นไปทางสีขาว D65 ไปยังช่วง 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.