Оттенки серого до красно-зелено-синего (MATLAB Jet)

Мне дали набор данных, который по сути является изображением, однако каждый пиксель в изображении представлен как значение от -1 до 1 включительно. Я пишу приложение, которое должно принять значения от 1 до 1 в градациях серого и сопоставить их с соответствующим значением RGB для цветовой шкалы MATLAB "Jet" (красно-зелено-синий градиент цвета).

Мне любопытно, если кто-нибудь знает, как взять линейное значение (например, от -1 до 1) и отобразить его в этом масштабе. Обратите внимание, что я на самом деле не использую MATLAB для этого (и я не могу), мне просто нужно взять значение в градациях серого и поместить его в градиент Jet.

Спасибо адам

6 ответов

Решение

Я надеюсь, что это то, что вы ищете:

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
  return (val-x0)*(y1-y0)/(x1-x0) + y0;
}
double blue( double grayscale ) {
  if ( grayscale < -0.33 ) return 1.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 1.0, -0.33, 0.0, 0.33 );
  else return 0.0;
}
double green( double grayscale ) {
  if ( grayscale < -1.0 ) return 0.0; // unexpected grayscale value
  if  ( grayscale < -0.33 ) return interpolate( grayscale, 0.0, -1.0, 1.0, -0.33 );
  else if ( grayscale < 0.33 ) return 1.0;
  else if ( grayscale <= 1.0 ) return interpolate( grayscale, 1.0, 0.33, 0.0, 1.0 );
  else return 1.0; // unexpected grayscale value
}
double red( double grayscale ) {
  if ( grayscale < -0.33 ) return 0.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 0.0, -0.33, 1.0, 0.33 );
  else return 1.0;
}

Я не уверен, что этот масштаб на 100% идентичен изображению, которое вы связали, но оно должно выглядеть очень похоже.

ОБНОВЛЕНИЕ Я переписал код в соответствии с описанием палитры MatLab Jet, найденной здесь

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
    return (val-x0)*(y1-y0)/(x1-x0) + y0;
}

double base( double val ) {
    if ( val <= -0.75 ) return 0;
    else if ( val <= -0.25 ) return interpolate( val, 0.0, -0.75, 1.0, -0.25 );
    else if ( val <= 0.25 ) return 1.0;
    else if ( val <= 0.75 ) return interpolate( val, 1.0, 0.25, 0.0, 0.75 );
    else return 0.0;
}

double red( double gray ) {
    return base( gray - 0.5 );
}
double green( double gray ) {
    return base( gray );
}
double blue( double gray ) {
    return base( gray + 0.5 );
}

Рассмотрим следующую функцию (написанную Полом Бурком - поиск Colour Ramping for Data Visualisation):

/*
   Return a RGB colour value given a scalar v in the range [vmin,vmax]
   In this case each colour component ranges from 0 (no contribution) to
   1 (fully saturated), modifications for other ranges is trivial.
   The colour is clipped at the end of the scales if v is outside
   the range [vmin,vmax]
*/

typedef struct {
    double r,g,b;
} COLOUR;

COLOUR GetColour(double v,double vmin,double vmax)
{
   COLOUR c = {1.0,1.0,1.0}; // white
   double dv;

   if (v < vmin)
      v = vmin;
   if (v > vmax)
      v = vmax;
   dv = vmax - vmin;

   if (v < (vmin + 0.25 * dv)) {
      c.r = 0;
      c.g = 4 * (v - vmin) / dv;
   } else if (v < (vmin + 0.5 * dv)) {
      c.r = 0;
      c.b = 1 + 4 * (vmin + 0.25 * dv - v) / dv;
   } else if (v < (vmin + 0.75 * dv)) {
      c.r = 4 * (v - vmin - 0.5 * dv) / dv;
      c.b = 0;
   } else {
      c.g = 1 + 4 * (vmin + 0.75 * dv - v) / dv;
      c.b = 0;
   }

   return(c);
}

Который, в вашем случае, вы бы использовали его для отображения значений в диапазоне [-1,1] для цветов как (это легко перевести из кода C в функцию MATLAB):

c = GetColour(v,-1.0,1.0);

Это приводит к следующей цветовой шкале "горячего к холодному":

color_ramp

Он в основном представляет собой переход по краям цветового куба RGB от синего к красному (проходящий через голубой, зеленый, желтый) и интерполяцию значений по этому пути.

color_cube


