Звездный B-V цветной индекс до кажущегося цвета RGB

Я пытаюсь преобразовать звездочку индекс цвета B-V в кажущийся цвет RGB. Помимо поиска таблиц и цветовых рамп, кажется, что для этого не существует хорошо известного алгоритма.

Какой индекс цвета B-V?

Это число астрономов назначает звезде, чтобы указать ее видимый цвет. Горячие звезды (низкий B-V) являются синими/фиолетовыми, а холодные звезды (высокий B-V) красны между этими звездами белого/оранжевого цвета.

Начальный алгоритм

B-V до Кельвина

enter image description here

var t = 4600 * ((1 / ((0.92 * bv) + 1.7)) +(1 / ((0.92 * bv) + 0.62)) );

Кельвины к xyY

Если вы моделируете звезду как черное тело, то вы можете использовать численное приближение Planckian locus для вычисления координат xy (CIE цветность)

enter image description here

enter image description here

enter image description here

// t to xyY
var x, y = 0;

if (t>=1667 && t<=4000) {
  x = ((-0.2661239 * Math.pow(10,9)) / Math.pow(t,3)) + ((-0.2343580 * Math.pow(10,6)) / Math.pow(t,2)) + ((0.8776956 * Math.pow(10,3)) / t) + 0.179910;
} else if (t > 4000 && t <= 25000) {
  x = ((-3.0258469 * Math.pow(10,9)) / Math.pow(t,3)) + ((2.1070379 * Math.pow(10,6)) / Math.pow(t,2)) + ((0.2226347 * Math.pow(10,3)) / t) + 0.240390;
}

if (t >= 1667 && t <= 2222) {
  y = -1.1063814 * Math.pow(x,3) - 1.34811020 * Math.pow(x,2) + 2.18555832 * x - 0.20219683;
} else if (t > 2222 && t <= 4000) {
  y = -0.9549476 * Math.pow(x,3) - 1.37418593 * Math.pow(x,2) + 2.09137015 * x - 0.16748867;
} else if (t > 4000 && t <= 25000) {
  y = 3.0817580 * Math.pow(x,3) - 5.87338670 * Math.pow(x,2) + 3.75112997 * x - 0.37001483;
}

xyY в XYZ (Y = 1)

enter image description here

enter image description here

// xyY to XYZ, Y = 1
var Y = (y == 0)? 0 : 1;
var X = (y == 0)? 0 : (x * Y) / y;
var Z = (y == 0)? 0 : ((1 - x - y) * Y) / y;

XYZ to RGB

enter image description here

var r = 0.41847 * X - 0.15866 * Y - 0.082835 * Z;
var g = -0.091169 * X + 0.25243 * Y + 0.015708 * Z;
var b = 0.00092090 * X - 0.0025498 * Y + 0.17860 * Z;

Вопрос

Я запустил этот алгоритм с индексами цвета B-V: 1.2, 1.0, 0.59, 0.0, -0.29. Это то, что я получил как результат.

enter image description here

Почему я получил этот странный результат? Горячие звезды голубоватые, но холодные звезды буроватые, и, похоже, не белые/оранжевые промежуточные звезды.

Update

Следуя комментарию Ozan, мне показалось, что я использовал неправильную матрицу для преобразования XYZ в RGB. Поскольку sRGB - это цветное пространство по умолчанию в Интернете (или оно?), Теперь я использую правильную матрицу, за которой следует функция гамма-коррекции (a = 0.055).

enter image description here

enter image description here

Теперь я получаю эту красивую рампу,

enter image description here

но на концах все еще нет красного/фиолетового.

Demo

Там также есть fiddle, с которым вы можете играть.

Обновление 2

Если вы используете гамма 0,5 и расширяете диапазон цветовых индексов B-V от 4,7 до -0,5, я получаю красный цвет с одной стороны, но до сих пор не фиолетовый. Здесь обновлен fiddle.

<Т411 >

Ответ 1

