如何自动生成N "distinct"颜色吗?

我写了下面两种方法来自动选择N种不同的颜色。它的工作原理是在RGB立方体上定义一个分段线性函数。这样做的好处是,如果你想要的话,你也可以得到一个渐进的比例,但当N变大时,颜色就会开始看起来相似。我还可以想象均匀地将RGB立方体细分为晶格,然后绘制点。有人知道其他方法吗?我排除了定义一个列表然后循环遍历它的可能性。我还应该说,我通常不关心它们是否冲突或看起来不好看,它们只需要在视觉上有区别。

public static List<Color> pick(int num) {
List<Color> colors = new ArrayList<Color>();
if (num < 2)
return colors;
float dx = 1.0f / (float) (num - 1);
for (int i = 0; i < num; i++) {
colors.add(get(i * dx));
}
return colors;
}


public static Color get(float x) {
float r = 0.0f;
float g = 0.0f;
float b = 1.0f;
if (x >= 0.0f && x < 0.2f) {
x = x / 0.2f;
r = 0.0f;
g = x;
b = 1.0f;
} else if (x >= 0.2f && x < 0.4f) {
x = (x - 0.2f) / 0.2f;
r = 0.0f;
g = 1.0f;
b = 1.0f - x;
} else if (x >= 0.4f && x < 0.6f) {
x = (x - 0.4f) / 0.2f;
r = x;
g = 1.0f;
b = 0.0f;
} else if (x >= 0.6f && x < 0.8f) {
x = (x - 0.6f) / 0.2f;
r = 1.0f;
g = 1.0f - x;
b = 0.0f;
} else if (x >= 0.8f && x <= 1.0f) {
x = (x - 0.8f) / 0.2f;
r = 1.0f;
g = 0.0f;
b = x;
}
return new Color(r, g, b);
}
163276 次浏览

如果N足够大,你会得到一些相似的颜色。世界上只有这么多。

为什么不把它们均匀地分布在光谱中,像这样:

IEnumerable<Color> CreateUniqueColors(int nColors)
{
int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
for(int r = 0; r < 255; r += subdivision)
for(int g = 0; g < 255; g += subdivision)
for(int b = 0; b < 255; b += subdivision)
yield return Color.FromArgb(r, g, b);
}

如果您想混合序列,以便相似的颜色不在彼此旁边,您可能会打乱结果列表。

是我想得不够周全吗?

你可以使用HSL颜色模型来创建颜色。

如果你想要的只是不同的色调(可能),以及亮度或饱和度的轻微变化,你可以像这样分配色调:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)


for(i = 0; i < 360; i += 360 / num_colors) {
HSLColor c;
c.hue = i;
c.saturation = 90 + randf() * 10;
c.lightness = 50 + randf() * 10;


addColor(c);
}

我有个主意。想象一个HSV气缸

< img src = " https://upload.wikimedia.org/wikipedia/commons/8/8f/HSV_cylinder.jpg " >

定义亮度和饱和度的上限和下限。这在空间内定义了一个正方形的横截面环。

现在,在这个空间中随机散布N个点。

然后对它们应用迭代排斥算法,要么迭代次数固定,要么直到这些点稳定下来。

现在你应该有N个点,代表N种颜色,它们在你感兴趣的颜色空间中尽可能不同。

雨果

这里有一个解决你的“独特”问题的解决方案,这完全是夸大的:

创建一个单位球体,并在其上放置带有排斥电荷的点。运行一个粒子系统,直到它们不再移动(或者delta“足够小”)。在这一点上,每个点之间的距离都尽可能远。将(x, y, z)转换为rgb。

我提到它是因为对于某些类型的问题,这种类型的解决方案比暴力解决方案更好。

我最初看到这里的方法用于镶嵌球体。

同样,遍历HSL空间或RGB空间的最明显的解决方案可能工作得很好。

这个问题出现在相当多的SO讨论中:

提出了不同的解决方案,但没有一个是最优的。幸运的是,科学来拯救

任意N

最后两本将通过大多数大学图书馆/代理免费提供。

N是有限且相对较小的

