Сводный список записей блога

--->>>> Сводный список записей блога <<<<---

07 марта 2021

Цветовое пространство HSV, его осмысление и как с этим знанием дальше жить...


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

А продвинутый радиолюбитель, который слышал слово "микроконтроллер", знает, что в микроконтроллерах есть специальные выходы, на которых можно получить сигналы с широтно-импульсной модуляцией (ШИМ или по-заморскому PWM). И что если к этим выходам подключить такой светодиод, то призвав в помощь опытного программиста, можно заставить светодиод светиться разными цветами без участия человека. А если еще этот радиолюбитель и сам немножко программист - то тут и все карты в руки.

Но вот беда. Пока нужно просто помигать-посветить разными цветами - цветовая модель RGB остается удобной. А когда таких светодиодов много, да когда наступают более интересные задачи независимого изменения цвета и яркости, то в ряде случаев в мозгах программиста-радиолюбителя может наступить "деление на ноль". Ибо вопрос - как учесть сразу несколько характеристик цвета и света и всё это в цветовой модели RGB - зачастую бывает сложный.

И тут на помощь приходит цветовая модель HSV (Hue - цветовой тон, Saturation - насыщенность, Value - уровень или же яркость). Модель придумал в середине 1970-х некий Элви Рэй Смит, один из со-основателей Pixar. Я не знаю, для чего это было нужно Элви Рэю, но нам эта модель позволит независимо выбирать цвет свечения светодиода, яркость свечения светодиода и насыщенность цвета. Прям как крутилки на старых тёплых ламповых цветных телевизорах.

Ниже - информация, которая может помочь осмыслению цветовой модели HSV и применением ее в любимом микроконтроллере.

Цветовой тон (Hue)

Цветовой тон обычно изображают в виде круга. Круг - 360°, направления, кратные 60°, соответствуют чистым цветам - красному, желтому, зеленому, голубому, синему, фиолетовому.

Или в виде цветовой полосы, где начало и конец совпадают.

Как то вот так.

Иногда диапазон цветового тона принимают как 0..100 или вообще в виде числа с плавающей запятой от 0 до 1. Но на самом деле это не важно, это просто способ выбора угла в цветовом круге.

Диапазоны насыщенности и яркости не привязаны к углу, это просто диапазоны от минимума до максимума. Их так же можно принять как число с плавающей запятой от 0 до 1, или диапазон от 0 до 100. В применении к вычислительной технике и, в частности, в микроконтроллерах диапазоны удобно выбирать кратные 8-битному числу - 0..255.

Если с насыщенностью и яркостью проблем при этом не возникает - 256 градаций достаточно практически всегда, то вот с цветовым кругом сложнее.
В случае использования 8-битных контроллеров значение 360 в однобайтовую переменную не поместится. Тут можно пойти 2 путями. Первый - использовать для выбора цвета двухбайтовую переменную. Но, возможно, не всегда удобно работать с таким диапазоном цвета в основной программе. Тогда можно пойти по второму пути - сжать диапазон выбора цвета до 256 значений. 

Тогда чистые цвета будут соответствовать следующим значениям:
0 - красный
43 - желтый
85 - зеленый
128 - голубой
170 - синий
213 - фиолетовый.

Насыщенность (Saturation)

Приходят Изя с Абрамом к раввину.
-Ребе - белый цвет - это цвет?
-Да, Изя, цвет.
-Ребе, а черный цвет - это цвет?
-Да, Изя, черный это таки тоже цвет.
-Видишь, Абрам, я тебе продал таки цветной телевизор!

Чем больше значение насыщенности, тем выше уровень цветовой составляющей. Как крутилка "Цветность" в старом телеке. Если значение насыщенности убрать до нуля - останется только значение яркости свечения и весь мир будет в черно-белых тонах. Но телевизор останется цветным. 

Яркость (Value)

Яркость, собственно, и определяет яркость свечения. Чем больше яркость, тем быстрее "сядет кинескоп".

Для понимания, какая крутилка на что влияет, можно посмотреть заглавную картинку в начале поста или ниже я сделал несколько картинок, заполнив обычный BITMAP преобразованными значениями HSV.



Диапазоны Hue и Saturation при максимальном значении Value.

Тут видно, что Hue меняет цвет. Saturation, когда имеет максимальное значение - на цвет влияния не оказывает. Но при уменьшении значения цвет вырождается в значение яркости. При максимальном значении Value это будет белый.

Если при нулевой насыщенности покрутить яркость (Value) - получим градации серого.


Диапазон Value при Saturation = 0

Если не менять насыщенность, а крутить яркость и цвет - получим вот такую картинку:


Противоположный случай - Saturation = 255


Процедуры преобразования HSV в RGB.