Вместо этого я использую встроенную интерполяцию. Несколько лет назад я нашел эту таблицу где-то:

     type     r   g   b    rrggbb        B-V

     O5(V)   155 176 255  #9bb0ff       -0.32 blue
     O6(V)   162 184 255  #a2b8ff
     O7(V)   157 177 255  #9db1ff
     O8(V)   157 177 255  #9db1ff
     O9(V)   154 178 255  #9ab2ff
   O9.5(V)   164 186 255  #a4baff
     B0(V)   156 178 255  #9cb2ff
   B0.5(V)   167 188 255  #a7bcff
     B1(V)   160 182 255  #a0b6ff
     B2(V)   160 180 255  #a0b4ff
     B3(V)   165 185 255  #a5b9ff
     B4(V)   164 184 255  #a4b8ff
     B5(V)   170 191 255  #aabfff
     B6(V)   172 189 255  #acbdff
     B7(V)   173 191 255  #adbfff
     B8(V)   177 195 255  #b1c3ff
     B9(V)   181 198 255  #b5c6ff
     A0(V)   185 201 255  #b9c9ff       0.00 White
     A1(V)   181 199 255  #b5c7ff
     A2(V)   187 203 255  #bbcbff
     A3(V)   191 207 255  #bfcfff
     A5(V)   202 215 255  #cad7ff
     A6(V)   199 212 255  #c7d4ff
     A7(V)   200 213 255  #c8d5ff
     A8(V)   213 222 255  #d5deff
     A9(V)   219 224 255  #dbe0ff
     F0(V)   224 229 255  #e0e5ff       0.31 yellowish
     F2(V)   236 239 255  #ecefff
     F4(V)   224 226 255  #e0e2ff
     F5(V)   248 247 255  #f8f7ff
     F6(V)   244 241 255  #f4f1ff
     F7(V)   246 243 255  #f6f3ff       0.50
     F8(V)   255 247 252  #fff7fc
     F9(V)   255 247 252  #fff7fc
     G0(V)   255 248 252  #fff8fc       0.59  Yellow
     G1(V)   255 247 248  #fff7f8
     G2(V)   255 245 242  #fff5f2
     G4(V)   255 241 229  #fff1e5
     G5(V)   255 244 234  #fff4ea
     G6(V)   255 244 235  #fff4eb
     G7(V)   255 244 235  #fff4eb
     G8(V)   255 237 222  #ffedde
     G9(V)   255 239 221  #ffefdd
     K0(V)   255 238 221  #ffeedd       0.82 Orange
     K1(V)   255 224 188  #ffe0bc
     K2(V)   255 227 196  #ffe3c4
     K3(V)   255 222 195  #ffdec3
     K4(V)   255 216 181  #ffd8b5
     K5(V)   255 210 161  #ffd2a1
     K7(V)   255 199 142  #ffc78e
     K8(V)   255 209 174  #ffd1ae
     M0(V)   255 195 139  #ffc38b       1.41 red
     M1(V)   255 204 142  #ffcc8e
     M2(V)   255 196 131  #ffc483
     M3(V)   255 206 129  #ffce81
     M4(V)   255 201 127  #ffc97f
     M5(V)   255 204 111  #ffcc6f
     M6(V)   255 195 112  #ffc370
     M8(V)   255 198 109  #ffc66d       2.00
  • просто интерполировать отсутствующие индексы B-V (линейно или лучше) перед использованием
  • затем используйте линейную интерполяцию, чтобы получить RGB = f (B-V);
  • найти ближайшую две строки в таблице и интерполировать между ними...

[edit1] heh просто случайно встречается this (оригинальная информация, о которой я упоминал ранее)

[edit2] здесь мое приближение без каких-либо материалов XYZ

BV to RGB

Итак, индекс BV от < -0.4 , 2.0 >

вот мой (С++) код для преобразования:

