可见光谱的RGB值

时间:2010-08-04 17:07:53

标签: algorithm rgb

我需要一种算法或函数来将可见光谱范围的每个波长映射到其等效的RGB值。 RGB系统和灯的波长之间是否存在任何结构关系? 喜欢这个图像: alt text http://www1.appstate.edu/~kms/classes/psy3203/Color/spectrum5.gif 抱歉,如果这是无关紧要的: - ]

11 个答案:

答案 0 :(得分:33)

我最近发现我的光谱颜色不能正常工作,因为它们基于非线性和移位数据。所以我做了很少的研究和数据编译,发现那里的大多数光谱图像是不正确的。此外,颜色范围彼此不匹配,因此我从这一点仅使用线性化的真实光谱数据like this

以下是我的整改后的输出:

spectral colors

  • 第一个光谱是我发现的最佳渲染光谱,但仍远离真实的光谱
  • 第二个是我们的太阳从地球取出的线性化光谱
  • 最后一个是我当前的颜色输出

以下是RGB图表:

这是两个图的合并:

graph merge

现在代码:

void spectral_color(double &r,double &g,double &b,double l) // RGB <0,1> <- lambda l <400,700> [nm]
    {
    double t;  r=0.0; g=0.0; b=0.0;
         if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); r=    +(0.33*t)-(0.20*t*t); }
    else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); r=0.14         -(0.13*t*t); }
    else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); r=    +(1.98*t)-(     t*t); }
    else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); r=0.98+(0.06*t)-(0.40*t*t); }
    else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); r=0.65-(0.84*t)+(0.20*t*t); }
         if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); g=             +(0.80*t*t); }
    else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); g=0.8 +(0.76*t)-(0.80*t*t); }
    else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); g=0.84-(0.84*t)           ; }
         if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); b=    +(2.20*t)-(1.50*t*t); }
    else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); b=0.7 -(     t)+(0.30*t*t); }
    }
//--------------------------------------------------------------------------

其中

  • l是[nm]可用值的波长,l = < 400.0 , 700.0 >
  • r,g,b返回范围< 0.0 , 1.0 >
  • 中的颜色分量

答案 1 :(得分:10)

部分“可见波长的近似RGB值”

信用:丹布鲁顿 - 色彩科学