Обратите внимание, что это немного отличается от цветовой карты "Jet", используемой в MATLAB, которая, насколько я могу судить, проходит по следующему пути:

#00007F: dark blue
#0000FF: blue
#007FFF: azure
#00FFFF: cyan
#7FFF7F: light green
#FFFF00: yellow
#FF7F00: orange
#FF0000: red
#7F0000: dark red

Вот сравнение, которое я сделал в MATLAB:

%# values
num = 64;
v = linspace(-1,1,num);

%# colormaps
clr1 = jet(num);
clr2 = zeros(num,3);
for i=1:num
    clr2(i,:) = GetColour(v(i), v(1), v(end));
end

Затем мы строим оба графика, используя:

figure
subplot(4,1,1), imagesc(v), colormap(clr), axis off
subplot(4,1,2:4), h = plot(v,clr); axis tight
set(h, {'Color'},{'r';'g';'b'}, 'LineWidth',3)

реактивный самолетhot_to_cold

Теперь вы можете изменить приведенный выше код C и использовать предложенные точки останова для достижения чего-то похожего на цветовую карту jet (все они используют линейную интерполяцию по каналам R,G,B, как вы можете видеть из приведенных выше графиков)...

Другие ответы трактуют интерполяцию как кусочно-линейную функцию. Это может быть упрощено путем использования фиксированной треугольной базисной функции для интерполяции. Нам нужна функция зажима, которая отображает свой вход в закрытый интервал единиц:

зажим (х) = макс (0, мин (х, 1))

И базовая функция для интерполяции:

N (t) = зажим (1,5 - | 2t |)

Тогда цвет становится:

r = N (t - 0,5), g = N (t), b = N (t + 0,5)

Построение этого от -1 до 1 дает:

График значений RGB от -1 до 1

Что совпадает с приведенным в этом ответе. Используя эффективную реализацию зажима:

double clamp(double v)
{
  const double t = v < 0 ? 0 : v;
  return t > 1.0 ? 1.0 : t;
}

и гарантируя, что ваше значение t находится в [-1, 1], тогда реактивный цвет просто:

double red   = clamp(1.5 - std::abs(2.0 * t - 1.0));
double green = clamp(1.5 - std::abs(2.0 * t));
double blue  = clamp(1.5 - std::abs(2.0 * t + 1.0));

Как показано в приведенной выше ссылке на реализацию clampКомпилятор может оптимизировать ветки. Компилятор также может использовать встроенные функции, чтобы установить бит знака для std::abs устранение другой ветки.

"Горячие к холодной"

Аналогичная обработка может быть использована для "цветного картирования". В этом случае основные и цветовые функции:

N (t) = зажим (2 - | 2t |)

r (t) = N (t-1), g (t) = N (t), b (t) = N (t + 1)

И участок от горячей к холодной для [-1, 1]:

Горячий на холодный участок

Программа OpenGL Shader

Исключение явных ветвей делает этот подход эффективным для реализации в качестве шейдерной программы OpenGL. GLSL предоставляет встроенные функции для обоих abs а также clamp которые работают на трехмерных векторах. Векторизация вычисления цвета и предпочтение встроенных функций над ветвлением может обеспечить значительный прирост производительности. Ниже приведена реализация в GLSL, которая возвращает цвет RGB jet в виде vec3, Обратите внимание, что базисная функция была изменена так, что t должно лежать в [0,1], а не в диапазоне, используемом в других примерах.

vec3 jet(float t)
{
  return clamp(vec3(1.5) - abs(4.0 * vec3(t) + vec3(-3, -2, -1)), vec3(0), vec3(1));
}

Я не совсем уверен, почему есть так много сложных ответов на это простое уравнение. Основываясь на диаграмме цветовой карты "Горячая-холодная" MatLab JET и графике, опубликованном выше в комментарии Амро (спасибо), логика очень проста для вычисления значений RGB с использованием высокоскоростной / базовой математики.

Я использую следующую функцию для рендеринга в реальном времени нормализованных данных для отображения спектрограмм, и она невероятно быстра и эффективна без сложной математики вне умножения и деления с двойной точностью, упрощенного с помощью троичной логической цепочки. Этот код написан на C#, но его очень легко перенести практически на любой другой язык (извините программисты PHP, вам не повезло благодаря ненормальному порядку троичной цепочки).