在这种情况下,可以使用列表解决方案。关于这个主题,有一篇非常有趣的文章是免费的:

有几个颜色列表可以考虑:

  • Boynton列出了11种几乎不会被混淆的颜色(可在前一节的第一篇论文中找到)
  • Kelly的22种最大对比度的颜色(可以在上面的论文中找到)

我还遇到了麻省理工学院的学生调色板。 最后,下面的链接可能在不同的颜色系统/坐标之间转换有用(例如,文章中的一些颜色没有在RGB中指定)

对于Kelly和Boynton的列表,我已经将其转换为RGB(除了白色和黑色,这应该很明显)。一些c#代码:

public static ReadOnlyCollection<Color> KellysMaxContrastSet
{
get { return _kellysMaxContrastSet.AsReadOnly(); }
}


private static readonly List<Color> _kellysMaxContrastSet = new List<Color>
{
UIntToColor(0xFFFFB300), //Vivid Yellow
UIntToColor(0xFF803E75), //Strong Purple
UIntToColor(0xFFFF6800), //Vivid Orange
UIntToColor(0xFFA6BDD7), //Very Light Blue
UIntToColor(0xFFC10020), //Vivid Red
UIntToColor(0xFFCEA262), //Grayish Yellow
UIntToColor(0xFF817066), //Medium Gray


//The following will not be good for people with defective color vision
UIntToColor(0xFF007D34), //Vivid Green
UIntToColor(0xFFF6768E), //Strong Purplish Pink
UIntToColor(0xFF00538A), //Strong Blue
UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
UIntToColor(0xFF53377A), //Strong Violet
UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
UIntToColor(0xFFB32851), //Strong Purplish Red
UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
UIntToColor(0xFF7F180D), //Strong Reddish Brown
UIntToColor(0xFF93AA00), //Vivid Yellowish Green
UIntToColor(0xFF593315), //Deep Yellowish Brown
UIntToColor(0xFFF13A13), //Vivid Reddish Orange
UIntToColor(0xFF232C16), //Dark Olive Green
};


public static ReadOnlyCollection<Color> BoyntonOptimized
{
get { return _boyntonOptimized.AsReadOnly(); }
}


private static readonly List<Color> _boyntonOptimized = new List<Color>
{
Color.FromArgb(0, 0, 255),      //Blue
Color.FromArgb(255, 0, 0),      //Red
Color.FromArgb(0, 255, 0),      //Green
Color.FromArgb(255, 255, 0),    //Yellow
Color.FromArgb(255, 0, 255),    //Magenta
Color.FromArgb(255, 128, 128),  //Pink
Color.FromArgb(128, 128, 128),  //Gray
Color.FromArgb(128, 0, 0),      //Brown
Color.FromArgb(255, 128, 0),    //Orange
};


static public Color UIntToColor(uint color)
{
var a = (byte)(color >> 24);
var r = (byte)(color >> 16);
var g = (byte)(color >> 8);
var b = (byte)(color >> 0);
return Color.FromArgb(a, r, g, b);
}

下面是十六进制和每通道8位的RGB值:

kelly_colors_hex = [
0xFFB300, # Vivid Yellow
0x803E75, # Strong Purple
0xFF6800, # Vivid Orange
0xA6BDD7, # Very Light Blue
0xC10020, # Vivid Red
0xCEA262, # Grayish Yellow
0x817066, # Medium Gray


# The following don't work well for people with defective color vision
0x007D34, # Vivid Green
0xF6768E, # Strong Purplish Pink
0x00538A, # Strong Blue
0xFF7A5C, # Strong Yellowish Pink
0x53377A, # Strong Violet
0xFF8E00, # Vivid Orange Yellow
0xB32851, # Strong Purplish Red
0xF4C800, # Vivid Greenish Yellow
0x7F180D, # Strong Reddish Brown
0x93AA00, # Vivid Yellowish Green
0x593315, # Deep Yellowish Brown
0xF13A13, # Vivid Reddish Orange
0x232C16, # Dark Olive Green
]