//---------------------------------------------------------------------------
void bv2rgb(double &r,double &g,double &b,double bv)    // RGB <0,1> <- BV <-0.4,+2.0> [-]
    {
    double t;  r=0.0; g=0.0; b=0.0; if (bv<-0.4) bv=-0.4; if (bv> 2.0) bv= 2.0;
         if ((bv>=-0.40)&&(bv<0.00)) { t=(bv+0.40)/(0.00+0.40); r=0.61+(0.11*t)+(0.1*t*t); }
    else if ((bv>= 0.00)&&(bv<0.40)) { t=(bv-0.00)/(0.40-0.00); r=0.83+(0.17*t)          ; }
    else if ((bv>= 0.40)&&(bv<2.10)) { t=(bv-0.40)/(2.10-0.40); r=1.00                   ; }
         if ((bv>=-0.40)&&(bv<0.00)) { t=(bv+0.40)/(0.00+0.40); g=0.70+(0.07*t)+(0.1*t*t); }
    else if ((bv>= 0.00)&&(bv<0.40)) { t=(bv-0.00)/(0.40-0.00); g=0.87+(0.11*t)          ; }
    else if ((bv>= 0.40)&&(bv<1.60)) { t=(bv-0.40)/(1.60-0.40); g=0.98-(0.16*t)          ; }
    else if ((bv>= 1.60)&&(bv<2.00)) { t=(bv-1.60)/(2.00-1.60); g=0.82         -(0.5*t*t); }
         if ((bv>=-0.40)&&(bv<0.40)) { t=(bv+0.40)/(0.40+0.40); b=1.00                   ; }
    else if ((bv>= 0.40)&&(bv<1.50)) { t=(bv-0.40)/(1.50-0.40); b=1.00-(0.47*t)+(0.1*t*t); }
    else if ((bv>= 1.50)&&(bv<1.94)) { t=(bv-1.50)/(1.94-1.50); b=0.63         -(0.6*t*t); }
    }
//---------------------------------------------------------------------------

[Примечание]

Этот цвет BV является черным цветом определенной освещенности, поэтому он представляет собой цвет звезды, рассматриваемый из космоса относительно звезды. Для визуально правильных цветов вам необходимо добавить эффекты атмосферного рассеяния нашей атмосферы и эффект Допплера для быстрых косилок!, например, наше Солнце "Белое", но после рассеяния света цвет варьируется от красного (вблизи горизонт) до желтого (около надира... полудня)

Если вы хотите визуально исправить цвет, этот QA может помочь:

Ответ 2

На всякий случай кому-то еще нужно преобразовать удобный С++ @Spektre в python. Я взял некоторые из дубликатов (которые, несомненно, фиксировал компилятор) и разрывы для g, когда bv>=2.0 и b, когда 1.94<bv<1.9509

def bv2rgb(bv):
  if bv < -0.4: bv = -0.4
  if bv > 2.0: bv = 2.0
  if bv >= -0.40 and bv < 0.00:
    t = (bv + 0.40) / (0.00 + 0.40)
    r = 0.61 + 0.11 * t + 0.1 * t * t
    g = 0.70 + 0.07 * t + 0.1 * t * t
    b = 1.0
  elif bv >= 0.00 and bv < 0.40:
    t = (bv - 0.00) / (0.40 - 0.00)
    r = 0.83 + (0.17 * t)
    g = 0.87 + (0.11 * t)
    b = 1.0
  elif bv >= 0.40 and bv < 1.60:
    t = (bv - 0.40) / (1.60 - 0.40)
    r = 1.0
    g = 0.98 - 0.16 * t
  else:
    t = (bv - 1.60) / (2.00 - 1.60)
    r = 1.0
    g = 0.82 - 0.5 * t * t
  if bv >= 0.40 and bv < 1.50:
    t = (bv - 0.40) / (1.50 - 0.40)
    b = 1.00 - 0.47 * t + 0.1 * t * t
  elif bv >= 1.50 and bv < 1.951:
    t = (bv - 1.50) / (1.94 - 1.50)
    b = 0.63 - 0.6 * t * t
  else:
    b = 0.0
  return (r, g, b)

Ответ 3

Вы попросили алгоритм, вы получите его.

Я исследовал эту тему, когда я предоставлял данные из базы данных HYG в Python3.5 с Pyglet и MongoDB. Я доволен тем, как мои звезды смотрят в мою starmap. Цвета можно найти в нижней части этого ответа.

1. Индекс цвета (B-V) до температуры (K)

Это функция, которую я использовал для данных B-V (ci) из базы данных HYG. В этом примере ci является значением B-V из списка, в котором я выполняю.

    temp = 4600 * (1 / (0.92 * ci + 1.7) + 1 / (0.92 * ci + 0.62))

