跟进:如何准确地计算颜色之间的“距离”

53
我正在寻找一种函数,尝试量化两种颜色之间的“距离”(或区别)程度。这个问题实际上有两个部分:
  1. 哪种颜色空间最能代表人类视觉?
  2. 在该空间中,哪种距离度量最能代表人类视觉(欧几里得距离?)

原始问题


1
当我处理类似情况时,最终我绘制了下面一些解决方案的图表。其他人可能会发现这些图表很有趣。https://dev59.com/EW025IYBdhLWcg3w1JiG - Basic
7个回答

48

转换为La*b*(也称为普通的“Lab”,你还会看到参考文献中提到“CIELAB”),这是一种很好的快速测量颜色差异的方法:

(L1-L2)^2 + (a1-a2)^2 + (b1-b2)^2

颜色科学家有其他更精细的度量方法,根据所需的准确度而定,可能不值得麻烦。

ab值以类似于锥体工作的方式表示对立的颜色,并且可以为负或正。 中性颜色-白色、灰色是a=0b=0L是亮度以特定方式定义,从零(纯黑)到无限大。

简单来说:>>给定一个颜色,我们的眼睛区分两个宽波长范围-蓝色与较长的波长。然后,由于最近的基因突变,较长波长的锥体分裂成两个,可以区分红色与绿色。

顺便说一下,超越只知道“RGB”或“CMYK”的颜色蒙古人同事将有助于你的职业生涯,这些颜色模式适用于设备但不适用于认知工作。我曾为不知道这些东西的成像科学家工作过!

有关颜色差异理论的更多有趣阅读,请尝试:

更多关于Lab的详细信息。目前我找不到一个好看的页面来提供转换公式,但我相信有人会编辑这个答案并提供。


4
关于转换公式的丑陋性:它们之所以丑陋,是因为从RGB到XYZ再到LAB的过程取决于视觉条件。参考链接:(警告,很丑)http://www.easyrgb.com/index.php?X=MATH - Gregg Lind
1
也许值得添加一些关于CIE定义的标准颜色距离度量和伪度量的描述:http://en.wikipedia.org/wiki/Color_difference - BartoszKP
顺便提一下,这是勾股定理的一半,它给出了在3D空间中LAB模型所占据的距离(非线性距离计算)两种颜色(点)之间的距离(不正确的,这是一个在应该是均匀的空间中的非线性距离计算)。关于这个主题,这里有更多信息:https://en.wikipedia.org/wiki/Color_difference。 - Prime

9
由于我无法访问上面提供的cmetric.htm链接,以及我在寻找颜色距离的许多其他实现(经过漫长的旅程后...)都失败了,因此我学习了如何使用OpenCV从两个RGB值计算出最佳颜色距离和最科学准确的颜色距离deltaE

这需要进行3次颜色空间转换+一些代码转换,从javascript (http://svn.int64.org/viewvc/int64/colors/colors.js) 转换为C++

最终的代码似乎可以直接使用,希望没有人发现严重的错误...但经过多次测试,看起来是没问题的。

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/photo/photo.hpp>
#include <math.h>

using namespace cv;
using namespace std;

#define REF_X 95.047; // Observer= 2°, Illuminant= D65
#define REF_Y 100.000;
#define REF_Z 108.883;

void bgr2xyz( const Vec3b& BGR, Vec3d& XYZ );
void xyz2lab( const Vec3d& XYZ, Vec3d& Lab );
void lab2lch( const Vec3d& Lab, Vec3d& LCH );
double deltaE2000( const Vec3b& bgr1, const Vec3b& bgr2 );
double deltaE2000( const Vec3d& lch1, const Vec3d& lch2 );


void bgr2xyz( const Vec3b& BGR, Vec3d& XYZ )
{
    double r = (double)BGR[2] / 255.0;
    double g = (double)BGR[1] / 255.0;
    double b = (double)BGR[0] / 255.0;
    if( r > 0.04045 )
        r = pow( ( r + 0.055 ) / 1.055, 2.4 );
    else
        r = r / 12.92;
    if( g > 0.04045 )
        g = pow( ( g + 0.055 ) / 1.055, 2.4 );
    else
        g = g / 12.92;
    if( b > 0.04045 )
        b = pow( ( b + 0.055 ) / 1.055, 2.4 );
    else
        b = b / 12.92;
    r *= 100.0;
    g *= 100.0;
    b *= 100.0;
    XYZ[0] = r * 0.4124 + g * 0.3576 + b * 0.1805;
    XYZ[1] = r * 0.2126 + g * 0.7152 + b * 0.0722;
    XYZ[2] = r * 0.0193 + g * 0.1192 + b * 0.9505;
}

void xyz2lab( const Vec3d& XYZ, Vec3d& Lab )
{
    double x = XYZ[0] / REF_X;
    double y = XYZ[1] / REF_X;
    double z = XYZ[2] / REF_X;
    if( x > 0.008856 )
        x = pow( x , .3333333333 );
    else
        x = ( 7.787 * x ) + ( 16.0 / 116.0 );
    if( y > 0.008856 )
        y = pow( y , .3333333333 );
    else
        y = ( 7.787 * y ) + ( 16.0 / 116.0 );
    if( z > 0.008856 )
        z = pow( z , .3333333333 );
    else
        z = ( 7.787 * z ) + ( 16.0 / 116.0 );
    Lab[0] = ( 116.0 * y ) - 16.0;
    Lab[1] = 500.0 * ( x - y );
    Lab[2] = 200.0 * ( y - z );
}

void lab2lch( const Vec3d& Lab, Vec3d& LCH )
{
    LCH[0] = Lab[0];
    LCH[1] = sqrt( ( Lab[1] * Lab[1] ) + ( Lab[2] * Lab[2] ) );
    LCH[2] = atan2( Lab[2], Lab[1] );
}

double deltaE2000( const Vec3b& bgr1, const Vec3b& bgr2 )
{
    Vec3d xyz1, xyz2, lab1, lab2, lch1, lch2;
    bgr2xyz( bgr1, xyz1 );
    bgr2xyz( bgr2, xyz2 );
    xyz2lab( xyz1, lab1 );
    xyz2lab( xyz2, lab2 );
    lab2lch( lab1, lch1 );
    lab2lch( lab2, lch2 );
    return deltaE2000( lch1, lch2 );
}

double deltaE2000( const Vec3d& lch1, const Vec3d& lch2 )
{
    double avg_L = ( lch1[0] + lch2[0] ) * 0.5;
    double delta_L = lch2[0] - lch1[0];
    double avg_C = ( lch1[1] + lch2[1] ) * 0.5;
    double delta_C = lch1[1] - lch2[1];
    double avg_H = ( lch1[2] + lch2[2] ) * 0.5;
    if( fabs( lch1[2] - lch2[2] ) > CV_PI )
        avg_H += CV_PI;
    double delta_H = lch2[2] - lch1[2];
    if( fabs( delta_H ) > CV_PI )
    {
        if( lch2[2] <= lch1[2] )
            delta_H += CV_PI * 2.0;
        else
            delta_H -= CV_PI * 2.0;
    }

    delta_H = sqrt( lch1[1] * lch2[1] ) * sin( delta_H ) * 2.0;
    double T = 1.0 -
            0.17 * cos( avg_H - CV_PI / 6.0 ) +
            0.24 * cos( avg_H * 2.0 ) +
            0.32 * cos( avg_H * 3.0 + CV_PI / 30.0 ) -
            0.20 * cos( avg_H * 4.0 - CV_PI * 7.0 / 20.0 );
    double SL = avg_L - 50.0;
    SL *= SL;
    SL = SL * 0.015 / sqrt( SL + 20.0 ) + 1.0;
    double SC = avg_C * 0.045 + 1.0;
    double SH = avg_C * T * 0.015 + 1.0;
    double delta_Theta = avg_H / 25.0 - CV_PI * 11.0 / 180.0;
    delta_Theta = exp( delta_Theta * -delta_Theta ) * ( CV_PI / 6.0 );
    double RT = pow( avg_C, 7.0 );
    RT = sqrt( RT / ( RT + 6103515625.0 ) ) * sin( delta_Theta ) * -2.0; // 6103515625 = 25^7
    delta_L /= SL;
    delta_C /= SC;
    delta_H /= SH;
    return sqrt( delta_L * delta_L + delta_C * delta_C + delta_H * delta_H + RT * delta_C * delta_H );
}

希望这能帮助到某些人 :)

