Создание набора цветов RGB с максимальным разнообразием - PullRequest
2 голосов
/ 28 мая 2019

Я пытаюсь создать алгоритм, который будет выводить набор различных значений цвета RGB, которые должны быть как можно более четкими. Например:

следующий набор из 3 цветов:

  1. (255, 0, 0) [ Красный ]
  2. (0, 255, 0) [ Зеленый ]
  3. (0, 0, 255) [ Синий ]

следующие 3 цвета будут:

  1. (255, 255, 0) [ Желтый ]
  2. (0, 255, 255) [ Голубой ]
  3. (255, 0, 255) [ Фиолетовый ]

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

enter image description here

Набор из 13 цветов должен включать цвета в диапазоне от 1 до 7, продолжайте этот шаблон бесконечно.

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

Ответы [ 2 ]

2 голосов
/ 02 июня 2019

Стоит прочесть статью в Википедии о разнице в цвете , как и статью о «недорогом приближении» от CompuPhase, на которую она ссылается.Я буду основывать свою попытку на последнем.

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

n_colors = 25
n_global_moves = 32

class Color:
    max_weighted_square_distance = (((512 + 127) * 65025) >> 8) + 4 * 65025 + (((767 - 127) * 65025) >> 8)

    def __init__(self, r, g, b):
        self.r, self.g, self.b = r, g, b

    def weighted_square_distance(self, other):
        rm = (self.r + other.r) // 2  # integer division
        dr =  self.r - other.r
        dg =  self.g - other.g
        db =  self.b - other.b
        return (((512 + rm) * dr*dr) >> 8) + 4 * dg*dg + (((767 - rm) * db*db) >> 8)

    def min_weighted_square_distance(self, index, others):
        min_wsd = self.max_weighted_square_distance
        for i in range(0, len(others)):
            if i != index:
                wsd = self.weighted_square_distance(others[i])
                if  min_wsd > wsd:
                    min_wsd = wsd
        return min_wsd

    def is_valid(self):
        return 0 <= self.r <= 255 and 0 <= self.g <= 255 and 0 <= self.b <= 255

    def add(self, other):
        return Color(self.r + other.r, self.g + other.g, self.b + other.b)

    def __repr__(self):
        return f"({self.r}, {self.g}, {self.b})"


colors = [Color(127, 127, 127) for i in range(0, n_colors)]

steps = [Color(dr, dg, db) for dr in [-1, 0, 1]
                           for dg in [-1, 0, 1]
                           for db in [-1, 0, 1] if dr or dg or db]  # i.e., except 0,0,0
moved = True
global_move_phase = False
global_move_count = 0
while moved or global_move_phase:
    moved = False
    for index in range(0, len(colors)):
        color = colors[index]
        if global_move_phase:
            best_min_wsd = -1
        else:
            best_min_wsd = color.min_weighted_square_distance(index, colors)
        for step in steps:
            new_color = color.add(step)
            if new_color.is_valid():
                new_min_wsd = new_color.min_weighted_square_distance(index, colors)
                if  best_min_wsd < new_min_wsd:
                    best_min_wsd = new_min_wsd
                    colors[index] = new_color
                    moved = True
    if not moved:
        if  global_move_count < n_global_moves:
            global_move_count += 1
            global_move_phase = True
    else:
        global_move_phase = False

print(f"n_colors: {n_colors}")
print(f"n_global_moves: {n_global_moves}")
print(colors)

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

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

Цвета перемещаются по одному, максимум на 1 в каждом из 3 направлений, к одному из смежных цветов, который максимизирует минимальное расстояние от других цветов.При этом глобальное минимальное расстояние (приблизительно) максимизируется.

Фазы «глобального перемещения» необходимы для преодоления ситуаций, в которых не будет двигаться ни один цвет, но для того, чтобы все цвета переместились в положение, которое являетсяне намного хуже, чем их текущий, заставляет целое находить лучшую конфигурацию с последующими регулярными движениями.Лучше всего это можно увидеть с 3 цветами и без глобальных перемещений, изменяя взвешенное квадратное расстояние, чтобы оно было просто rd*rd+gd*gd+bd*bd: конфигурация становится

[(2, 0, 0), (0, 253, 255), (255, 255, 2)]

, в то время как, добавляя 2 глобальных движения, конфигурация становится ожидаемойone

[(0, 0, 0), (0, 255, 255), (255, 255, 0)]