2. Получите большой стол.

Я взял этот, и я предлагаю вам сделать тоже. Выберите столбец температуры и столбец значений RGB или rgb в качестве ссылки

3. Предварительная обработка данных.

Из данных таблицы rgb я сгенерировал три упорядоченных списка (n = 391) (мой метод: очистка и выбор с помощью программного обеспечения электронной таблицы и текстовый редактор, способный одновременно иметь миллионы курсоров, затем импортировать результирующие разделенные запятыми файл mongoDB, поэтому я мог бы легко работать со списками значений в python через оболочку pymongo, без лишних помех в файле script). Преимущество метода, который я буду выкладывать, заключается в том, что вы можете отображать данные цвета из других таблиц, которые могут использовать CMYK или HSV и соответствующим образом адаптироваться. Вы можете даже перекрестно ссылаться. Однако вы должны составить списки, которые выглядят так, из таблицы RGB, предложенной мной;

    reds = [255, 255, ... , 155, 155]
    greens = [56, 71, ..., 188,188]
    blues = [0, 0, ..., 255, 255]

    """ this temps list is also (n=391) and corresponds to the table values."""
    temps = []
    for i in range(1000,40100,100):
        temps.append(i)

После этого я применил некоторые гауссовские сглаживания к этим спискам (он помогает получить лучшие полиномы, поскольку он избавляется от некоторой флуктуации), после чего я применил метод polyfit() (полиномиальная регрессия) от пакета numpy до значений температуры относительно R, G и B значения

colors = [reds,greens,blues]

""" you can tweak the degree value to see if you can get better coeffs. """
def smoothListGaussian2(myarray, degree=3):
    myarray = np.pad(myarray, (degree-1,degree-1), mode='edge')
    window=degree*2-1
    weight=np.arange(-degree+1, degree)/window
    weight = np.exp(-(16*weight**2))
    weight /= sum(weight)
    smoothed = np.convolve(myarray, weight, mode='valid')
    return smoothed

i=0

for color in colors:

    color = smoothListGaussian2(color)
    x = np.array(temps)
    y = np.array(color)

    names = ["reds","greens","blues"]
    """ raise/lower the k value (third one) in c """
    z = np.polyfit(x, y, 20)
    f = np.poly1d(z)
    #plt.plot(x,f(x),str(names[i][0]+"-"))
    print("%sPoly = " % names[i], z)

    i += 1
plt.show()

Это дает вам (n) коэффициенты (a) для многочленов вида:

введите описание изображения здесь.

Подумайте об этом сейчас, вы, вероятно, можете использовать polyfit, чтобы придумать коэффициенты для преобразования CI прямо в RGB... и пропустить шаг CI to temperature conversion, но путем преобразования в во-первых, связь между температурой и выбранным цветовым пространством более понятна.

4. Фактический алгоритм: значения температуры плагина в полиномах RGB

Как я уже говорил, вы можете использовать другие спектральные данные и другие цветовые пространства для соответствия полиномиальным кривым, этот шаг все равно будет тем же (с небольшими изменениями)

В любом случае, здесь простой код в полном объеме, который я использовал (также, это с k = 20 полиномами):

import numpy as np

redco = [ 1.62098281e-82, -5.03110845e-77, 6.66758278e-72, -4.71441850e-67, 1.66429493e-62, -1.50701672e-59, -2.42533006e-53, 8.42586475e-49, 7.94816523e-45, -1.68655179e-39, 7.25404556e-35, -1.85559350e-30, 3.23793430e-26, -4.00670131e-22, 3.53445102e-18, -2.19200432e-14, 9.27939743e-11, -2.56131914e-07,  4.29917840e-04, -3.88866019e-01, 3.97307766e+02]
greenco = [ 1.21775217e-82, -3.79265302e-77, 5.04300808e-72, -3.57741292e-67, 1.26763387e-62, -1.28724846e-59, -1.84618419e-53, 6.43113038e-49, 6.05135293e-45, -1.28642374e-39, 5.52273817e-35, -1.40682723e-30, 2.43659251e-26, -2.97762151e-22, 2.57295370e-18, -1.54137817e-14, 6.14141996e-11, -1.50922703e-07,  1.90667190e-04, -1.23973583e-02,-1.33464366e+01]
blueco = [ 2.17374683e-82, -6.82574350e-77, 9.17262316e-72, -6.60390151e-67, 2.40324203e-62, -5.77694976e-59, -3.42234361e-53, 1.26662864e-48, 8.75794575e-45, -2.45089758e-39, 1.10698770e-34, -2.95752654e-30, 5.41656027e-26, -7.10396545e-22, 6.74083578e-18, -4.59335728e-14, 2.20051751e-10, -7.14068799e-07,  1.46622559e-03, -1.60740964e+00, 6.85200095e+02]

