Оттенки серого до красно-зелено-синего (MATLAB Jet) цветовая гамма - PullRequest
33 голосов
/ 10 октября 2011

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

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

Спасибо, Адам

Ответы [ 6 ]

70 голосов
/ 18 октября 2011

Рассмотрим следующую функцию (написанную Полом Бурком - поиск 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)

jet hot_to_cold

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

22 голосов
/ 10 октября 2011

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

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% идентичен изображению, которое вы связали, но оно должно выглядеть очень похоже.

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

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 );
}
8 голосов
/ 08 октября 2017

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

clamp(x)=max(0, min(x, 1))

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

N(t) = clamp(1.5 - |2t|)

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

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

При построении этого значения от -1 до 1 получается:

Plot of RGB values from -1 to 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) = clamp(2 - |2t|)

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

И график «горячая-холодная» для [-1, 1]:

Hot-to-cold plot

Программа шейдеров OpenGL

Устранение явных ветвей делает этот подход эффективным для реализации в качестве программы шейдеров 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));
}
3 голосов
/ 14 июля 2017

Я не совсем уверен, почему есть так много сложных ответов на это простое уравнение. Основываясь на диаграмме цветовой карты «Горячая-холодная» 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, хотя эта функция не проверяет работоспособность, если вы находитесь вне этого диапазона (я делаю это до моего вызова здесь).

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

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

1 голос
/ 10 октября 2011

Похоже, у вас есть значения оттенка системы 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

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

0 голосов
/ 10 октября 2011

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

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...