kelly_colors = dict(vivid_yellow=(255, 179, 0),
strong_purple=(128, 62, 117),
vivid_orange=(255, 104, 0),
very_light_blue=(166, 189, 215),
vivid_red=(193, 0, 32),
grayish_yellow=(206, 162, 98),
medium_gray=(129, 112, 102),


# these aren't good for people with defective color vision:
vivid_green=(0, 125, 52),
strong_purplish_pink=(246, 118, 142),
strong_blue=(0, 83, 138),
strong_yellowish_pink=(255, 122, 92),
strong_violet=(83, 55, 122),
vivid_orange_yellow=(255, 142, 0),
strong_purplish_red=(179, 40, 81),
vivid_greenish_yellow=(244, 200, 0),
strong_reddish_brown=(127, 24, 13),
vivid_yellowish_green=(147, 170, 0),
deep_yellowish_brown=(89, 51, 21),
vivid_reddish_orange=(241, 58, 19),
dark_olive_green=(35, 44, 22))

对于所有Java开发人员,以下是JavaFX的颜色:

// Don't forget to import javafx.scene.paint.Color;


private static final Color[] KELLY_COLORS = {
Color.web("0xFFB300"),    // Vivid Yellow
Color.web("0x803E75"),    // Strong Purple
Color.web("0xFF6800"),    // Vivid Orange
Color.web("0xA6BDD7"),    // Very Light Blue
Color.web("0xC10020"),    // Vivid Red
Color.web("0xCEA262"),    // Grayish Yellow
Color.web("0x817066"),    // Medium Gray


Color.web("0x007D34"),    // Vivid Green
Color.web("0xF6768E"),    // Strong Purplish Pink
Color.web("0x00538A"),    // Strong Blue
Color.web("0xFF7A5C"),    // Strong Yellowish Pink
Color.web("0x53377A"),    // Strong Violet
Color.web("0xFF8E00"),    // Vivid Orange Yellow
Color.web("0xB32851"),    // Strong Purplish Red
Color.web("0xF4C800"),    // Vivid Greenish Yellow
Color.web("0x7F180D"),    // Strong Reddish Brown
Color.web("0x93AA00"),    // Vivid Yellowish Green
Color.web("0x593315"),    // Deep Yellowish Brown
Color.web("0xF13A13"),    // Vivid Reddish Orange
Color.web("0x232C16"),    // Dark Olive Green
};

以下是根据上面的顺序未排序的凯利颜色。

unsorted kelly colors

以下是按色调排序的方凯利颜色(注意一些黄色的对比不是很明显)

 sorted kelly colors

我会尽量把饱和度和亮度调到最大,只关注色调。在我看来,H可以从0到255,然后绕圈。现在如果你想要两种对比色,你可以取这个环的对边,即0和128。如果你想要4种颜色,你需要取一些以圆周长度256的1/4为间隔的颜色,即0,64,128,192。当然,正如其他人建议的那样,当你需要N种颜色时,你可以用256/N将它们分开。

我想补充的是,用二进制数的反向表示来形成这个序列。看看这个:

0 = 00000000  after reversal is 00000000 = 0
1 = 00000001  after reversal is 10000000 = 128
2 = 00000010  after reversal is 01000000 = 64
3 = 00000011  after reversal is 11000000 = 192
< p >… 这样,如果你需要N个不同的颜色,你可以只取前N个数字,把它们颠倒过来,你就能得到尽可能多的距离点(因为N是2的幂),同时保持序列的每个前缀都有很大不同

在我的用例中,这是一个重要的目标,因为我有一个图表,其中颜色是根据这种颜色所覆盖的区域进行排序的。我希望图表中最大的区域具有较大的对比度,并且我对一些小区域使用与前十名相似的颜色也没有问题,因为读者通过观察区域就可以很明显地看出哪个是哪个。

为了子孙后代,我在这里添加了Python中公认的答案。

import numpy as np
import colorsys


def _get_colors(num_colors):
colors=[]
for i in np.arange(0., 360., 360. / num_colors):
hue = i/360.
lightness = (50 + np.random.rand() * 10)/100.
saturation = (90 + np.random.rand() * 10)/100.
colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
return colors

