生成具有最高多样性的RGB颜色集

时间:2019-05-28 09:11:43

标签: algorithm colors rgb

我正在尝试创建一种算法,该算法将输出一组不同的RGB颜色值,这些值应尽可能不同。 例如:

以下是三种颜色:

  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 个答案:

答案 0 :(得分:1)

首先让我问一下,您是否要保留在sRGB中并浏览每种RGB组合?

或者(这是我的假设),您实际上是否想要彼此“最远”的颜色?由于您使用了“ 不同”一词,因此我将介绍查找颜色差异。

为您的感知建模

sRGB是引用您的显示/输出的色彩空间。而且,尽管伽玛曲线在感知上是“排序”的,但整体sRGB色彩空间却不是,它的目的是为显示建模而不是人类感知。

要确定各种颜色之间的“最大距离”,您需要一个感知模型,或者使用感知均匀的色彩空间,或者使用颜色外观模型(CAM)。

由于只需要sRGB值,因此使用统一的色彩空间就足够了,例如CIELAB或CIELUV。由于它们使用笛卡尔坐标,因此(L*a*b*)中两种颜色之间的差异只是欧几里德距离。

enter image description here

LAB diffrence equation

如果您要使用极坐标(即色相角),则可以比CIELAB跨一步,进入CIELCh。


操作方法

我建议Bruce Lindbloom's site进行相关的数学计算。

简化步骤:

  1. 通过从三个颜色通道中的每一个去除伽玛曲线来线性化sRGB。
  2. 将线性化的值转换为CIE XYZ(使用D65,无自适应)
  3. 将XYZ转换为L * a * b *
  4. 找到相反的地方:
    一种。现在,通过绘制一条穿过0的线,使该线到零的两边的距离相等,来找到“相反”的颜色。或
    b。 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 * firstcolor

enter image description here

现在,您会注意到这些颜色的色相角发生了很大变化-事实是,虽然LAB是“某种程度的均匀”,但在蓝色区域中却非常蠕动。

看看数字:

enter image description here

它们在色相,色度,a,b方面似乎有很大的不同...但是它们创建相同的十六进制颜色值!因此,是的,即使CIELAB也存在误差(尤其是蓝色)。

如果您想提高准确性,请尝试CIECAM02

答案 1 :(得分:0)

这是我的尝试。您没有指定语言,所以我将用Python编写(未经优化,以便轻松翻译成其他语言)

n_colors = 25
rw, gw, bw = 2, 4, 1
n_global_moves = 32

class Color:
    max_weighted_square_distance = (rw * rw + gw * gw + bw * bw) * 255 * 255

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

    def weighted_square_distance(self, other):
        rd = rw * (self.r - other.r)
        gd = gw * (self.g - other.g)
        bd = bw * (self.b - other.b)
        return rd*rd + gd*gd + bd*bd

    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:
            min_wsd = -1
        else:
            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  min_wsd < new_min_wsd:
                    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"rw, gw, bw: {rw}, {gw}, {bw}")
print(f"n_global_moves: {n_global_moves}")
print(colors)

首先将颜色全部设置为灰色,即放在颜色立方体的中央,然后以希望最大程度地增大颜色之间的最小距离的方式在颜色立方体中移动。

为节省CPU时间,使用距离的平方代替距离本身,因为距离本身需要计算平方根。

为补偿我们视锥细胞的不同灵敏度,在计算欧几里得距离时,我们在3个颜色方向上使用了不同的权重(rwgwbw)。这会使颜色立方体有效地变成颜色长方体。

一次将一种颜色在三个方向中的每个方向上最多移动1个,将其移动到相邻颜色之一,以最大程度地减少与其他颜色的最小距离。这样,全局最小距离被(大约)最大化了。

需要“全局移动”阶段,以克服没有颜色移动的情况,但是强迫所有颜色移动到不比当前颜色差很多的位置时,会导致整体找到更好的配置。随后的常规动作。这最好用3种颜色显示且没有全局移动,将rwgwbw都设置为1:配置变为

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

同时,通过添加2条全局移动,配置将成为预期的

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

该算法产生8个解之一。通过将c替换为255-c(其中crgb,并以所有组合代替$response = array(); $a = 0; $contacts[$a][0] = $c['contact']; $contacts[$a][1] = $c['name']; $contacts[$a][2] = $c['phone']; $contacts[$a][3] = $c['phone2']; $contacts[$a][4] = $c['email']; $contacts[$a][5] = $c['active']; $a++; $response['contacts'] = $contacts; echo json_encode($response); ,产生了另外7个。

该算法对颜色“距离”进行了简化假设。我不知道颜色长方体的扭曲是否会导致2种​​颜色之间的感知距离等于欧几里得距离。如果存在,则在此扭曲内移动颜色会产生更好的结果。

我没有校正显示器的gamma,因为它的目的恰恰是为了改变眼睛的亮度,以补偿眼睛在高和低亮度下的不同灵敏度。当然,屏幕伽玛可能无法精确地做到这一点,对伽玛进行(取决于屏幕!)修改会产生更好的结果,但是标准伽玛是一个很好的起点。

这是25种颜色的算法输出:

enter image description here


警告

发布此答案后,我发现了Wikipedia article on color difference。我使用的权重可能适合于组件对亮度的贡献,但不适用于色差的感知。此外,还有一个更好的公式(无需完全更改色彩空间),所以我将不得不修复这两个方面。