redco = np.poly1d(redco)
greenco = np.poly1d(greenco)
blueco = np.poly1d(blueco)

def temp2rgb(temp):
    red = 0
    green = 0
    blue = 0

    """ since all lists have to have equal length, this is ok."""

    red = redco(temp)
    green = greenco(temp)
    blue = blueco(temp)

    print(red,green,blue)

    if round(red) > 255:
        red = 255
    elif red < 0:
        red = 0
    if round(green) > 255:
        green = 255
    elif green < 0:
        green = 0
    if round(blue) > 255:
        blue = 255
    elif blue < 0:
        blue = 0

    color = (int(red),
             int(green),
             int(blue))
    print(color)
    return color

О, и еще несколько заметок и изображений...

Шкала температуры черного тела OBAFGKM от моих многочленов:

введите описание изображения здесь

Сюжет для RGB [0-255] по темпу [0-40000K],

  • +: данные таблицы
  • кривые: полиномиальное соответствие введите описание изображения здесь Увеличение по наименьшим значениям точности: введите описание изображения здесь

Здесь фиолетовый

Как вы можете видеть, есть некоторые отклонения, но это вряд ли заметно невооруженным глазом, и если вы действительно хотите улучшить его (я этого не делаю), у вас есть другие варианты:

  • Разделите списки, где значение зеленого цвета самое высокое, и посмотрите, получите ли вы лучшие полиномы для новых левых и правых частей списков. Немного нравится:

Корректирующие меры.

  1. Напишите правила исключения (возможно, просто k = 2 или k = 3 poly) для значений в этом окне наименьшей точности.
  2. Попробуйте использовать другие алгоритмы сглаживания перед полифитом().
  3. Попробуйте использовать другие источники или цветовые пространства.

Я также доволен общей работой моих полиномов. Когда я загружаю объекты 120000 звезд моей заправки с размером не менее 18 цветных вершин, это занимает всего несколько секунд, к моему большому удивлению. Однако есть возможности для улучшения. Для более реалистичного представления (вместо того, чтобы просто работать с световым излучением черного тела), я мог бы добавить гравитационное линзирование, атмосферные эффекты, релятивистский допплер и т.д.

О, и PURPLE, как и было обещано.

Некоторые другие полезные ссылки:

Ответ 4

Почему нет фиолетового или темно-синего? Бесконечная цветовая температура, до того, как наша атмосфера стала менее синеватой, имеет 1931 CIE-координаты X =.240, y =.234.

Спектр черного тела при бесконечной цветовой температуре имеет спектральное распределение мощности по мощности на единицу длины волны ширины полосы, обратно пропорциональное длине волны до 4-й степени. При 700 нм это составляет 10,7%, как при 400 нм.

Ответ 5

В качестве поправки к коду @paddyg, который не работал у меня (особенно для цвета с bv < 0.4): вот точная версия кода С++ для @Spektre в Python:

