【问题标题】:Generate RGB color set with highest diversity生成具有最高多样性的 RGB 颜色集
【发布时间】:2019-10-13 18:36:23
【问题描述】:

我正在尝试创建一种算法,该算法将输出一组不同的 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) [紫色]

下一个颜色应该在新间隔之间。基本上,我的想法是像这样遍历整个色谱系统区间:

一组 13 种颜色应包括 1 到 7 之间的颜色,无限延续该模式。

我目前正在努力将此模式应用于 RGB 值的算法,因为它对我来说似乎并不简单。我很感谢任何可以为我指出解决方案的提示。

【问题讨论】:

  • 需要考虑的一些想法... 1) 黑色和白色是颜色立方体上相距最远的两种颜色,但您没有它们 2) 相距最远取决于观察者,红绿色- 失明非常普遍 3) 您可能希望查看 HSL 色彩空间并保持饱和度和值最大化,然后将 360 色相圆圈拆分为您想要的任意颜色 - 这或多或少是您直觉上已经在做的事情。跨度>
  • HSL 在此处描述 en.wikipedia.org/wiki/HSL_and_HSV 并且您在此图的下半部分为您的前 3 个值有效地选择了 0/120/240 度,然后为您的第二个 3 添加了 60/180/300此图下部的值en.wikipedia.org/wiki/HSL_and_HSV#/media/…
  • @MarkSetchell 感谢您的意见!我也在考虑改用 HSV,但我仍然想知道如何在 RGB 空间中解决这个问题。另外,您的第一条评论是正确的; 最高多样性可能有点模棱两可。
  • 它的目的是什么?您的选择不利于对比,因此不利于向读者大脑传达快速信息。所以我不会推荐其他 cmets 中的解决方案(例如仅更改色调),而是用于地图。
  • 看看这个:Multi-Band Image raster to RGB 只需将可见光谱分成N 波长/颜色,这样你就有了 N 种不同的颜色(也可以用作多波段渲染的原色因为它们总和为白色,并且通过它们的线性组合,您可以获得任何颜色)。

标签: algorithm colors rgb


【解决方案1】:

Wikipedia article on color difference 值得一读,其中链接的 CompuPhase 的article on a “low-cost approximation” 也是如此。我将基于后者的尝试。

您没有指定语言,所以我将用 优化的 Python 编写它(参考文章中已经存在的整数优化除外),以便它易于翻译翻译成其他语言。

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 颜色立方体的中心,然后在颜色立方体中移动,以便希望最大化颜色之间的最小距离。

为了节省 CPU 时间,使用距离的平方而不是距离本身,这需要计算平方根。

颜色一次移动一种,在 3 个方向的每个方向上最多移动 1 个颜色,移动到与其他颜色的最小距离最大的相邻颜色之一。通过这样做,全局最小距离(大约)最大化。

需要“全局移动”阶段来克服没有颜色移动的情况,但是强制所有颜色移动到一个不比当前位置差多少的位置会导致整体找到一个更好的配置随后的常规动作。这可以用3种颜色和没有全局移动最好看,将加权平方距离修改为简单的rd*rd+gd*gd+bd*bd:配置变为

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

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

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

该算法仅产生几种可能的解决方案之一。不幸的是,由于使用的度量不是欧几里得,不可能简单地翻转 8 种可能组合中的 3 维(即,替换 r255-r 和/或相同的 g 和/或 b ) 以获得等效的解决方案。最好按照尝试颜色移动步骤的顺序引入随机性,并改变随机种子。

我没有对显示器的gamma进行校正,因为它的目的正是改变亮度的间距,以补偿眼睛在高亮度和低亮度下的不同灵敏度。当然,屏幕 gamma 曲线会偏离理想值,对 gamma 进行(取决于系统!)修改会产生更好的结果,但标准 gamma 是一个很好的起点。

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

请注意,前 8 种颜色(底行和上行的前 3 种颜色)靠近 RGB 立方体的角(它们不是 角,因为非-欧几里得度量)。

【讨论】:

    【解决方案2】:

    首先我问一下,你要不要留在sRGB中,并遍历每个RGB组合?

    或者(这是我的假设)你真的想要彼此“最远”的颜色吗?由于您使用了术语“distinct”,因此我将介绍如何找到颜色差异。

    塑造你的看法

    sRGB 是指您的显示/输出的色彩空间。虽然伽马曲线在感知上是“有点”均匀的,但整体 sRGB 色彩空间却不是,它旨在模拟显示器而不是人类感知。

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

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

    如果您想使用极坐标(即色相角),那么您可以通过 CIELAB 一步,进入 CIELCh。


    怎么做

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

    简化步骤:

    1. 通过从三个颜色通道中的每一个中移除伽马曲线来线性化 sRGB。
    2. 将线性化值转换为 CIE XYZ(使用 D65,无适配)
    3. 将 XYZ 转换为 L* a* b*
    4. 寻找对立面:
      一种。现在通过绘制一条穿过 0 的线来找到“相反”的颜色,使这条线与零两侧的距离相等。或
      湾。 ALT:再从 LAB 转换到 CIELCh,然后将色调旋转 180 度找到相反的结果。然后转换回 LAB。
    5. 将 LAB 转换为 XYZ。

    6. 将 XYZ 转换为 sRGB。

    7. 将 sRGB 伽马曲线添加回每个通道。

    继续使用 sRGB?

    如果您不太关心感知均匀性,那么您可以只使用 sRGB,尽管结果会不太准确。在这种情况下,您需要做的就是获取每个通道相对于 255 的差异(即反转每个通道):

    不同点有什么区别?

    以下是上面讨论的两种方法的一些比较:

    对于起始颜色#0C5490 sRGB差分法:

    相同的起始颜色,但使用 CIELAB L* C* h*(只是将色调旋转 180 度,不调整 L*)。

    起始色#DFE217,sRGB差异法:

    在 CIELAB LCh 中,只需将色调旋转 180:

    再次在 LCh 中,但这次也将 L* 调整为 (100 - L*firstcolor)

    现在您会注意到这些色调角度发生了很大变化——事实上,虽然 LAB “有点均匀”,但它在蓝色区域非常松散。

    看看数字:

    它们在色调、色度、a、b 方面似乎大不相同……但它们创建相同的 HEX 颜色值!所以是的,即使是 CIELAB 也有不准确的地方(尤其是蓝色)。

    如果您想要更高的准确性,请尝试 CIECAM02

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 2020-10-04
      • 2014-01-22
      • 2023-03-16
      • 2014-05-26
      相关资源
      最近更新 更多