就像Uri Cohen的答案,但它是一个生成器。首先要把颜色分开。确定的。

样本,左边的颜色先: < img src = " https://i.stack.imgur.com/qJcEu.png " alt = "采样" / > < / p >

#!/usr/bin/env python3
from typing import Iterable, Tuple
import colorsys
import itertools
from fractions import Fraction
from pprint import pprint


def zenos_dichotomy() -> Iterable[Fraction]:
"""
http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7
"""
for k in itertools.count():
yield Fraction(1,2**k)


def fracs() -> Iterable[Fraction]:
"""
[Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...]
[0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...]
"""
yield Fraction(0)
for k in zenos_dichotomy():
i = k.denominator # [1,2,4,8,16,...]
for j in range(1,i,2):
yield Fraction(j,i)


# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant
# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)


HSVTuple = Tuple[Fraction, Fraction, Fraction]
RGBTuple = Tuple[float, float, float]


def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]:
for s in [Fraction(6,10)]: # optionally use range
for v in [Fraction(8,10),Fraction(5,10)]: # could use range too
yield (h, s, v) # use bias for v here if you use range


def hsv_to_rgb(x: HSVTuple) -> RGBTuple:
return colorsys.hsv_to_rgb(*map(float, x))


flatten = itertools.chain.from_iterable


def hsvs() -> Iterable[HSVTuple]:
return flatten(map(hue_to_tones, fracs()))


def rgbs() -> Iterable[RGBTuple]:
return map(hsv_to_rgb, hsvs())


def rgb_to_css(x: RGBTuple) -> str:
uint8tuple = map(lambda y: int(y*255), x)
return "rgb({},{},{})".format(*uint8tuple)


def css_colors() -> Iterable[str]:
return map(rgb_to_css, rgbs())


if __name__ == "__main__":
# sample 100 colors in css format
sample_colors = list(itertools.islice(css_colors(), 100))
pprint(sample_colors)

每个人似乎都忽略了非常有用的YUV颜色空间的存在,它被设计用来表示人类视觉系统中可感知的颜色差异。YUV中的距离代表人类感知的差异。我需要这个功能的MagicCube4D实现4维魔方和无限数量的其他4D扭曲谜题有任意数量的脸。

我的解决方案首先在YUV中选择随机点,然后迭代分解最接近的两个点,在返回结果时只转换为RGB。方法是O(n^3),但对于小数字或可以缓存的数字来说,这并不重要。它当然可以变得更有效,但结果似乎很好。

该函数允许亮度阈值的可选规范,以不产生任何成分比给定量更亮或更暗的颜色。IE,你可能不希望值接近黑色或白色。当产生的颜色将被用作基础色,然后通过光照、分层、透明度等进行阴影处理,并且必须仍然与基础色不同时,这是有用的。

import java.awt.Color;
import java.util.Random;


/**
* Contains a method to generate N visually distinct colors and helper methods.
*
* @author Melinda Green
*/
public class ColorUtils {
private ColorUtils() {} // To disallow instantiation.
private final static float
U_OFF = .436f,
V_OFF = .615f;
private static final long RAND_SEED = 0;
private static Random rand = new Random(RAND_SEED);


/*
* Returns an array of ncolors RGB triplets such that each is as unique from the rest as possible
* and each color has at least one component greater than minComponent and one less than maxComponent.
* Use min == 1 and max == 0 to include the full RGB color range.
*
* Warning: O N^2 algorithm blows up fast for more than 100 colors.
*/
public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) {
rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs


float[][] yuv = new float[ncolors][3];


// initialize array with random colors
for(int got = 0; got < ncolors;) {
System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3);
}
// continually break up the worst-fit color pair until we get tired of searching
for(int c = 0; c < ncolors * 1000; c++) {
float worst = 8888;
int worstID = 0;
for(int i = 1; i < yuv.length; i++) {
for(int j = 0; j < i; j++) {
float dist = sqrdist(yuv[i], yuv[j]);
if(dist < worst) {
worst = dist;
worstID = i;
}
}
}
float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv);
if(best == null)
break;
else
yuv[worstID] = best;
}


