Преобразование цвета RGB от 0-1 до 0-255 - распределение вероятности интенсивности

Мне интересно, если я (и все в Интернете и в компаниях, в которых я работал) неправильно преобразовывал цвета. Очевидно, что когда я хочу преобразовать цвет из диапазона 0–255 в диапазон 0–1, я просто делю значения на 255, верно? А когда я хочу пойти другим путем, я умножаю.

Но так ли это? Представьте, что мои цвета являются результатом какой-то математики (простой, как билинейная интерполяция, или настолько сложной, насколько вы могли бы пожелать (или осмелиться). Тогда это может быть не лучший способ. С определенной точки зрения, при преобразовании из числа с плавающей запятой 0–1 до целого числа 0–255, я хочу разбить интервал на 256 фрагментов одного размера, а затем выполнить «поиск». Если я просто умножу на 255 (значения rgb в масштабе от 0 до 1) и раунд, "куски" или "бункеры" для 0 и 255 будет в два раза меньше остальных, т. е. вероятностное распределение интенсивности цвета будет неверным.

Я не думаю, что это часто проблема на практике. Например. в high-end VFX всегда используются цвета с плавающей запятой (0-1 плюс суперчерные/супербелые) и преобразуются в 0-255 только в качестве самого последнего шага. Хотя меня это все еще беспокоит. Какой правильный ответ?


person BIOStheZerg    schedule 20.02.2018    source источник


Ответы (2)


Это не обязательно так, давайте визуализируем это, используя 2-битное квантование (4 уровня):

если мы разделим на 4 (эквивалент 256 в 8 битах)

0.00 to 0.24 = Black  
0.25 to 0.49 = Dark Gray  
0.50 to 0.74 = Light Gray  
0.75 to 0.99 = White  

Вы можете заметить, что бины разделены одинаково (размер 0,25), если умножить на 256, все будет правильно. За исключением 1.0, которому соответствует 256. Мы можем исправить это, зажав значения от 0 до 255.

if (value > 255) {
    value = 255
}

Но если мы хотим упростить вычисления и избежать необходимости зажима, мы можем разделить на 3 (эквивалентно 255 в 8 битах).

0.000 to 0.333 = Black  
0.334 to 0.666 = Dark Gray  
0.667 to 0.999 = Light Gray  
1.000 to 1.333 = White  

Распространенным заблуждением является то, что последний бин (чисто белый) отображается только в единственном числе 1,0, но это не так, он распространяется до:

4/3     = 1.33333 for 2 bits
256/255 = 1.00392 for 8 bits

Для математических вычислений рассматривайте значения от 1,0 до 1,00392 как чисто белые, а от 0 до 0,00392 — как чисто черные, и все ячейки должны быть одинакового размера (0,00392).

Примечание: использовать Math.round(value * 255) неправильно, если мы используем это представление!
Это приведет к отображению неправильных цветов, и все бины будут смещены влево на половину их размера.
Вы должны использовать Math.floor(value * 255) или даже прямое приведение к int (int)(value * 255).

Однако, если вы хотите, чтобы значения представляли медиану интервалов, вы можете использовать Math.round(). Однако новые контейнеры будут:

-0.166 to 0.166 = Black  
 0.167 to 0.500 = Dark Gray  
 0.501 to 0.833 = Light Gray  
 0.834 to 1.166 = White  

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

Я рекомендую использовать Median Bins, если вы делаете правильные и точные научные расчеты (например, интерполяцию, кластеризацию и т. д.), в противном случае преобразование числа с плавающей запятой в целое число происходит намного быстрее и больше подходит для быстрых графических операций.

person Bloc97    schedule 31.07.2018
comment
Мне очень нравится первая идея, умножение на 4 (256), настил и зажатие вместо 3 (255) и округление - так просто и изящно, и урны точно одинакового размера! (как же я сам до этого не додумался?!?) Ко второй части я отношусь чуть менее восторженно, просто по той причине, что диапазон 0-1 кажется мне скорее каноничным, т.е. снаружи при преобразовании в другой стандартный диапазон. Конечно, глядя на значение 0 (8-битное), кто-нибудь может представить, что оно передается из интервала [0;0,0039], а не [-0,0020;0,0020]? - person BIOStheZerg; 06.08.2018
comment
Конечно, [0, 0,9999...] кажется каноническим, поскольку используется часто. Например. равномерные генераторы обычно находятся в диапазоне [0, 0,999...], а обратные преобразования CDF обычно также принимают входные данные [0, 0,999...], поэтому, если вы работаете с научными проблемами, это представление предпочтительнее. Но если вы выполняете быстрые графические вычисления (например, в играх), где точность не так важна, трюк [0, 1.00392] полезен, когда вы не хотите выходить за границы значений (1 * 256 переполняется при использовании беззнакового 8-битного ценности). - person Bloc97; 09.08.2018
comment
Преимущество использования медианных значений показано в этом примере: если у вас есть два значения (0 и 1,9) и вы получаете их медианное значение, это будет 0,95, что ближе к 1, чем к 0. Теперь, если 2 было белым, а 0 черным, медиана 0 и 1,9 должна быть цвета, похожего на серый (1), но с полом он черный (0). Так что закругление в данном случае предпочтительнее напольного покрытия. - person Bloc97; 09.08.2018

чтобы получить правильный результат, вы должны умножить его на 255. (999...), а затем перевести его в целое число, иначе вы получите неправильные значения в критических точках 1/4 1/2 и 3/4 и т. д.. . -- вы не можете включать вычитатель, потому что это приведет к отрицательным значениям при значениях x, близких к 0. Предыдущий ответ просто возвращает к потолку верхние значения, что в свою очередь приведет к неправильным результатам около x = 1...

Это самый простой ответ, который я нашел для правильной работы... с уважением

кредиты: https://forum.arduino.cc/index.php?topic=78108.msg590057#msg590057

person Maximilian Köhler    schedule 07.04.2019
comment
Справедливое замечание, и мне нравится его простота. Однако есть возражение: кто решает, какие значения правильны и неправильны для 1/4, 1/2 и т. д.? - person BIOStheZerg; 28.10.2020