最佳匹配颜色的算法。

29

我有一个包含约200个RGB格式颜色的数组。 我想编写一个程序,它可以接受任何RGB颜色并尝试匹配最“相似”的颜色。

我需要一个很好的“相似”定义,尽可能接近于人类感知。

我还想显示有关匹配准确性的一些信息。例如黑白:100%,对于具有稍微不同色调的相似颜色:-4%。

我需要使用神经网络吗? 是否有更简单的替代方法?


这个问题是关于建议一个好的相似度函数,还是关于一个算法来快速找到与给定颜色相似度最高的颜色(或颜色集合)在数组中的位置? - mjv
两者都需要。如果我想尝试创建算法,首先需要一些相似性的定义。我认为“感知上相似”是我正在寻找的。 - Maciek Sawicki
7个回答

39

将所有颜色转换为CIE Lab颜色空间,并在该空间中计算距离。

deltaE = sqrt(deltaL^2 + deltaA^2 + deltaB^2)

颜色的最低 DeltaE 值越小,它们在视觉上就越相似。


17
记住,你不需要进行平方根运算 - 平方根是一个单调递增的函数,因此这一步是多余的。 - Rooke
6
没错,如果你只是进行排序,距离的平方和距离本身一样好。如果你想比较“不同的程度”,那就留下它。 - hobbs
CIE Lab被用于所有主要的颜色管理系统中,例如来自苹果、微软和Adobe的系统,以完全相同的方式进行最近颜色计算。这是一个非常有趣的话题。 - Bob Murphy
从您的经验来看,deltaE的良好低值是多少?即当deltaE <14时,颜色在感知上是相似的。 - Marius
使用Lab值的常规缩放,人们可以看到的最小差异约为deltaE=1,而在显示器上可表示的最大差异可能为150-200。由您决定何时结束“相似”并开始“不相似” :) - hobbs

4
不需要神经网络!只需将HSL颜色值视为向量,并为向量定义加权模数函数,如下所示:
modulus = sqrt(a*H1*H1 + b*S1*S1 + c*L1*L1);

where a,b,c are weights you should decide based on your visual definition of what
creates a bigger difference in perceived color - a 1% change in Hue or a 1%
change in Saturation

我建议您使用 a = b = 0.5 和 c = 1。

最后,找出模数可能取值的范围,并定义类似颜色为那些模数非常接近的颜色(比如说5%)。


3
这是一个好的简单替代方法。从RGB到HSL的转换比从RGB到Lab的转换简单得多。 :) - hobbs
Crimson,你能检查一下取模的数学公式吗?我觉得它不对。你想要的更像是 a * (H1 - H2)**2 + ...,对吧? - hobbs
@hobbs - 最好计算两个模数,然后再进行比较,而不仅仅是计算差向量的模数。 - KJ Saxena
1
但是您不想要将两种不同颜色的色调等相乘,对吗? - hobbs
嗨,我想知道你所说的“找出模数范围”的意思是什么?我们是只需使用abs(mod1-mod2)比较两种颜色的模数,还是“找出范围”意味着不同的事情? - poncho

1

我还要指出最小二乘法,只是作为稍微简单一点的方法。也就是说,你取一个数的差,平方它,然后将所有这些平方差相加。


1

最近我也遇到了同样的问题,于是在网上比较了各种算法。一开始我对使用CIELAB颜色空间感到犹豫,因为它看起来很复杂,但实际上并没有想象中那么难。下面是你需要比较两个RGB值的全部代码。

struct CIELAB {
    float L, a, b;
};

float gammaCorrect( float v )
{
    return 100.0f * (v <= 0.04045f ? v / 12.92f : powf( (v + 0.055f) / 1.055f, 2.4f ));
}

float nonlinearToLinear( float v )
{
    return v > 0.008856f ? cbrtf( v ) : 7.787f * v + 16.0f / 116.0f;
}