原始FORTRAN代码@(http://www.physics.sfasu.edu/astro/color/spectra.html

返回平滑(连续)光谱,红色边重。

w - 波长,R,G和B - 颜色成分

忽略伽玛和强度简单的叶子:

if w >= 380 and w < 440:
    R = -(w - 440.) / (440. - 380.)
    G = 0.0
    B = 1.0
elif w >= 440 and w < 490:
    R = 0.0
    G = (w - 440.) / (490. - 440.)
    B = 1.0
elif w >= 490 and w < 510:
    R = 0.0
    G = 1.0
    B = -(w - 510.) / (510. - 490.)
elif w >= 510 and w < 580:
    R = (w - 510.) / (580. - 510.)
    G = 1.0
    B = 0.0
elif w >= 580 and w < 645:
    R = 1.0
    G = -(w - 645.) / (645. - 580.)
    B = 0.0
elif w >= 645 and w <= 780:
    R = 1.0
    G = 0.0
    B = 0.0
else:
    R = 0.0
    G = 0.0
    B = 0.0

答案 2 :(得分:8)

频率和所谓的Hue之间存在关系,但出于复杂的感知原因,监控色域和校准,您可以在昂贵的实验室设备之外实现最佳效果。

有关数学的信息,请参阅http://en.wikipedia.org/wiki/HSL_and_HSV,并注意您必须为Hue⇔频率映射提出最佳猜测。我希望这种经验映射不是线性的。

答案 3 :(得分:5)

我认为答案未能解决实际问题的问题。

RGB值通常来自XYZ颜色空间,该颜色空间是标准人类观察者函数,照明和样品在~360-830范围内的每个波长的相对功率的组合。

我不确定你想要在这里实现什么,但是可以为样本计算相对“准确”的RGB值,其中每个离散频带(例如10nm)完全饱和。变换看起来像Spectrum ->XYZ->RGB。查看Bruce Lindbloom的数学网站。在XYZ中,您还可以轻松计算huechromacolorimetric值,例如L*a*b*

答案 4 :(得分:5)

如果你想要精确匹配,那么唯一的解决方案就是用你的光谱值对x,y,z颜色匹配函数进行卷积,这样你最终得到一个(设备无关的)XYZ颜色表示,你可以在以后转换进入(取决于设备)RGB。

这里描述: http://www.cs.rit.edu/~ncs/color/t_spectr.html

你可以在这里找到卷积的x,y,z颜色匹配函数: http://cvrl.ioo.ucl.ac.uk/cmfs.htm

答案 5 :(得分:4)

这是color profiles处理的大部分内容。基本上,对于给定的设备(扫描仪,相机,显示器,打印机等),颜色配置文件会告知特定输入组的光的实际颜色。

另请注意,对于大多数真实设备,您只处理一些不连续的光波长,并且通过直接产生该波长来产生中间颜色 ,但通过混合不同数量的两个相邻波长可用的波长。鉴于我们以同样的方式感知颜色,这不是一个真正的问题,但取决于你关心的原因,无论如何都可能值得了解。

没有颜色配置文件(或等效信息),您缺少将RGB值映射到颜色所需的信息。纯红色的RGB值通常会映射到设备能够产生/感应的最红色(同样,纯蓝色到最蓝色) - 但“最红”或“最蓝”可以并且会变化(广泛)基于该设备。

答案 6 :(得分:3)

要将波长转换为RGB颜色

首先,您查阅CIE 1964 Supplementary Standard Colorimetric Observer图表archive

  

https://imgur.com/a/JDatZNm

并查找所需波长的 CIE颜色匹配功能值。

例如,我要获得455 nm光的颜色:

enter image description here

对于我们所需的波长:

| nm  | CIE color matching functions  |  Chromacity coordinates     |
| nm  |     X    |     Y    |    Z    |    x    |    y    |    z    |
|-----|----------|----------|---------|---------|---------|---------| 
| 455 | 0.342957 | 0.106256 | 1.90070 | 0.14594 | 0.04522 | 0.80884 |

注意:色度坐标仅根据CIE颜色匹配函数计算得出:

x = X / (X+Y+Z)
y = Y / (X+Y+Z)
z = Z / (Z+Y+Z)

鉴于:

X+Y+Z = 0.342257+0.106256+1.90070 = 2.349913

我们计算:

x = 0.342257 / 2.349913 = 0.145945
y = 0.106256 / 2.349913 = 0.045217
z = 1.900700 / 2.349913 = 0.808838

您使用两种不同的颜色空间指定了455 nm的光:

  • XYZ:(0.342957、0.106256、1.900700)
  • xyz:(0.145945、0.045217、0.808838)

我们还可以添加第三个颜色空间: xyY

x = x = 0.145945
y = y = 0.045217
Y = y = 0.045217

我们现在在3种不同的颜色空间中指定了455 nm的光:

  • XYZ:(0.342957、0.106256、1.900700)
  • xyz:(0.145945、0.045217、0.808838)
  • xyY :(0.145945、0.045217、0.045217)

因此,我们已经将纯单色发出的光的波长转换为 XYZ 颜色。现在我们要将其转换为RGB。

如何将XYZ转换为RGB?

XYZ xyz xyY 是使用绝对物理描述颜色的绝对颜色空间。

同时,人们使用的每个实用色彩空间:

  • 实验室
  • Luv
  • HSV
  • HSL
  • RGB

依赖一些 whitepoint 。然后将颜色描述为相对于该白点。

例如

  • RGB白(255,255,255)表示“白色”
  • 实验室白(100,0,0)表示“白色”
  • LCH白色(100、0、309)表示“白色”
  • HSL白色(240、0、100)表示“白色”
  • HSV白色(240、0、100)表示“白色”

但是没有白色这样的颜色。您如何定义白色?阳光的颜色?

  • 一天中的什么时间?
  • 云量多少?
  • 什么纬度?
  • 在地球上?

有些人使用其白炽灯泡(非常橙色)的白色来表示白色。有些人使用其荧光灯的颜色。白色没有绝对的物理定义-白色在我们的大脑中。

所以我们必须选择白色

我们必须选择白色。 (真的必须选择白色。)还有很多白色可供选择:

我会为您挑选一种白色。 sRGB使用的白色:

  • D65 -北欧晴朗夏日的日光照明

D65(其颜色接近6504K,但并非完全由于地球的大气层),其颜色为:

  • XYZ_D65:(0.95047,1.00000,1.08883)

这样,您可以将XYZ转换为Lab(或Luv)-一种同样能够表达所有理论色彩的色彩空间。现在,我们有了445 nm单色光的第四个颜色空间表示:

  • XYZ:(0.342957、0.106256、1.900700)
  • xyz:(0.145945、0.045217、0.808838)
  • xyY :(0.145945、0.045217、0.045217)
  • 实验室:(38.94259,119.14058,-146.08508)(假设d65)

但是您想要RGB

Lab(和Luv)是相对于 some 白点的色彩空间。即使您被迫选择任意白点,您仍然可以代表所有可能的颜色。

RGB不是那样。使用RGB:

  • 颜色不仅相对于某些白点
  • 但是相对于三种原色:红色,绿色,蓝色

如果您将RGB颜色指定为(255,0,0),则表示您要“仅红色”。但是没有红色的定义。没有“红色”,“绿色”或“蓝色”之类的东西。彩虹是连续的,没有箭头说:

  

这是红色的

这又意味着我们必须选择三个选择三种原色。您必须选择三种原色才能说出“红色”,“绿色”和“蓝色”。同样,您可以从红色,绿色,蓝色的许多不同定义中进行选择:

  • CIE 1931
  • ROMM RGB
  • Adob​​e Wide Gamut RGB
  • DCI-P3
  • NTSC(1953)
  • Apple RGB
  • sRGB
  • 日本NTSC
  • PAL / SECAM
  • Adob​​e RGB 98
  • scRGB

我帮你选。我会选择these three colors:

  • 红色:xyY =(0.6400,0.3300,0.2126)
  • 绿色:xyY =(0.3000,0.6000,0.7152)
  • 蓝色: xyY =(0.1500,0.0600,0.0722)

这些也是国际委员会在1996年选择的初选。

他们创建了一个每个人都应该使用的标准:

  • Whitepoint :D65日光
  • 红色 :( 0.6400、0.3300、0.2126)
  • 绿色 :( 0.3000、0.6000、0.7152)
  • 蓝色:(0.1500、0.0600、0.0722)

他们称该标准为sRGB

最后一击

现在我们选择了

  • 白点
  • 三个原语

我们现在可以convert you XYZ color into RGB:

  • RGB =(1.47450,-178.21694,345.59392)

不幸的是,该RGB值存在一些问题:

  • 您的监视器无法显示绿色(-178.21694);这意味着它是显示器无法显示的颜色。
  • 您的显示器不能显示超过255的蓝色(345.59392);显示器只能像蓝色一样呈蓝色-它不能变蓝。这意味着它是显示器无法显示的颜色。

所以我们必须舍入:

  • XYZ:(0.342957、0.106256、1.900700)
  • xyz:(0.145945、0.045217、0.808838)
  • xyY :(0.145945、0.045217、0.045217)
  • 实验室:(38.94259,119.14058,-146.08508)(d65)
  • RGB :(1、0、255)(sRGB)

现在,我们得到光的波长455 nm的最接近的sRGB:

enter image description here

答案 7 :(得分:1)

Patapom几乎是正确的:对于每个波长,你计算CIE XYZ值,然后使用标准公式将它们转换为(比方说)sRGB(如果你很幸运,你会找到你可以用来进行这种转换的代码) 。因此,关键步骤是获取XYZ值。幸运的是,对于单波长光,这很容易:XYZ色彩匹配功能只是列出给定波长的XYZ值的表格。所以只需查一查。如果你的光谱更复杂,可能是黑体,那么你必须将XYZ响应的平均值乘以光中每个波长的数量。

答案 8 :(得分:1)

我不是程序员。我不是医师。我只是一个有两只眼睛(就像任何人一样)的音乐家。

所以...我知道电磁波具有对数刻度模式 广播电视范围。为什么它在可见光范围内应该有所不同?

在广播电视世界中,我们使用一个简单的方程式:根据极端频率之间的比率,我们将两个频率之间的给定范围划分为一定数量的零件。

让我们说:如果我们的范围开始于100 MHZ,结束于200 MHZ,则比率为2(200等于100乘以2)。

因此,如果必须将该范围分成10个相等的部分,则必须使用以下等式:

第一个频率(100 MHZ)乘以2的第十个根。

该新值乘以2的第十个根。

依此类推。

为什么我们使用2的第十个根?简单:请记住,它不是线性音阶,而是对数音阶(与音符完全一样)。

因此,根据该等式,我们知道可见光谱在780纳米到380纳米之间(大约384.02 THZ和789.26 THZ;这两个值都非常接近,因为它是根据各个光学功能的可变值),我们只知道这些频率之间的比率:

789.26 / 384.02 = 2,055

此外,我们知道与极端频率等效的RGB是:

384.02 THZ = 95,0,0(hx = 5F0000)

788.92 THZ = 97,0,97(hx = 610061)

此外,我们知道这些点之间所有可能的RGB组合均为= 1595

因此,使用所有这些值,我们得到一个简单的方程式:

RGB = 95,0,0 = 384.02 THZ

RGB = 96,0,0 = 384.02 THZ乘以(2,055的1595根)= 384,19 THZ

RGB = 97,0,0 = 384,19 THZ乘以(2,055的1595根)= 384.37 THZ

依此类推

简单的比例尺。

只是我的拙见。

答案 9 :(得分:0)

VBA代码源自可见波长的近似&#34; RGB值&#34;作者:Dan Bruton(astro@tamu.edu)。 链接到他原来的Fortran代码:http://www.physics.sfasu.edu/astro/color/spectra.html Spectra计划:http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm

Sub Wavelength_To_RGB()

'Purpose: Loop thru the wavelengths in the visible spectrum of light
'         and output the RGB values and colors to a worksheet.
'         Wavelength range: 380nm and 780nm

Dim j As Long, CellRow As Long
Dim R As Double, G As Double, B As Double
Dim iR As Integer, iG As Integer, iB As Integer
Dim WL As Double
Dim Gamma As Double
Dim SSS As Double


Gamma = 0.8
CellRow = 1

For j = 380 To 780

  WL = j

  Select Case WL

  Case 380 To 440
      R = -(WL - 440#) / (440# - 380#)
      G = 0#
      B = 1#
  Case 440 To 490
      R = 0#
      G = ((WL - 440#) / (490# - 440#))
      B = 1#
  Case 490 To 510
      R = 0#
      G = 1#
      B = (-(WL - 510#) / (510# - 490#))
  Case 510 To 580
      R = ((WL - 510#) / (580# - 510#))
      G = 1#
      B = 0#
  Case 580 To 645
      R = 1#
      G = (-(WL - 645#) / (645# - 580#))
      B = 0#
  Case 645 To 780
      R = 1#
      G = 0#
      B = 0#
  Case Else
      R = 0#
      G = 0#
      B = 0#
  End Select

  'LET THE INTENSITY SSS FALL OFF NEAR THE VISION LIMITS
  If WL > 700 Then
     SSS = 0.3 + 0.7 * (780# - WL) / (780# - 700#)
  ElseIf WL < 420 Then
     SSS = 0.3 + 0.7 * (WL - 380#) / (420# - 380#)
  Else
     SSS = 1#
  End If

  'GAMMA ADJUST
  R = (SSS * R) ^ Gamma
  G = (SSS * G) ^ Gamma
  B = (SSS * B) ^ Gamma

  'Multiply by 255
  R = R * 255
  G = G * 255
  B = B * 255

  'Change RGB data type from Double to Integer.
  iR = CInt(R)
  iG = CInt(G)
  iB = CInt(B)

  'Output to worksheet
  Cells(CellRow, 1).Interior.Color = RGB(iR, iG, iB)
  Cells(CellRow, 2) = WL
  Cells(CellRow, 3) = "(" & iR & "," & iG & "," & iB & ")"
  CellRow = CellRow + 1

Next j


End Sub

答案 10 :(得分:0)

基于流行的answer的可运行示例:

function spectrogram() {
  var svgns = 'http://www.w3.org/2000/svg';
  var svg = document.createElementNS(svgns, 'svg');
  var defs = document.createElementNS(svgns, 'defs');
  var gradient = document.createElementNS(svgns, 'linearGradient');
  var rect = document.createElementNS(svgns, 'rect');

  var stops = spectral_gradient( 400, 700, 3 );

  for( var i = 0, length = stops.length; i < length; i++ ) {
    var stop = document.createElementNS(svgns, 'stop');
    stop.setAttribute('offset', stops[i].offset);
    stop.setAttribute('stop-color', stops[i].color);
    gradient.appendChild(stop);
  }

  // Apply the <lineargradient> to <defs>
  gradient.id = 'Gradient';
  gradient.setAttribute('x1', '0');
  gradient.setAttribute('x2', '1');
  gradient.setAttribute('y1', '0');
  gradient.setAttribute('y2', '0');
  defs.appendChild(gradient);

  // Setup the <rect> element.
  rect.setAttribute('fill', 'url(#Gradient)');
  rect.setAttribute('width', '100%');
  rect.setAttribute('height', '100%');

  // Assign an id, classname, width and height
  svg.setAttribute('width', '100%');
  svg.setAttribute('height', '100%')
  svg.setAttribute('version', '1.1');
  svg.setAttribute('xmlns', svgns);

  // Add the <defs> and <rect> elements to <svg>
  svg.appendChild(defs);
  svg.appendChild(rect);

  // Add the <svg> element to <body>
  document.body.appendChild(svg);
}

function spectral_gradient( wl1, wl2, steps ) {
  var stops = [];
  var delta = Math.abs( wl2 - wl1 );

  for( var wl = wl1; wl <= wl2; wl += steps ) {
    var offset = Math.round( (1 - Math.abs( wl2 - wl ) / delta) * 100 );
    stops.push({
      "color": wavelength2hex( wl ),
      "offset": offset + "%"
    });
  }

  return stops;
}

function wavelength2hex( l ) {
  var wl = wavelength2rgb( l );
  var rgb = {
    "r": Math.round( wl.r * 255 ),
    "g": Math.round( wl.g * 255 ),
    "b": Math.round( wl.b * 255 )
  };

  return rgb2hex( rgb.r, rgb.g, rgb.b );
}

function wavelength2rgb( l ) {
  var t;
  var r = 0.0;
  var g = 0.0;
  var b = 0.0;

  if ((l >= 400.0) && (l < 410.0)) {
    t = (l - 400.0) / (410.0 - 400.0);
    r = +(0.33 * t) - (0.20 * t * t);
  } else if ((l >= 410.0) && (l < 475.0)) {
    t = (l - 410.0) / (475.0 - 410.0);
    r = 0.14 - (0.13 * t * t);
  } else if ((l >= 545.0) && (l < 595.0)) {
    t = (l - 545.0) / (595.0 - 545.0);
    r = +(1.98 * t) - (t * t);
  } else if ((l >= 595.0) && (l < 650.0)) {
    t = (l - 595.0) / (650.0 - 595.0);
    r = 0.98 + (0.06 * t) - (0.40 * t * t);
  } else if ((l >= 650.0) && (l < 700.0)) {
    t = (l - 650.0) / (700.0 - 650.0);
    r = 0.65 - (0.84 * t) + (0.20 * t * t);
  }

  if ((l >= 415.0) && (l < 475.0)) {
    t = (l - 415.0) / (475.0 - 415.0);
    g = +(0.80 * t * t);
  } else if ((l >= 475.0) && (l < 590.0)) {
    t = (l - 475.0) / (590.0 - 475.0);
    g = 0.8 + (0.76 * t) - (0.80 * t * t);
  } else if ((l >= 585.0) && (l < 639.0)) {
    t = (l - 585.0) / (639.0 - 585.0);
    g = 0.84 - (0.84 * t);
  }

  if ((l >= 400.0) && (l < 475.0)) {
    t = (l - 400.0) / (475.0 - 400.0);
    b = +(2.20 * t) - (1.50 * t * t);
  } else if ((l >= 475.0) && (l < 560.0)) {
    t = (l - 475.0) / (560.0 - 475.0);
    b = 0.7 - (t) + (0.30 * t * t);
  }

  return {"r": r, "g": g, "b": b};
}

function rgb2hex( r, g, b ) {
  return "#" + hex( r ) + hex( g ) + hex( b );
}

function hex( v ) {
  return v.toString( 16 ).padStart( 2, "0" );
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <script src="js/spectrum.js"></script>
</head>
<body onload="spectrogram();">
</body>
</html>