如何比较两种颜色的相似性/差异性

230
我想设计一个程序,可以帮助我评估5种预定义的颜色中哪种更类似于变量颜色,并给出相似度百分比。问题是我不知道如何逐步手动完成这个过程,因此想要设计一个程序更加困难。
更多细节:这些颜色来自具有不同颜色的凝胶管的照片。我有5个不同颜色的管子,每个代表5个级别中的1个。我想拍摄其他样品的照片,并在计算机上通过比较颜色来评估该样品属于哪个级别,并且我还想知道其近似百分比。我希望程序能够像这样实现:http://www.colortools.net/color_matcher.html 如果您能告诉我需要采取哪些步骤,即使这些步骤是我需要手动思考和执行的事情,也将非常有帮助。

2
我对文本进行了微小的更改,将一个葡萄牙语单词更改为我认为是正确的英语等效词...如果我犯了错误,请将其更改回来。 - Beska
14
有一篇关于颜色差异的维基百科文章:http://en.wikipedia.org/wiki/Color_difference - Ocaso Protal
5
这应该很有趣:http://stevehanov.ca/blog/index.php?id=116它探讨了在三种不同的颜色模型中计算差异的方法。 - Vlad
一些用于颜色对比度检测的不错算法:http://www.splitbrain.org/blog/2008-09/18-calculating_color_contrast_with_php - Kevin
也许Lea Verou的游戏能够帮到你。 - Laurent S.
显示剩余5条评论
20个回答

3
我尝试了多种方法,如LAB色彩空间、HSV比较等等,发现亮度对于这个目的非常有效。以下是Python版本。
def lum(c):
    def factor(component):
        component = component / 255;
        if (component <= 0.03928):
            component = component / 12.92;
        else:
            component = math.pow(((component + 0.055) / 1.055), 2.4);

        return component
    components = [factor(ci) for ci in c]

    return (components[0] * 0.2126 + components[1] * 0.7152 + components[2] * 0.0722) + 0.05;

def color_distance(c1, c2):

    l1 = lum(c1)
    l2 = lum(c2)
    higher = max(l1, l2)
    lower = min(l1, l2)

    return (higher - lower) / higher


c1 = ImageColor.getrgb('white')
c2 = ImageColor.getrgb('yellow')
print(color_distance(c1, c2))

将会给你:
0.0687619047619048

ImageColor 的起源是什么?编辑 我找到了,它是 from PIL import ImageColor - ademar111190
亮度不就是颜色的亮度吗?因此,在这种情况下,只要亮度相同,绿色、蓝色和红色就不会被报告为不同的颜色了吗? - Peter B.

3

Android ColorUtils API RGBToHSL: 我有两个int型的argb颜色(color1,color2),希望获取两种颜色之间的距离/差异。我所做的是:

private float getHue(int color) {
    int R = (color >> 16) & 0xff;
    int G = (color >>  8) & 0xff;
    int B = (color      ) & 0xff;
    float[] colorHue = new float[3];
    ColorUtils.RGBToHSL(R, G, B, colorHue);
    return colorHue[0];
}

我使用以下代码来查找两种颜色之间的距离。

private float getDistance(getHue(color1), getHue(color2)) {
    float avgHue = (hue1 + hue2)/2;
    return Math.abs(hue1 - avgHue);
}

2

我在我的安卓设备中使用了这个,虽然不建议使用RGB颜色空间,但似乎还算满意:

    public double colourDistance(int red1,int green1, int blue1, int red2, int green2, int blue2)
{
      double rmean = ( red1 + red2 )/2;
    int r = red1 - red2;
    int g = green1 - green2;
    int b = blue1 - blue2;
    double weightR = 2 + rmean/256;
    double weightG = 4.0;
    double weightB = 2 + (255-rmean)/256;
    return Math.sqrt(weightR*r*r + weightG*g*g + weightB*b*b);
}

然后我使用以下方法来获取相似度的百分比:
double maxColDist = 764.8339663572415;
double d1 = colourDistance(red1,green1,blue1,red2,green2,blue2);
String s1 = (int) Math.round(((maxColDist-d1)/maxColDist)*100) + "% match";

它的表现还不错。


最后一行可以简化为Math.round(100 - d1 / 7.648339663572415)。 - MysteryPancake