Color[] rgbs = new Color[yuv.length];
for(int i = 0; i < yuv.length; i++) {
float[] rgb = new float[3];
yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb);
rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]);
//System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]);
}


return rgbs;
}


public static void hsv2rgb(float h, float s, float v, float[] rgb) {
// H is given on [0->6] or -1. S and V are given on [0->1].
// RGB are each returned on [0->1].
float m, n, f;
int i;


float[] hsv = new float[3];


hsv[0] = h;
hsv[1] = s;
hsv[2] = v;
System.out.println("H: " + h + " S: " + s + " V:" + v);
if(hsv[0] == -1) {
rgb[0] = rgb[1] = rgb[2] = hsv[2];
return;
}
i = (int) (Math.floor(hsv[0]));
f = hsv[0] - i;
if(i % 2 == 0)
f = 1 - f; // if i is even
m = hsv[2] * (1 - hsv[1]);
n = hsv[2] * (1 - hsv[1] * f);
switch(i) {
case 6:
case 0:
rgb[0] = hsv[2];
rgb[1] = n;
rgb[2] = m;
break;
case 1:
rgb[0] = n;
rgb[1] = hsv[2];
rgb[2] = m;
break;
case 2:
rgb[0] = m;
rgb[1] = hsv[2];
rgb[2] = n;
break;
case 3:
rgb[0] = m;
rgb[1] = n;
rgb[2] = hsv[2];
break;
case 4:
rgb[0] = n;
rgb[1] = m;
rgb[2] = hsv[2];
break;
case 5:
rgb[0] = hsv[2];
rgb[1] = m;
rgb[2] = n;
break;
}
}




// From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas
public static void yuv2rgb(float y, float u, float v, float[] rgb) {
rgb[0] = 1 * y + 0 * u + 1.13983f * v;
rgb[1] = 1 * y + -.39465f * u + -.58060f * v;
rgb[2] = 1 * y + 2.03211f * u + 0 * v;
}


public static void rgb2yuv(float r, float g, float b, float[] yuv) {
yuv[0] = .299f * r + .587f * g + .114f * b;
yuv[1] = -.14713f * r + -.28886f * g + .436f * b;
yuv[2] = .615f * r + -.51499f * g + -.10001f * b;
}


private static float[] randYUVinRGBRange(float minComponent, float maxComponent) {
while(true) {
float y = rand.nextFloat(); // * YFRAC + 1-YFRAC);
float u = rand.nextFloat() * 2 * U_OFF - U_OFF;
float v = rand.nextFloat() * 2 * V_OFF - V_OFF;
float[] rgb = new float[3];
yuv2rgb(y, u, v, rgb);
float r = rgb[0], g = rgb[1], b = rgb[2];
if(0 <= r && r <= 1 &&
0 <= g && g <= 1 &&
0 <= b && b <= 1 &&
(r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components
(r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components


return new float[]{y, u, v};
}
}


private static float sqrdist(float[] a, float[] b) {
float sum = 0;
for(int i = 0; i < a.length; i++) {
float diff = a[i] - b[i];
sum += diff * diff;
}
return sum;
}


private static double worstFit(Color[] colors) {
float worst = 8888;
float[] a = new float[3], b = new float[3];
for(int i = 1; i < colors.length; i++) {
colors[i].getColorComponents(a);
for(int j = 0; j < i; j++) {
colors[j].getColorComponents(b);
float dist = sqrdist(a, b);
if(dist < worst) {
worst = dist;
}
}
}
return Math.sqrt(worst);
}


private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) {
for(int attempt = 1; attempt < 100 * in.length; attempt++) {
float[] candidate = randYUVinRGBRange(minComponent, maxComponent);
boolean good = true;
for(int i = 0; i < in.length; i++)
if(sqrdist(candidate, in[i]) < bestDistSqrd)
good = false;
if(good)
return candidate;
}
return null; // after a bunch of passes, couldn't find a candidate that beat the best.
}




/**
* Simple example program.
*/
public static void main(String[] args) {
final int ncolors = 10;
Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f);
for(int i = 0; i < colors.length; i++) {
System.out.println(colors[i].toString());
}
System.out.println("Worst fit color = " + worstFit(colors));
}


}

