Звездный 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;
}
![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;
![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],
- +: данные таблицы
- кривые: полиномиальное соответствие
Увеличение по наименьшим значениям точности:
![введите описание изображения здесь]()
Здесь фиолетовый
Как вы можете видеть, есть некоторые отклонения, но это вряд ли заметно невооруженным глазом, и если вы действительно хотите улучшить его (я этого не делаю), у вас есть другие варианты:
- Разделите списки, где значение зеленого цвета самое высокое, и посмотрите, получите ли вы лучшие полиномы для новых левых и правых частей списков. Немного нравится:
![Корректирующие меры.]()
- Напишите правила исключения (возможно, просто k = 2 или k = 3 poly) для значений в этом окне наименьшей точности.
- Попробуйте использовать другие алгоритмы сглаживания перед полифитом().
- Попробуйте использовать другие источники или цветовые пространства.
Я также доволен общей работой моих полиномов. Когда я загружаю объекты 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, я бы сказал, что там довольно сильная корреляция с изображением.
Кроме того, не забывайте, что даже для очень горячих звезд все еще будет некоторый выход на более длинных волнах, что будет иметь тенденцию обесцветить "синеву". Излучение черного тела - широкий спектр.