2
最好的方法是使用deltaE。DeltaE是一个显示颜色差异的数字。如果deltae < 1,则人眼无法识别差异。我在canvas和js中编写了一段代码,用于将RGB转换为LAB,然后计算delta e。在此示例中,该代码识别具有与我保存为LAB1的基准颜色不同颜色的像素,然后如果它们不同,则使这些像素变为红色。您可以通过增加或减少可接受的delta e范围来增加或降低颜色差异的灵敏度。在我编写的行(deltae <= 10)中,我将deltaE分配为10:
<script>   
  var constants = {
    canvasWidth: 700, // In pixels.
    canvasHeight: 600, // In pixels.
    colorMap: new Array() 
          };



  // -----------------------------------------------------------------------------------------------------

  function fillcolormap(imageObj1) {


    function rgbtoxyz(red1,green1,blue1){ // a converter for converting rgb model to xyz model
 var red2 = red1/255;
 var green2 = green1/255;
 var blue2 = blue1/255;
 if(red2>0.04045){
      red2 = (red2+0.055)/1.055;
      red2 = Math.pow(red2,2.4);
 }
 else{
      red2 = red2/12.92;
 }
 if(green2>0.04045){
      green2 = (green2+0.055)/1.055;
      green2 = Math.pow(green2,2.4);    
 }
 else{
      green2 = green2/12.92;
 }
 if(blue2>0.04045){
      blue2 = (blue2+0.055)/1.055;
      blue2 = Math.pow(blue2,2.4);    
 }
 else{
      blue2 = blue2/12.92;
 }
 red2 = (red2*100);
 green2 = (green2*100);
 blue2 = (blue2*100);
 var x = (red2 * 0.4124) + (green2 * 0.3576) + (blue2 * 0.1805);
 var y = (red2 * 0.2126) + (green2 * 0.7152) + (blue2 * 0.0722);
 var z = (red2 * 0.0193) + (green2 * 0.1192) + (blue2 * 0.9505);
 var xyzresult = new Array();
 xyzresult[0] = x;
 xyzresult[1] = y;
 xyzresult[2] = z;
 return(xyzresult);
} //end of rgb_to_xyz function
function xyztolab(xyz){ //a convertor from xyz to lab model
 var x = xyz[0];
 var y = xyz[1];
 var z = xyz[2];
 var x2 = x/95.047;
 var y2 = y/100;
 var z2 = z/108.883;
 if(x2>0.008856){
      x2 = Math.pow(x2,1/3);
 }
 else{
      x2 = (7.787*x2) + (16/116);
 }
 if(y2>0.008856){
      y2 = Math.pow(y2,1/3);
 }
 else{
      y2 = (7.787*y2) + (16/116);
 }
 if(z2>0.008856){
      z2 = Math.pow(z2,1/3);
 }
 else{
      z2 = (7.787*z2) + (16/116);
 }
 var l= 116*y2 - 16;
 var a= 500*(x2-y2);
 var b= 200*(y2-z2);
 var labresult = new Array();
 labresult[0] = l;
 labresult[1] = a;
 labresult[2] = b;
 return(labresult);

}

    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var imageX = 0;
    var imageY = 0;

    context.drawImage(imageObj1, imageX, imageY, 240, 140);
    var imageData = context.getImageData(0, 0, 240, 140);
    var data = imageData.data;
    var n = data.length;
   // iterate over all pixels

    var m = 0;
    for (var i = 0; i < n; i += 4) {
      var red = data[i];
      var green = data[i + 1];
      var blue = data[i + 2];
    var xyzcolor = new Array();
    xyzcolor = rgbtoxyz(red,green,blue);
    var lab = new Array();
    lab = xyztolab(xyzcolor);
    constants.colorMap.push(lab); //fill up the colormap array with lab colors.         
      } 

  }

// -----------------------------------------------------------------------------------------------------


// -----------------------------------------------------------------------------------------------------

    function colorize(pixqty) {

         function deltae94(lab1,lab2){    //calculating Delta E 1994

         var c1 = Math.sqrt((lab1[1]*lab1[1])+(lab1[2]*lab1[2]));
         var c2 =  Math.sqrt((lab2[1]*lab2[1])+(lab2[2]*lab2[2]));
         var dc = c1-c2;
         var dl = lab1[0]-lab2[0];
         var da = lab1[1]-lab2[1];
         var db = lab1[2]-lab2[2];
         var dh = Math.sqrt((da*da)+(db*db)-(dc*dc));
         var first = dl;
         var second = dc/(1+(0.045*c1));
         var third = dh/(1+(0.015*c1));
         var deresult = Math.sqrt((first*first)+(second*second)+(third*third));
         return(deresult);
          } // end of deltae94 function
    var lab11 =  new Array("80","-4","21");
    var lab12 = new Array();
    var k2=0;
    var canvas = document.getElementById('myCanvas');
                                        var context = canvas.getContext('2d');
                                        var imageData = context.getImageData(0, 0, 240, 140);
                                        var data = imageData.data;

    for (var i=0; i<pixqty; i++) {

    lab12 = constants.colorMap[i];

    var deltae = deltae94(lab11,lab12);     
                                        if (deltae <= 10) {

                                        data[i*4] = 255;
                                        data[(i*4)+1] = 0;
                                        data[(i*4)+2] = 0;  
                                        k2++;
                                        } // end of if 
                                } //end of for loop
    context.clearRect(0,0,240,140);
    alert(k2);
    context.putImageData(imageData,0,0);
} 
// -----------------------------------------------------------------------------------------------------

$(window).load(function () {    
  var imageObj = new Image();
  imageObj.onload = function() {
  fillcolormap(imageObj);    
  }
  imageObj.src = './mixcolor.png';
});