这在MATLAB中是微不足道的(有一个hsv命令):

cmap = hsv(number_of_colors)

我为R写了一个名为qualpalr的包,它是专门为此目的设计的。我建议你看看装饰图案来找出它是如何工作的,但我会尽量总结要点。

qualpalr获取HSL颜色空间中的颜色规范(前面在本线程中描述过),将其投射到DIN99d颜色空间(在感知上是均匀的),并找到使它们之间的最小距离最大化的n

# Create a palette of 4 colors of hues from 0 to 360, saturations between
# 0.1 and 0.5, and lightness from 0.6 to 0.85
pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85)))


# Look at the colors in hex format
pal$hex
#> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0"


# Create a palette using one of the predefined color subspaces
pal2 <- qualpal(n = 4, colorspace = "pretty")


# Distance matrix of the DIN99d color differences
pal2$de_DIN99d
#>        #69A3CC #6ECC6E #CA6BC4
#> 6ECC6E      22
#> CA6BC4      21      30
#> CD976B      24      21      21


plot(pal2)

enter image description here

我认为这个简单的递归算法补充了公认的答案,以产生不同的色调值。我为hsv做了它,但也可以用于其他颜色空间。

它在循环中产生色调,在每个循环中尽可能彼此分离。

/**
* 1st cycle: 0, 120, 240
* 2nd cycle (+60): 60, 180, 300
* 3th cycle (+30): 30, 150, 270, 90, 210, 330
* 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345
*/
public static float recursiveHue(int n) {
// if 3: alternates red, green, blue variations
float firstCycle = 3;


// First cycle
if (n < firstCycle) {
return n * 360f / firstCycle;
}
// Each cycle has as much values as all previous cycles summed (powers of 2)
else {
// floor of log base 2
int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2));
// divDown stores the larger power of 2 that is still lower than n
int divDown = (int)(firstCycle * Math.pow(2, numCycles));
// same hues than previous cycle, but summing an offset (half than previous cycle)
return recursiveHue(n % divDown) + 180f / divDown;
}
}

我在这里找不到这种算法。我希望这对你有所帮助,这是我在这里的第一篇文章。

HSL颜色模型可能非常适合“排序”颜色,但如果你正在寻找视觉上独特的颜色,你肯定需要实验室颜色模型。

CIELAB被设计成相对于人类色觉而言在感知上是一致的,这意味着这些数值中相同数量的数值变化对应着大约相同数量的视觉感知变化。

一旦你知道了这一点,从广泛的颜色范围中找到N种颜色的最优子集仍然是一个(NP)困难的问题,有点类似于旅行推销员问题和所有使用k-mean算法或其他东西的解决方案不会有真正的帮助。

也就是说,如果N不是太大,如果你从一个有限的颜色集开始,你会很容易找到一个非常好的不同颜色的子集,根据一个简单的随机函数的Lab距离。