Алгоритм дает только одно из нескольких возможных решений.К сожалению, поскольку используемая метрика не является евклидовой, невозможно просто перевернуть 3 измерения в 8 возможных комбинациях (то есть заменить r255-r и / или то же самое для g и / или b) чтобы получить эквивалентные решения.Вероятно, лучше ввести случайность в том порядке, в котором выполняются шаги перемещения цвета, и варьировать случайное начальное число.

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

Это результат выводаалгоритм для 25 цветов:

output for 25 colors as 5 by 5 color patches

Обратите внимание, что первые 8 цветов (нижний ряд и первые 3 цвета ряда выше) близки куглы куба RGB (они не при углах из-за неевклидовой метрики).

1 голос
/ 13 июня 2019

Сначала позвольте мне спросить, хотите ли вы остаться в sRGB и просмотреть каждую комбинацию RGB?

ИЛИ (и это мое предположение) вы действительно хотите, чтобы цвета были "наиболее далекими" друг от друга??Поскольку вы использовали термин " отличный ", я собираюсь охватить обнаружение цветовых различий.

Моделирование вашего восприятия

sRGB - это цветовое пространство, котороеотносится к вашему отображению / выводу.И хотя гамма-кривая «сорта» воспринимаема однородно, общее цветовое пространство sRGB не является таким, оно предназначено для большей модели дисплея, чем для человеческого восприятия.

Чтобы определить «максимальное расстояние» между цветами с точки зрения восприятия, вынужна модель восприятия, либо с использованием однородного по восприятию цветового пространства, либо с использованием модели внешнего вида цвета (CAM).

Так как вы просто хотите получить значения sRGB в качестве результата, тогда, вероятно, достаточно использовать единое цветовое пространство, напримеркак CIELAB или CIELUV.Поскольку они используют декартовы координаты, разница между двумя цветами в (L*a*b*) является просто евклидовым расстоянием.

enter image description here

LAB diffrence equation

Если вы хотите работать с полярными координатами (т. Е. Углом оттенка), тогдаВы можете пройти один шаг за CIELAB в CIELCh.


Как это сделать

Я предлагаю Сайт Брюса Линдблюма для соответствующей математики.

Упрощенные шаги:

  1. Линеаризировать sRGB путем удаления гамма-кривой из каждого из трех цветовых каналов.
  2. Преобразовать линеаризованные значения в CIE XYZ (используйте D65, без адаптации)
  3. Конвертировать XYZ в L * a * b *
  4. Найти противоположность:
    a.Теперь найдите «противоположный» цвет, проведя линию через 0, сделав линию равной по расстоянию от обеих сторон нуля.ИЛИ
    б.ALT: Сделайте еще одно преобразование из LAB в CIELCh, затем найдите противоположность, повернув оттенок на 180 градусов.Затем преобразуйте обратно в LAB.
  5. Преобразование LAB в XYZ.

  6. Преобразование XYZ в sRGB.

  7. Добавитьгамма-кривая sRGB для возврата к каждому каналу.

Пребывание в sRGB?

Если вас меньше беспокоит однородность восприятия, тогда вы можете просто остаться в sRGB, хотярезультаты будут менее точными.В этом случае все, что вам нужно сделать, это взять разность каждого канала относительно 255 (т.е. инвертировать каждый канал):

enter image description here

В чем разницаРазличия?

Вот некоторые сравнения двух методов, обсуждаемых выше:

Для начального цвета # 0C5490 sRGB Различный метод:

enter image description here

Тот же начальный цвет, но с использованием CIELAB L * C * h * (и просто поворот оттенка на 180 градусов, без регулировки на L *).

enter image description here

Начальный цвет # DFE217, метод различия sRGB:

enter image description here

Здесь, в CIELAB LCh, только вращающийся оттенок 180:

enter image description here

И снова в LCh, но на этот раз также корректируем L * как (100 - L * первый цвет )

enter image description here

Теперь вы заметите большое изменение угла оттенка на них - правда в том, что, хотя LAB "несколько однороден", он довольно короток вплощадь синего цвета.

Взгляните на цифры:

enter image description here

Они кажутся существенно разными для оттенка, насыщенности цвета, a, b ... покаони создают одинаковое значение цвета HEX!Так что да, даже у CIELAB есть неточности (особенно синие).

Если вы хотите большей точности, попробуйте CIECAM02

...