public byte[] GetMatlabRgb(double ordinal)
{
    byte[] triplet = new byte[3];
    triplet[0] = (ordinal < 0.0)  ? (byte)0 : (ordinal >= 0.5)  ? (byte)255 : (byte)(ordinal / 0.5 * 255);
    triplet[1] = (ordinal < -0.5) ? (byte)((ordinal + 1) / 0.5 * 255) : (ordinal > 0.5) ? (byte)(255 - ((ordinal - 0.5) / 0.5 * 255)) : (byte)255;
    triplet[2] = (ordinal > 0.0)  ? (byte)0 : (ordinal <= -0.5) ? (byte)255 : (byte)(ordinal * -1.0 / 0.5 * 255);
    return triplet;
}

Функция принимает порядковый диапазон от -1,0 до 1,0 в соответствии со спецификацией цвета JET, хотя эта функция не проверяет работоспособность, если вы находитесь вне этого диапазона (я делаю это перед моим вызовом здесь).

Поэтому убедитесь, что вы выполняете проверку работоспособности / границ до вызова этой функции или просто добавляете собственное ограничение, чтобы ограничить значение, когда вы реализуете его самостоятельно.

Эта реализация не учитывает светимость, поэтому ее нельзя считать пуристической реализацией, но она достаточно хорошо помогает вам в этом и намного быстрее.

Похоже, у вас есть значения оттенка системы HSL, а насыщенность и яркость неявны. Ищите конвертацию HSL в RGB в Интернете, и вы найдете множество объяснений, код и т. Д. (Вот одна ссылка)

Однако в вашем конкретном случае давайте предположим, что по умолчанию все цветовые насыщенности установлены на 1, а яркость на 0,5. Вот формула, которую вы можете использовать для получения значений RGB:

Вообразите для каждого пикселя, у вас есть h значение, которое вы читаете из ваших данных.

hue = (h+1.0)/2;  // This is to make it in range [0, 1]
temp[3] = {hue+1.0/3, hue, hue-1.0/3};
if (temp[0] > 1.0)
    temp[0] -= 1.0;
if (temp[2] < 0.0)
    temp[2] += 1.0;

float RGB[3];
for (int i = 0; i < 3; ++i)
{
    if (temp[i]*6.0 < 1.0)
        RGB[i] = 6.0f*temp[i];
    else if (temp[i]*2.0 < 1.0)
        RGB[i] = 1;
    else if (temp[i]*3.0 < 2.0)
        RGB[i] = ((2.0/3.0)-temp[i])*6.0f;
    else
        RGB[i] = 0;
}

И там у вас есть значения RGB в RGB все в диапазоне [0, 1]. Обратите внимание, что исходное преобразование является более сложным, я упростил его на основе значений насыщенности =1 и освещенности =0,5

Почему эта формула? Смотрите эту запись в Википедии

Код Java(обработка), который будет генерировать Jet и HotAndCold RGB. Я создал этот код, следуя схеме распределения RGB в сообщении Amro выше.

color JetColor(float v,float vmin,float vmax){
       float r=0, g=0, b=0;
       float x = (v-vmin)/(vmax-vmin);
       r = 255*constrain(-4*abs(x-0.75) + 1.5,0,1);
       g = 255*constrain(-4*abs(x-0.50) + 1.5,0,1);
       b = 255*constrain(-4*abs(x-0.25) + 1.5,0,1);
       return color(r,g,b);
    }

color HeatColor(float v,float vmin,float vmax){
       float r=0, g=0, b=0;
       float x = (v-vmin)/(vmax-vmin);
       r = 255*constrain(-4*abs(x-0.75) + 2,0,1);
       g = 255*constrain(-4*abs(x-0.50) + 2,0,1);
       b = 255*constrain(-4*abs(x) + 2,0,1);
       return color(r,g,b);
    }
    //Values are calculated on trapezoid cutoff points in format y=constrain(a(x-t)+b,0,1)
    //Where a=((delta)y/(delta)x), t=x-offset value to symetric middle of trapezoid, and b=y-a(x-t) for the last peak point (x,y)

Это, вероятно, не совсем то же самое, но это может быть достаточно близко для ваших нужд:

if (-0.75 > value) {
    blue = 1.75 + value;
} else if (0.25 > value) {
    blue = 0.25 - value;
} else {
    blue = 0;
}

if ( -0.5 > value) {
    green = 0;
} else if (0.5 > value) {
    green = 1 - 2*abs(value);
} else {
    green = 0;
}

if ( -0.25 > value) {
    red = 0;
} else if (0.75 > value) {
    red = 0.25 + value;
} else {
    red = 1.75 - value;
}
Другие вопросы по тегам