def bv2rgb(bv):
    if bv < -0.40: bv = -0.40
    if bv > 2.00: bv = 2.00

    r = 0.0
    g = 0.0
    b = 0.0

    if  -0.40 <= bv<0.00:
        t=(bv+0.40)/(0.00+0.40)
        r=0.61+(0.11*t)+(0.1*t*t)
    elif 0.00 <= bv<0.40:
        t=(bv-0.00)/(0.40-0.00)
        r=0.83+(0.17*t)
    elif 0.40 <= bv<2.10:
        t=(bv-0.40)/(2.10-0.40)
        r=1.00
    if  -0.40 <= bv<0.00:
        t=(bv+0.40)/(0.00+0.40)
        g=0.70+(0.07*t)+(0.1*t*t)
    elif 0.00 <= bv<0.40:
        t=(bv-0.00)/(0.40-0.00)
        g=0.87+(0.11*t)
    elif 0.40 <= bv<1.60:
        t=(bv-0.40)/(1.60-0.40)
        g=0.98-(0.16*t)
    elif 1.60 <= bv<2.00:
        t=(bv-1.60)/(2.00-1.60)
        g=0.82-(0.5*t*t)
    if  -0.40 <= bv<0.40:
        t=(bv+0.40)/(0.40+0.40)
        b=1.00
    elif 0.40 <= bv<1.50:
        t=(bv-0.40)/(1.50-0.40)
        b=1.00-(0.47*t)+(0.1*t*t)
    elif 1.50 <= bv<1.94:
        t=(bv-1.50)/(1.94-1.50)
        b=0.63-(0.6*t*t)

    return (r, g, b)

Ответ 6

@Spektre ответ в Swift 3.0:

private func bv2ToRGB(for bv: CGFloat, logging: Bool = false) -> Color {
    var bv = bv
    var t: CGFloat = 0
    var r: CGFloat = 0
    var g: CGFloat = 0
    var b: CGFloat = 0

    if bv < -0.4 { bv = -0.4}
    if bv > 2.0 { bv = 2.0}

    switch bv {
    case -0.4 ... 0.0:
        t = (bv+0.40)/(0.00+0.40)
        r = 0.61+(0.11*t)+(0.1*t*t)
    case 0.0 ... 0.4:
        t = (bv-0.00)/(0.40-0.00)
        r = 0.83+(0.17*t)
    case 0.4 ... 2.1:
        t = (bv-0.40)/(2.10-0.40)
        r = 1.00
    default: break
    }

    switch bv {
    case -0.4 ... 0.0:
        t = (bv+0.40)/(0.00+0.40)
        g = 0.70 + (0.07*t)+(0.1*t*t)
    case 0.0 ... 0.4:
        t = (bv-0.00)/(0.40-0.00)
        g = 0.87 + (0.11*t)
    case 0.4 ... 1.6:
        t = (bv-0.40)/(1.60-0.40)
        g = 0.98 - (0.16*t)
    case 1.6 ... 2.0:
        t = (bv-1.60)/(2.00-1.60)
        g = 0.82         - (0.5*t*t)
    default: break
    }

    switch bv {
    case -0.4 ... 0.4:
        t = (bv+0.40)/(0.40+0.40)
        b = 1.0
    case 0.4 ... 1.5:
        t = (bv-0.40)/(1.50-0.40)
        b = 1.00 - (0.47*t)+(0.1*t*t)
    case 1.5 ... 1.94:
        t = (bv-1.50)/(1.94-1.50)
        b = 0.63         - (0.6*t*t)
    default: break
    }

    #if os(OSX)
        return NSColor(calibratedRed: r, green: g, blue: b, alpha: 1.0)
    #else
        return UIColor(red: r, green: g, blue: b, alpha: 1.0)
    #endif
}

Ответ 7

В ответ на вопрос, почему нет фиалки?: Я думаю, что ответ заключается в том, что звезды просто не такие. Вернее, они не получают этот цвет, когда мы фотографируем их. Цвета, полученные на этой резьбе для различных температур/значений B-V, кажутся мне очень точными. Возьмите эту фотографию, которую я взял с Альбирео в Лебеде: https://www.flickr.com/photos/[email protected]/6939409750/in/photolist-bB54th-bzdhKG Albireo A (слева) - звезда типа K с B-V 1.074, а Alberio B (справа) - звезда типа B с B-V от -0.06. Глядя на цвета в диаграммах выше для тех значений B-V, я бы сказал, что там довольно сильная корреляция с изображением. Кроме того, не забывайте, что даже для очень горячих звезд все еще будет некоторый выход на более длинных волнах, что будет иметь тенденцию обесцветить "синеву". Излучение черного тела - широкий спектр.