6
HSL和HSV更适合人类的色彩感知。根据维基百科的说法:
有时在使用艺术材料、数字图像或其他媒体时,使用HSV或HSL颜色模型比使用RGB或CMYK等替代模型更可取,因为这些模型模拟人类感知颜色的方式存在差异。RGB和CMYK分别是加性和减性模型,模拟了混合时原色光或颜料(分别)组合形成新颜色的方式。

Graphical depiction of HSV


注意:红色在0°,所以略带黄色的红色在+10°,略带蓝色的红色在-10°或350°。现在计算距离并不像简单地减去这两个值那么容易。 - Otto Allmendinger
2
在其他答案的背景下并不是很有帮助。CIE定义的度量标准更适合于人类对颜色的感知,而不是HLS和HSV。 - BartoszKP
在这篇论文中,提出了HSV空间中颜色距离的公式。http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.2498&rep=rep1&type=pdf - fyts
人们喜欢互相重复关于CIE比HSV更好的事情,但他们从未有过证据。到目前为止,双锥形HSV距离对我来说效果很好。https://github.com/Nakilon/colorutils/blob/f8727837f58669610931008eb18a9fd2695da13b/lib/colorutils.rb#L18-L26 - Nakilon

4
最简单的方法是将颜色视为从同一起点发出的3D向量,并计算它们终点之间的距离。
如果需要考虑绿色在判断强度方面更加突出的因素,可以对值进行加权。 ImageMagic 提供以下比例:
- 红色:0.3 - 绿色:0.6 - 蓝色:0.1
当然,这样的值只有与其他颜色的值相关时才有意义,而不是对人类有意义的东西,因此您只能将这些值用于相似性排序。

4
首先,我建议使用常见的度量HSV(色相,饱和度和明度)或HSL更能代表人类感知颜色,而不是RGB或CYMK。详见Wikipedia上的HSL、HSV
我认为天真地在HSL空间中绘制两种颜色的点并计算差异向量的大小是不够的。然而,这意味着明亮的黄色和明亮的绿色将被认为与绿色到深绿色一样不同。但是许多人认为红色和粉色是两种不同的颜色。
此外,该参数空间中同方向的差异向量并不相等。例如,人眼更容易分辨绿色。从绿色到红色的色相偏移可能会看起来更大。此外,从小饱和度到零的饱和度变化是灰色和粉色之间的区别,而在其他情况下,饱和度变化就是两个红色阴影之间的区别。
从程序员的角度来看,您需要绘制差异向量,但需根据HSL空间中各个区域调整长度的比例矩阵进行修改。这将是相当任意的,并基于各种颜色理论观点进行调整,具体取决于您想要将其应用于什么。
更好的方法是查看是否有人已经在线上做了这样的事情...

4

2

作为一个色盲者,我认为增加比正常视力更多的分离是有好处的。最常见的色盲类型是红/绿色盲。这并不意味着你看不到红色或绿色,而是更难以看到它们之间的差异。因此,对于色盲人来说,需要更大的分离度才能区分颜色。


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