我已经编写了这样一个工具供我自己使用(你可以在这里找到它:https://mokole.com/palette.html),下面是我得到的N=7: enter image description here < / p >

它都是javascript,所以请随意查看页面的源代码,并根据自己的需要进行调整。

这个OpenCV函数使用HSV颜色模型在0<=H<=360º周围生成n均匀分布的颜色,最大S=1.0, V=1.0。函数在bgr_mat中输出BGR颜色:

void distributed_colors (int n, cv::Mat_<cv::Vec3f> & bgr_mat) {
cv::Mat_<cv::Vec3f> hsv_mat(n,CV_32F,cv::Vec3f(0.0,1.0,1.0));
double step = 360.0/n;
double h= 0.0;
cv::Vec3f value;
for (int i=0;i<n;i++,h+=step) {
value = hsv_mat.at<cv::Vec3f>(i);
hsv_mat.at<cv::Vec3f>(i)[0] = h;
}
cv::cvtColor(hsv_mat, bgr_mat, CV_HSV2BGR);
bgr_mat *= 255;
}

对于Python用户来说,seaborn非常简洁:

>>> import seaborn as sns
>>> sns.color_palette(n_colors=4)
< p > enter image description here
它返回RGB元组列表:

[(0.12156862745098039, 0.4666666666666667, 0.7058823529411765),
(1.0, 0.4980392156862745, 0.054901960784313725),
(0.17254901960784313, 0.6274509803921569, 0.17254901960784313),
(0.8392156862745098, 0.15294117647058825, 0.1568627450980392)]

我们只需要一个RGB三联体对的范围,这些三联体之间的距离最大。

我们可以定义一个简单的线性渐变,然后调整渐变的大小以获得所需的颜色数量。

在python中:

from skimage.transform import resize
import numpy as np
def distinguishable_colors(n, shuffle = True,
sinusoidal = False,
oscillate_tone = False):
ramp = ([1, 0, 0],[1,1,0],[0,1,0],[0,0,1], [1,0,1]) if n>3 else ([1,0,0], [0,1,0],[0,0,1])
    

coltrio = np.vstack(ramp)
    

colmap = np.round(resize(coltrio, [n,3], preserve_range=True,
order = 1 if n>3 else 3
, mode = 'wrap'),3)
    

if sinusoidal: colmap = np.sin(colmap*np.pi/2)
    

colmap = [colmap[x,] for x  in range(colmap.shape[0])]
    

if oscillate_tone:
oscillate = [0,1]*round(len(colmap)/2+.5)
oscillate = [np.array([osc,osc,osc]) for osc in oscillate]
colmap = [.8*colmap[x] + .2*oscillate[x] for x in range(len(colmap))]
    

#Whether to shuffle the output colors
if shuffle:
random.seed(1)
random.shuffle(colmap)
        

return colmap

enter image description here

enter image description here

enter image description here

Janus的回答,但更容易读懂。我还稍微调整了配色方案,并在你可以自己修改的地方做了标记

我已经把这个片段直接粘贴到一个jupyter笔记本。

import colorsys
import itertools
from fractions import Fraction
from IPython.display import HTML as html_print


def infinite_hues():
yield Fraction(0)
for k in itertools.count():
i = 2**k # zenos_dichotomy
for j in range(1,i,2):
yield Fraction(j,i)


def hue_to_hsvs(h: Fraction):
# tweak values to adjust scheme
for s in [Fraction(6,10)]:
for v in [Fraction(6,10), Fraction(9,10)]:
yield (h, s, v)


def rgb_to_css(rgb) -> str:
uint8tuple = map(lambda y: int(y*255), rgb)
return "rgb({},{},{})".format(*uint8tuple)


def css_to_html(css):
return f"<text style=background-color:{css}>&nbsp;&nbsp;&nbsp;&nbsp;</text>"


def show_colors(n=33):
hues = infinite_hues()
hsvs = itertools.chain.from_iterable(hue_to_hsvs(hue) for hue in hues)
rgbs = (colorsys.hsv_to_rgb(*hsv) for hsv in hsvs)
csss = (rgb_to_css(rgb) for rgb in rgbs)
htmls = (css_to_html(css) for css in csss)


myhtmls = itertools.islice(htmls, n)
display(html_print("".join(myhtmls)))


show_colors()

colors

上面有很多非常好的答案,但如果有人正在寻找一个快速的python解决方案,那么提到python包distinctify可能会很有用。它是pypi提供的一个轻量级包,使用起来非常简单:

from distinctipy import distinctipy


colors = distinctipy.get_colors(12)


print(colors)


# display the colours
distinctipy.color_swatch(colors)

它返回一个rgb元组列表

[(0, 1, 0), (1, 0, 1), (0, 0.5, 1), (1, 0.5, 0), (0.5, 0.75, 0.5), (0.4552518132842178, 0.12660764790179446, 0.5467915225460569), (1, 0, 0), (0.12076092516775849, 0.9942188027771208, 0.9239958090462229), (0.254747094970068, 0.4768020779917903, 0.02444859177890535), (0.7854526395841417, 0.48630704929211144, 0.9902480906347156), (0, 0, 1), (1, 1, 0)]

enter image description here

此外,它还有一些额外的功能,比如生成不同于现有颜色列表的颜色。