// ---------------------------------------------------------------------------------------------------
 var pixno2 = 240*140; 
 </script>

1
我有点担心你的整数除法。1/316/116 都计算为 0,这几乎肯定不是你想要的结果。可能你的算法是正确的,但你的代码肯定不对。 - Dawood ibn Kareem
您正在描述CIE-LAB dE94。Delta E意味着欧几里得距离的变化。也就是说,在标准的Lab颜色空间中,由您非常标准的欧几里得距离公式给出的欧几里得距离。而Delta E的修改,即76、94、2000(还有用于纺织品等的Delta E,CMC)是Lab颜色空间内位置之间不同的距离公式。每个Lab的代码都是相同的,但颜色差异的代码不同。简而言之,Delta E并不是那个被称为的东西。 - Tatarize

1

我猜你最终想要分析整张图片,对吧?这样你就可以检查与身份颜色矩阵之间的最小/最大差异。

大多数用于处理图形的数学运算使用矩阵,因为使用它们的可能算法通常比经典的逐点距离和比较计算更快。(例如,用于使用DirectX、OpenGL等操作)

所以我认为你应该从这里开始:

http://en.wikipedia.org/wiki/Identity_matrix

http://en.wikipedia.org/wiki/Matrix_difference_equation

...正如Beska在上面的评论中所说:

这可能不会给出最好的“可见”差异...

这也意味着,如果您正在处理图像,则算法取决于您对“相似”的定义。


0

你需要将任何RGB颜色转换为Lab颜色空间,以便能够以人类看到的方式进行比较。否则,你将得到一些非常奇怪的RGB颜色“匹配”。

维基百科上关于颜色差异的链接介绍了多年来定义的各种Lab颜色空间差异算法。最简单的只检查两个lab颜色的欧几里得距离的算法是有效的,但有一些缺陷。

方便的是,在OpenIMAJ项目中有一个更复杂的CIEDE2000算法的Java实现。提供你的两组Lab颜色,它会给你返回单个距离值。


0

比较颜色的唯一“正确”方法是使用CIELab或CIELuv中的deltaE。

但对于许多应用程序,我认为这是一个足够好的近似:

distance = 3 * |dR| + 4 * |dG| + 3 * |dB|

我认为在比较颜色时,加权曼哈顿距离更有意义。请记住,颜色原色只存在于我们的头脑中,它们没有任何物理意义。 CIELab和CIELuv是从我们对颜色的感知进行统计建模的。


0

如果想要快速且简单的话,可以这样做

import java.awt.Color;
private Color dropPrecision(Color c,int threshold){
    return new Color((c.getRed()/threshold),
                     (c.getGreen()/threshold),
                     (c.getBlue()/threshold));
}
public boolean inThreshold(Color _1,Color _2,int threshold){
    return dropPrecision(_1,threshold)==dropPrecision(_2,threshold);
}

利用整数除法对颜色进行量化。


0

Swift 5答案

我发现这个线程是因为我需要一个Swift版本的这个问题。由于没有人回答解决方案,这里是我的解决方案:

extension UIColor {

    var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        getRed(&red, green: &green, blue: &blue, alpha: &alpha)

        return (red, green, blue, alpha)
    }

    func isSimilar(to colorB: UIColor) -> Bool {
        let rgbA = self.rgba
        let rgbB = colorB.rgba

        let diffRed = abs(CGFloat(rgbA.red) - CGFloat(rgbB.red))
        let diffGreen = abs(rgbA.green - rgbB.green)
        let diffBlue = abs(rgbA.blue - rgbB.blue)

        let pctRed = diffRed
        let pctGreen = diffGreen
        let pctBlue = diffBlue

        let pct = (pctRed + pctGreen + pctBlue) / 3 * 100

        return pct < 10 ? true : false
    }
}

使用方法:

let black: UIColor = UIColor.black
let white: UIColor = UIColor.white

let similar: Bool = black.isSimilar(to: white)

我设置少于10%的差异以返回类似的颜色,但您可以自定义此内容。


0
虽然已经有一些非常好的答案了,但这里有一个应用程序将很多东西整合在一起 - 它可以为任何RGB十六进制字符串提供名称。
不用说,__main只是一个演示者。
import sys
import matplotlib.colors as mc
import numpy as np
from scipy.spatial import KDTree
import cv2

class ColourNamer:
    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]

    def hex_to_rgb(self, value):  # return normative rgb tuple.
        value = value.lstrip('#')
        lv = len(value)  #6 = 8 bit, 12 = 16 bit.
        ints = tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
        return tuple(a / 65536 if lv == 12 else a/256 for a in ints)

if __name__ == '__main__':
    cn = ColourNamer()
    if len(sys.argv) > 0:
        hex_c = sys.argv[1]
        print(cn.name(cn.hex_to_rgb(hex_c)))

例如
$python colour.py #fe3288
strong pink

$python colour.py #134323
evergreen

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