如何比较颜色对象并获取最接近颜色的Color[]?

15

假设我有一个颜色数组(包含整个颜色光谱,从红到红)。更简短的版本如下:

public Color[] ColorArray = new Color[360] { Color.FromArgb(255, 245, 244, 242), Color.FromArgb(255, 245, 244, 240), Color.FromArgb(255, 245, 244, 238) }

现在,如果我有一个单独的

Color object (Color c = Color.FromArgb(255, 14, 4, 5))

我该如何获取数组中与所选颜色最接近的值?这是否可能?


可以实现,您只需要编写自定义比较器...但是如何确定(255,0,255)(0, 255, 255)中哪一个更接近白色呢? - Sayse
你需要定义“更接近”的含义。通常这很明显,但对于颜色来说,进行一些额外的思考和测试可能是个好主意。直接的方法是通过添加3个RGB通道的差异来测量距离;但也许你更愿意通过给色调比亮度和饱和度更多的权重来衡量测量值..?所有这些值都可以直接在Color类中访问.. - TaW
2个回答

34
颜色距离并不是一个精确定义的概念。因此,这里有三种方法来衡量它:
1. 检查颜色的色调,忽略饱和度和亮度。 2. 仅在RGB空间中测量直接距离。 3. 以某种方式权衡色调、饱和度和亮度。
显然,您可能想要更改第三种测量中的魔术数字:色调在0-360之间,亮度和饱和度在0-1之间,因此使用这些数字,色调比饱和度和亮度重约3.6倍。
更新:我发布的原始解决方案包含几个错误:
1. 我使用的Linq未找到最接近的匹配,而是从下面最接近;这意味着有50%的机会偏差1。 2. 在某些地方,我使用了color.GetBrightness()方法。姑且这么说吧:这完全没用。例如:蓝色和黄色具有相同的值0.5! 3. 色调的值从0-360,但当然它们会循环!我完全错过了这一点。
我已经用更正后的代码替换了大部分原始答案:
这些现在是方法的新版本,每个方法都返回找到的最接近匹配的索引:
// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
    var hue1 = target.GetHue();
    var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
    var diffMin = diffs.Min(n => n);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
    var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
    return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}

// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
    float hue1 = target.GetHue();
    var num1 = ColorNum(target);
    var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) + 
                                   getHueDistance(n.GetHue(), hue1) );
    var diffMin = diffs.Min(x => x);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

一些辅助函数:
 // color brightness as perceived:
float getBrightness(Color c)  
    { return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}

// distance between two hues:
float getHueDistance(float hue1, float hue2)
{ 
    float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }

//  weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat + 
                                      getBrightness(c) * factorBri; }

// distance in RGB space
int ColorDiff(Color c1, Color c2) 
      { return  (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R) 
                               + (c1.G - c2.G) * (c1.G - c2.G)
                               + (c1.B - c2.B) * (c1.B - c2.B)); }

这是我用于截屏文本的小助手:

Brush tBrush(Color c) { 
      return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }

我已经更新了截图,不仅显示了13种颜色,还显示了一些主要偏红的颜色进行测试;所有颜色都显示其色相、饱和度和亮度值。最后三个数字是三种方法的结果。
可以看到,对于明亮和非饱和颜色来说,简单距离方法在色相方面是相当误导的:最后一种颜色(象牙色)实际上是一种明亮和苍白的黄色!
在我看来,评估所有颜色属性的第三种方法是最好的。但你应该尝试调整评估数值!
最终它真的取决于你想要实现什么;如果像看起来那样,你只关心颜色的色相,那就简单地采用第一种方法!你可以像这样使用你的数组进行调用:
int indexInArray = closestColor1(clist.ToList(), someColor);

想要了解更多关于颜色差异的知识,请参考维基百科上的相关页面

颜色差异

// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;

// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);

我有一个包含所有颜色的色轮,然后我获取了一个RGB值。现在我想把取色器放在最接近的颜色上。如果RGB值是类似红色的,它应该在色轮的红色部分。 - Niels
如果你只关心颜色的色调,特别是当你的颜色列表都是饱和的时候,选择第一种方法! - TaW
2
随着亮度接近0或1,色调的权重需要降低。换句话说,我可以有两种非常接近,非常明亮(或非常暗)的颜色,但它们的色调可能相差180。 - Mark Miller
有趣的概念,可能经常是有很多意义的。第二种方法(在RGB空间中直接距离)基本上做了类似的事情,尽管... - TaW
第一种方法是有效的,但会混合红色和白色。如果我的列表中首先是白色,那么所有的红色输出都会变成白色;如果红色在列表中排在第一位,那么所有的白色都会读成红色... - undefined

3

试试这个:

    static void Main()
    {
        Color[] ColorArray =
        {
            Color.FromArgb(255, 245, 244, 242), 
            Color.FromArgb(255, 245, 244, 240),
            Color.FromArgb(255, 245, 244, 238)
        };

        var closest = GetClosestColor(ColorArray, Color.FromArgb(255, 245, 244, 241));
        Console.WriteLine(closest);
    }

    private static Color GetClosestColor(Color[] colorArray, Color baseColor)
    {
        var colors = colorArray.Select(x => new {Value = x, Diff = GetDiff(x, baseColor)}).ToList();
        var min = colors.Min(x => x.Diff);
        return colors.Find(x => x.Diff == min).Value;
    }

    private static int GetDiff(Color color, Color baseColor)
    {
        int a = color.A - baseColor.A,
            r = color.R - baseColor.R,
            g = color.G - baseColor.G,
            b = color.B - baseColor.B;
        return a*a + r*r + g*g + b*b;
    }

在这里,我将“closest”解释为ARGB空间中的欧几里得距离。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接