Ниже - пара процедурок (по сути, одна для двух вариантов Hue). Процедуры не мои, взяты с просторов интернету.
Процедура работает просто - в параметрах процедуры задаются значения Hue, Saturation и Value, а так же передается указатель на структуру RGB, в которую будет сохранено RGB-значение.

Структура:

typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} sRGBtype;

Процедура преобразования HSV для диапазона Hue от 0 до 255:

void HSV2RGB(uint8_t h, uint8_t s, uint8_t v, sRGBtype* RGB) {
uint8_t region = (uint16_t)h * 24 / (17*60);
region %= 6;
uint8_t vs256 = ((uint16_t)v * s + s) / 256;
uint8_t vMin = v - vs256;
uint8_t a = (uint16_t)vs256 * (((uint16_t)h*24 / 17) % 60 ) / 60;
uint8_t vInc = vMin + a; 
uint8_t vDec = v - a; 
switch (region) {
case 0: RGB->r = v; RGB->g = vInc; RGB->b = vMin; break;
case 1: RGB->r = vDec; RGB->g = v; RGB->b = vMin; break;
case 2: RGB->r = vMin; RGB->g = v; RGB->b = vInc; break;
case 3: RGB->r = vMin; RGB->g = vDec; RGB->b = v; break;
case 4: RGB->r = vInc; RGB->g = vMin; RGB->b = v; break;
case 5: RGB->r = v; RGB->g = vMin; RGB->b = vDec; break;
} // switch
}



Процедура преобразования HSV для диапазона Hue от 0 до 360. (360 трактуется как 0)


void HSV2RGB(uint16_t h, uint8_t s, uint8_t v, sRGBtype* RGB) {
uint8_t region = h / 60;
region %= 6;
uint8_t vs256 = ((uint16_t)v * s + s) / 256;
uint8_t vMin = v - vs256;
uint8_t a = (uint16_t)vs256 * ( h % 60 ) / 60;
uint8_t vInc = vMin + a; 
uint8_t vDec = v - a; 
switch (region) {
case 0: RGB->r = v; RGB->g = vInc; RGB->b = vMin; break;
case 1: RGB->r = vDec; RGB->g = v; RGB->b = vMin; break;
case 2: RGB->r = vMin; RGB->g = v; RGB->b = vInc; break;
case 3: RGB->r = vMin; RGB->g = vDec; RGB->b = v; break;
case 4: RGB->r = vInc; RGB->g = vMin; RGB->b = v; break;
case 5: RGB->r = v; RGB->g = vMin; RGB->b = vDec; break;
} // switch
}



Вариант для диапазона Hue от 0 до 360

Дополнительные размышления про яркость.

При управлении яркостью светодиодом через ШИМ коэффициент заполнения модуляции соответствует  мощности свечения диода. Но линейное изменение яркости свечения светодиода не воспринимается человеческим глазом, как линейное. Для "линеаризации" восприятия яркости необходимо вводить нелинейную коррекцию, обратную восприятию глаза. Как вариант, квадратичную.


Квадратичная коррекция (для диапазона от 0 до 255)


Для однобайтового коэффициента ШИМ это может быть вот такая формула (показана на графике выше):
v' = v2 / 255 

Но для простых 8-битных МК, у которых нет аппаратного деления, операция деления на 255 "неудобная".  В таком случае можно применить следующую формулу:

v' = v * ( v+1 ) / 256

Операция деления на 256 - это просто отбрасывание младшего байта, что несколько быстрее программного деления.




3 комментария:

  1. О! Спасибо, очень доходчиво.

    ОтветитьУдалить
    Ответы
    1. Кстати, я тут подумал. Если сектор перехода от одного чистого цвета к другому (который 1/6 круга) принять не за 60°, а за 64 "условных единицы", то весь круг составит 64*6 = 384 единицы. И тогда во второй версии процедуры нужно деление на 60 заменить на деление на 64. Теоретически для больших массивов это ещё ускорит математику. Ибо тогда
      x / 64 == x >> 6,
      а
      x % 64 == x & 0x3F
      А если диапазон hue взять как 6*256 = 1536, то деление на 256 будет быстрым даже на AVR, где нет аппаратных сдвигов на N бит. Только посмотреть, что бы там математика не вылезла за пределы 16 бит.

      Удалить

======= !!! ВНИМАНИЕ !!! ======================================================================
Гугл умный и боится спама. Поэтому иногда ваши комментарии Гугл отправляет мне на премодерацию. Отправлять или нет - решаю не я, а алгоритмы Гугла. Если ваш комментарий не появился сразу, значит я получу уведомление и опубликую ваш комментарий через некоторое время. Я стараюсь это делать достаточно оперативно.