CIELAB RGBToCIELAB( int R, int G, int B )
{
    float red = gammaCorrect( R / 255.0f );
    float green = gammaCorrect( G / 255.0f );
    float blue = gammaCorrect( B / 255.0f );

    float xr = nonlinearToLinear( (red * 0.4124564f + green * 0.3575761f + blue * 0.1804375f) / 95.047f );
    float yr = nonlinearToLinear( (red * 0.2126729f + green * 0.7151522f + blue * 0.0721750f) / 100.000f );
    float zr = nonlinearToLinear( (red * 0.0193339f + green * 0.1191920f + blue * 0.9503041f) / 108.883f );

    return { 116.0f * yr - 16.0f, 500.0f * (xr - yr), 200.0f * (yr - zr) };
}

float similarity( int R0, int G0, int B0, int R1, int G1, int B1 )
{
    CIELAB lab0 = RGBToCIELAB( R0, G0, B0 );
    CIELAB lab1 = RGBToCIELAB( R1, G1, B1 );
    float dL = lab0.L - lab1.L;
    float da = lab0.a - lab1.a;
    float db = lab0.b - lab1.b;
    return dL*dL + da*da + db*db;
}

对于similarity()函数,结果越低表示匹配度越好。为了提高效率,请将RGB颜色列表预先转换为CIELAB空间。

如果需要更简单的算法,可以参考维基百科的Color difference页面,其中有一个相当不错的算法。您可以使用整数运算来实现它,如果只比较相似性,则可以跳过平方根计算。

int similarity( int R0, int G0, int B0, int R1, int G1, int B1 )
{
    int dr = R0 - R1;
    int dg = G0 - G1;
    int db = B0 - B1;
    int redsum = R0 + R1;
    return (1024 + redsum) * dr*dr + 2048 * dg*dg + (1534 - redsum) * db*db;
}

计算不会超过32位有符号整数。

我发现这种匹配与在CIELAB空间中的匹配相比明显劣质,但计算是微不足道的。

我还尝试在HSV颜色空间中进行匹配,但对于某些颜色对没有得到良好的结果。例如,纯白和纯黑(它们之间的差异很大)可能具有相同的色调和饱和度,因此可能匹配得更好。


0

请在发布回答时非常小心,不要推销自己的作品。确保您在此处实际回答问题,并仅将您的博客/来源用作备份和参考。目前这很可能会被标记为垃圾邮件。 - ChrisF

0
我最快实现这个的方法是将颜色添加到八叉树中,然后像量化一样使用每个位来指导您到达最深的子节点。一旦您无法再深入,要么您在最深层(最低位),此时您已经找到了精确的颜色,要么您需要的下一个子节点不存在 - 此时您只需要具有最接近您正在搜索的位的位的子节点,那就是您最接近的颜色。这比将所有内容转换为HSL并返回,或计算每个欧几里得距离要快得多。
这是我在CodeProject上的代码:https://www.codeproject.com/tips/1046574/octtree-based-nearest-color-search

0
这是一个完整的Python代码,用于根据RGB中的任意值找到颜色的名称。
import matplotlib.colors as mc
import numpy as np
from scipy.spatial import KDTree
import cv2


class ColorNamer:
    def __init__(self):
        self.clut = {}
        self.clut_list = []
        self.clut_tree = None

        for name in mc.XKCD_COLORS:
            rgb = mc.to_rgb(mc.XKCD_COLORS[name])
            lab = cv2.cvtColor(np.single([[rgb]]), cv2.COLOR_RGB2Lab)[0][0]
            self.clut[tuple(lab)] = name[5:]
        self.clut_list = list(self.clut.keys())
        self.clut_tree = KDTree(self.clut_list)


    def name(self, rgb):
        lab = tuple(cv2.cvtColor(np.single([[rgb]]), cv2.COLOR_RGB2Lab)[0][0])
        dist, point = self.clut_tree.query(lab, 1)
        idx = int(point)
        key = self.clut_list[idx]
        return self.clut[key]

if __name__ == '__main__':
    cn = ColorNamer()
    print(cn.name((.3211, .543, .633)))

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