修改颜色以使其与背景不太相似的算法


23

我正在创建一个演示示例,其中有20个球互相弹跳,背景填充有纯色。每个球的颜色是randint(0, 255)针对每个R,G,B元组分量随机选择的。

问题是某些球最终的颜色与背景非常相似,因此很难看到它们。我想避免这种情况,如果选择的颜色太相似(在给定的阈值范围内),则滚动另一种颜色,或者将其移离背景颜色。

如何计算相似度指标用作阈值?或者,如何变换颜色使其变淡(例如,偏蓝)

明显的免责声明:我对色彩理论一无所知,所以我不知道上述概念是否存在!

我正在用python进行编码,但是我正在寻找概念和策略,因此伪代码很好(或者只是理论上)。

编辑:

要澄清几点:

  • 每个球都有自己的随机颜色。我希望它们尽可能地保持随机性,并尽可能地探索色彩空间(无论是RGB还是HSV,都pygame接受两种格式来定义色彩)

  • 我只是想避免每种颜色与背景“太接近”。问题不仅仅在于色调:我完全可以在深蓝色背景下使用浅蓝色球(或者在“白色”蓝色下使用“全蓝色”,等等)

  • 现在,我不在乎一个球的颜色是否与另一个球相似(甚至相同)。避免这种情况是有好处的,但这是一个小问题。

  • 背景色是恒定的单一RGB颜色。目前,我使用的是“基本”蓝色(0, 0, 255),但我对此还不太满意。因此,请考虑背景为任意颜色(具有任意色调,亮度,饱和度等)。

编辑2:

TL,DR版本:只是提供一种创建X种随机颜色的方法,这样就不会在任意(但单一和恒定)颜色的背景下“淡入淡出”



1
为什么不只是在每个球周围画一个轮廓呢?
ashes999 2014年

1
然后可能会使轮廓颜色与背景颜色和球形颜色不同;)
Kromster说支持Monica 2014年

1
@AlexM .:难以相信没有简单的解决方案来“生成避免任意颜色的随机颜色”
MestreLion 2014年

1
@MestreLion仅使用黑色作为轮廓,并生成最小RGB值为128的颜色。就这么简单。
ashes999

Answers:


33

您可以利用颜色构成一个(三维)颜色空间并计算该颜色空间中的距离这一事实。然后,您需要在此颜色空间中定义一个指标,以找到两种颜色之间的距离。

例如,两点之间的欧氏空间中的距离x =(x1,x2,x3)和y =(y1,y2,y3)由d(x,y)= sqrt((y1-x1)*(y1- x1)+(y2-x2)*(y2-x2)+(y3-x3)*(y3-x3))。

现在,您可以计算球形颜色和背景颜色之间的距离,如果它太小,请创建新的随机颜色,然后再次进行测试。

您会发现RGB和HSV都不是完成此任务的良好色彩空间。所述上色差维基百科文章采用大号一个 b颜色空间,并给出几种可能的度量标准。

关于该主题的stackoverflow也有一个讨论。


那是一个非常好的方法,我可以做到:)谢谢!此外,还要详细说明如何找到适当的“最小距离”阈值?如果RGB不足,如何转换为L a b?我并不是在寻求准确的感知指标,因此任何“足够好”的默认设置都可以。
MestreLion 2014年

1
喜欢这个答案,经过深思熟虑,很好地解释了如何完成任务,同时提供了所需的方程式和参考资料。好的第一答案。
2014年

1
如果您正在寻找人类感知的“足够好”的近似值,则可能需要研究YUV颜色空间,它是直接从RGB转换的。
trm 2014年

5
节省一些时间,不要求平方根(昂贵)。只需将您的最小颜色距离值与正方形进行比较即可。
克里斯·库德莫

1
@MestreLion 发明了L a b *颜色空间在感知上是统一的,这意味着两种颜色之间的感知距离与颜色空间中的实际距离相同。由于RGB颜色取决于设备,因此您无法在Lab和RGB之间直接转换。但是,如果使用特定的绝对RGB颜色空间,则可以对其进行转换。看到这篇文章:en.wikipedia.org/wiki/SRGB_color_space和这篇文章:en.wikipedia.org/wiki/Lab_color_space
jwir3 2014年

8

联合国网络可访问性标准页面(http://www.un.org/webaccessibility/1_visual/13_colourcontrast.shtml)确实指出了确保网站上正确文本对比的标准,同时请记住,有些用户是色盲的。这对您可能尤其重要,因为您的某些用户可能也会色盲(如果亮度相同,很难从绿色背景分辨出红色的球)。

联合国页面指向位于(http://juicystudio.com/services/luminositycontrastratio.php#specify)上的Juicy Studios光度颜色对比度分析器,该报告指出背景上的文本必须具有4.5:1的对比度,除非我相信与您的情况类似:

  • 大文本:大文本和大文本图像的对比度至少为3:1

现在什么是对比度?进一步跟随超链接(这很有趣!),我们进入W3页面,该页面将对比度定义为:

Contrast Ratio = (L1 + 0.05)/(L2 + 0.05)

其中L1和L2是颜色的相对亮度。L1是发光体颜色越多,L2是发光体颜色越。同样,我们点击链接到同一W3页面,该页面指定:

L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:

if RsRGB <= 0.03928 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4
and RsRGB, GsRGB, and BsRGB are defined as:

RsRGB = R8bit/255
GsRGB = G8bit/255
BsRGB = B8bit/255

注意:插入符号上面 ^表示幂(x²)

我们有一个很好的对比算法框架(对色盲友好!)

  • 确定每种颜色的R,G,B值
  • 确定每种颜色的亮度
  • 如果更多发光体颜色与最小发光体颜色的对比度小于3,请重新滚动!

2
可能要注意,^应该是幂。对于像C这样的疯狂用户来说,这意味着xor(我真的以为您试图在一分钟内对浮点值进行按位运算)。
法拉普

这是一种了不起的方法,我喜欢!:)
MestreLion 2014年

不用担心^,在这种情况下,我永远不会考虑按位异或。(或实际上任何C.mon C phreaks,^都是伪代码求幂的事实上的simbol(尽管很少有语言在python中实际使用它…… **
MestreLion

1
只是拼写错误。另外,如果一个人对^有麻烦,将来将来也有可能有人。具体来说并没有什么坏处。
约翰

1
@John不用担心,。我不确定混淆的根源(即哪种语言首先使用^出于何种目的)。我只知道^由于我在VB上的经验而有时用于取幂(我不知道有其他语言将它用作取幂,但可能有一些语言)。就像单挑一样,它是插入符号,而不是胡萝卜符号。在有人对蔬菜编程(尤其是涉及平方根的编程)开个可怕的笑话之前,可能需要更改它。
法拉普2014年

2

通过生成随机值使用RGB值 0-255为每个组件值来不仅可以创建与背景相似的颜色,而且还可以为球提供非常相似的颜色。

我可能会选择一种不太随机的方法,并创建保证不同的颜色。您可以在色相范围内为每个〜32度创建一种颜色,并改变亮度或饱和度(使用HSV颜色模型而不是RGB)。

所以像这样:

numColors = 20;
stepSize = 360 / (numColors / 2 + 1); // hue step size
for(i = 0; i < numColors; i++){
    color = HSV(i / 2 * stepSize, 0.5 + (i % 2) * 0.5, 1.0) 
}

它将创建20种颜色,分布均匀。这里假设你正在使用整数运算,所以i = 0i = 1会产生相同的色调值。

当然,这不会很好地缩放。.如果您要创建100个颜色值,则色相间距可能不够,您将再次获得相似的颜色。在这种情况下,您还可以更改V(亮度)值。

我不知道您的背景色是什么样,但是HSV使得比较颜色也更加容易(只需比较这三个分量,不要忘记HUE是圆形的(0 == 360))。

更新:

由于我并未真正回答您的问题,而是提供了另一种方法,因此以下是针对任意背景和完全随机的彩色球的算法的外观(这是一种蛮力方法,但在您的用例中应该可以正常工作):

// background color, currently equals RGB(0, 0, 255)
bg = HSV(240, 1.0, 1.0)

// number of colors to create is equal to the amount of balls
numColors = balls.length

// set threshold to compare colors. you can tweak this to your liking
threshold = 0.15

for(i = 0; i < numColors; i++){
    do {
        // assuming rnd returns a random float 0-1 inclusive
        color = HSV(rnd() * 360, rnd(), rnd())
        // calculate delta values, normalize hue to 0-1
        dH = (180 - abs(abs(bg.h - color.h) - 180)) / 360
        dS = bg.s - color.s
        dV = bg.v - color.v
        // "distance" between bg and color
        difference = sqrt(dH * dH + dS * dS + dV * dV)
    } while(difference < threshold)

    ball[i].color = color
}

关于您的算法,如何考虑背景色?
MestreLion 2014年

当前它没有考虑背景颜色...您可以通过比较生成的颜色(如上一段所述)来对其进行调整,然后创建具有不同颜色的颜色V(因为当前算法不会对此进行更改)。
bummzack 2014年

@MestreLion添加了具有替代算法的更新
bummzack 2014年

太好了,这似乎是个不错的策略。蛮力还可以,颜色只会在初始化时生成一次,而不是在游戏循环中生成。似乎您使用的是与René相同的方法,在HSV中具有简单的欧几里德距离,对吗?是否有合适的HSV阈值参考?H,S和V的权重与RGB一样远吗?
MestreLion,2014年

@MestreLion是的,我的第二种算法几乎是René在回答中解释的。遗憾的是,对于良好的阈值并没有确定的值,因为不能均匀地感知颜色变化。我们认识到绿色的变化要快于蓝色的变化...因此,对于绿色色调,较小的阈值可能适用,但对于其他颜色等而言还不够。如果您无法通过HSV色彩空间获得理想的结果,则可能最适合。
bummzack

1

既然您提到您的背景是固定的,则球的颜色仍然可以是随机的,但是它们必须落在一定范围内,仍然与背景互补。

基本。在我们这样做之前,您需要了解基础知识。考虑以下颜色:

Black   #000000 rgb(0,0,0)
Red     #FF0000 rgb(255,0,0)
Green   #00FF00 rgb(0,255,0)
Blue    #0000FF rgb(0,0,255)
Yellow  #FFFF00 rgb(255,255,0)
Cyan    #00FFFF rgb(0,255,255)
Pink    #FF00FF rgb(255,0,255)
Gray    #C0C0C0 rgb(192,192,192)
White   #FFFFFF rgb(255,255,255)

颜色混合RGB [(0..255),(0..255),(0..255)]会如上所述创建新颜色。

计算负色计算负色就像将红色转换为青色,将绿色转换为紫色,将蓝色转换为黄色一样。

Red     #FF0000 rgb(255,0,0) ->     Cyan    #00FFFF rgb(0,255,255)
Green   #00FF00 rgb(0,255,0) ->     Purple   #FF00FF    rgb(255,0,255)
Blue    #0000FF rgb(0,0,255) ->     Yellow  #FFFF00 rgb(255,255,0)

互补色

根据有关计算补色的参考:http : //serennu.com/colour/rgbtohsl.php

关于HSL

HSL用色相,饱和度和亮度来表示颜色,并为颜色的这三个属性分别指定一个数字。

色相是指色轮在色轮上的位置,以0°至359°的度数表示,代表色轮的360°;0°是红色,180°是红色的与青色相反的颜色,依此类推。

饱和度是颜色的强度,它是多么暗淡或明亮。饱和度越低,颜色看起来越暗(灰色)。它以百分比表示,其中100%为完全饱和(最亮),0%为不饱和(灰色)。

亮度是颜色的亮度。与饱和度略有不同。颜色越白,其“亮度”值越高,则黑越多,其“亮度”越低。因此100%亮度将颜色变成白色,0%亮度将颜色变成黑色,而“纯”颜色将是50%亮度。

看到“饱和度”和“亮度”之间的区别比解释它容易得多。如果要澄清,请尝试在颜色计算器页面上查看“亮度”和“饱和度”变化,选择相当明亮的颜色作为起始色。

因此,HSL表示法看起来像这样,按以下顺序给出了“色相”,“饱和度”和“亮度”值:

红色:0°100%50%浅粉色:0°100%90%青色:180°100%50%以下是步骤:

  1. 将您的颜色转换为HSL。

  2. 将“色相”值更改为相反的色相值(例如,如果您的“色相”为50°,则相反的色相将在方向盘上为230°,在更远处为180°)。

  3. 保持“饱和度”和“亮度”值不变。

  4. 将此新的HSL值转换回原始颜色表示法(RGB或其他颜色)。

EasyRGB.com等网站可以为您进行从RGB到HSL的通用转换,反之亦然。

程式设计根据参考在PHP中完成的示例

从RGB转换为HSL

蓝色#0000FF rgb(0,0,255)之上的值可以表示为红色十六进制00 +绿色十六进制00 +蓝色十六进制FF

$redhex  = substr($hexcode,0,2);
$greenhex = substr($hexcode,2,2);
$bluehex = substr($hexcode,4,2);

也可以表示为红色十进制0 +绿色十进制0 +蓝色十进制255

$var_r = (hexdec($redhex)) / 255;
$var_g = (hexdec($greenhex)) / 255;
$var_b = (hexdec($bluehex)) / 255;

现在,将这些值插入到rgb2hsl例程中。以下是我的EasyRGB.com通用代码的PHP版本:

输入是上面的输入$ var_r,$ var_g和$ var_b输出是等效于$ h,$ s和$ l的HSL-它们再次表示为1的分数,如输入值

$var_min = min($var_r,$var_g,$var_b);ttt
$var_max = max($var_r,$var_g,$var_b);
$del_max = $var_max - $var_min;

$l = ($var_max + $var_min) / 2;

if ($del_max == 0)
{
        $h = 0;
        $s = 0;
}
else
{
        if ($l < 0.5)
        {
                $s = $del_max / ($var_max + $var_min);
        }
        else
        {
                $s = $del_max / (2 - $var_max - $var_min);
        };

        $del_r = ((($var_max - $var_r) / 6) + ($del_max / 2)) / $del_max;
        $del_g = ((($var_max - $var_g) / 6) + ($del_max / 2)) / $del_max;
        $del_b = ((($var_max - $var_b) / 6) + ($del_max / 2)) / $del_max;

        if ($var_r == $var_max)
        {
                $h = $del_b - $del_g;
        }
        elseif ($var_g == $var_max)
        {
                $h = (1 / 3) + $del_r - $del_b;
        }
        elseif ($var_b == $var_max)
        {
                $h = (2 / 3) + $del_g - $del_r;
        };

        if ($h < 0)
        {
                $h += 1;
        };

        if ($h > 1)
        {
                $h -= 1;
        };
};

因此,现在我们在变量$ h,$ s和$ l中将颜色作为HSL值。在此阶段,这三个输出变量再次保持为1的分数,而不是度和百分比。因此,例如,青色(180°100%50%)将显示为$ h = 0.5,$ s = 1和$ l = 0.5。

接下来找到相反色相的值,即相距180°或0.5的色相(我敢肯定,数学家有一种更优雅的方法做到这一点,但是):

计算相反的色相$ h2

            $h2 = $h + 0.5;

            if ($h2 > 1)
                    {
                            $h2 -= 1;
                    };

补色的HSL值现在为$ h2,$ s,$ l。因此,我们已经准备好将其转换回RGB(同样,这是EasyRGB.com公式的PHP版本)。请注意,这次输入和输出格式有所不同,请参见代码顶部的注释:

输入是补色的HSL值,以$ h2,$ s,$ l的1的分数保存。输出是标准255 255 255格式的RGB,以$ r,$ g,$ b保留。使用功能hue_2_rgb转换色相,如下所示在此代码的末尾

    if ($s == 0)
    {
            $r = $l * 255;
            $g = $l * 255;
            $b = $l * 255;
    }
    else
    {
            if ($l < 0.5)
            {
                    $var_2 = $l * (1 + $s);
            }
            elset
            {
                    $var_2 = ($l + $s) - ($s * $l);
            };

            $var_1 = 2 * $l - $var_2;
            $r = 255 * hue_2_rgb($var_1,$var_2,$h2 + (1 / 3));
            $g = 255 * hue_2_rgb($var_1,$var_2,$h2);
            $b = 255 * hue_2_rgb($var_1,$var_2,$h2 - (1 / 3));
    };

   // Function to convert hue to RGB, called from above

    function hue_2_rgb($v1,$v2,$vh)
    {
            if ($vh < 0)
            {
                    $vh += 1;
            };

            if ($vh > 1)
            {
                    $vh -= 1;
            };

            if ((6 * $vh) < 1)
            {
                    return ($v1 + ($v2 - $v1) * 6 * $vh);
            };

            if ((2 * $vh) < 1)
            {
                    return ($v2);
            };

            if ((3 * $vh) < 2)
            {
                    return ($v1 + ($v2 - $v1) * ((2 / 3 - $vh) * 6));
            };

            return ($v1);
    };

在执行完该例程后,我们终于有了255255255(RGB)格式的$ r,$ g和$ b,可以将其转换为六位数的十六进制:

    $rhex = sprintf("%02X",round($r));
    $ghex = sprintf("%02X",round($g));
    $bhex = sprintf("%02X",round($b));

    $rgbhex = $rhex.$ghex.$bhex;

$ rgbhex是我们的答案-十六进制中的互补色。

由于您的颜色背景是蓝色或0,0,255,因此HSL为

色相(H):240度/饱和度(S):100%/亮度(L):4.9%

240的对面是60,然后转回RGB,则值为#181800


谢谢!但是该链接谈论的是自身产生“互补色”(无论是哪种颜色)。对于如何使用它来解决问题,我仍然一无所知:请确保每种随机颜色与背景颜色都不相似。背景颜色是恒定的,单色,并且有多个球。
MestreLion 2014年

感谢Krom现在这样做。@MestreLion,由于背景是恒定的,因此您可以为球指定一个范围,该范围仍与背景互补。
Ace Caserya 2014年

目前正在解释我的答案。
Ace Caserya 2014年

做完了 我对网站的说明没有太大的更改,因为它解释得足够好。积分的家伙在EasyRGB.com
王牌Caserya


1

我想扩展@ ashes999创建轮廓的想法。

我的代码将尽可能地像python一样(我一生中从未写过python的代码,但是我看过一两次书)。

def lerp(a,b,value): # assuming your library was not kind enough to give you this for free
    return a + ((b - a) * value);

def drawRandomBall(bgColour,radius,point):
    var ballColour = Colour(random(0,255),random(0,255),random(0,255);
    var outlineColour = Colour(lerp(bgColour.R,255 - ballColour.R,0.5),lerp(bgColour.G,255 - ballColour.G,0.5),lerp(bgColour.B,255 - ballColour.B,0.5));
    fillCircle(point,radius+1,outlineColour);
    fillCircle(point,radius,ballColour);

它看起来可能并不特别漂亮,并且对于生产代码而言并不是很理想,但是作为快速修复程序就足够了。我还没有测试代码,因为我没有一个“球绕着屏幕弹跳”的程序,可以用来测试它。


编辑:

看到实际使用的代码后,情况突然发生了变化,因此我提供了此解决方案。

将276-284行替换为以下内容:

# Colour generator
def generateColourNonBG(leeway):
    color = (randint(0,255), randint(0,255), randint(0,255))
    while color[0] >= BG_COLOR[0]-leeway and color[0] =< BG_COLOR[0] + leeway and
    color[1] >= BG_COLOR[1]-leeway and color[1] =< BG_COLOR[1] + leeway and
    color[2] >= BG_COLOR[2]-leeway and color[2] =< BG_COLOR[2] + leeway:
        color = (randint(0,255), randint(0,255), randint(0,255))

# Ball generator
def generateBall():
    Ball(generateColourNonBG(5),
                   radius=randint(10, radius),
                   position=[randint(100, screen.get_size()[0]-radius),
                             randint(100, screen.get_size()[0]-radius)],
                   velocity=[randint(50, 50+vel[0]), randint(50, 50+vel[1])],
                   )                       

# Create the balls
balls = pygame.sprite.Group()
for _ in xrange(BALLS):
    balls.add(generateBall())

在这个新的简化版本中,工厂方法会喷出球,而专业方法会产生颜色。颜色生成器测试随机生成的颜色,以查看它是否在与背景颜色接近的特定范围内。如果一种颜色的所有三个颜色通道都在leewayBG的任一侧,则认为该颜色与BG颜色过于接近。因此,如果leeway为0,则仅拒绝与BG完全匹配的颜色。我选择5作为默认值,它拒绝BG颜色,并且拒绝任一侧的5种颜色。由于BG颜色是白色,因此我不确定是否会拒绝黑色,因为数字会环绕。可以通过使用min和max函数来避免这种情况,但是为了简洁起见,我省略了它们。


我对任何策略都持开放态度 :)至于要残废的程序,我可以给你:github.com/MestreLion/rainballs相关行是279-284
MestreLion 2014年

我正在pygame用作图书馆。没有Lerp,只有基本的RGB-HSV-CYMK转换: pygame.org/docs/ref/color.html
MestreLion 2014年

@MestreLion哦,洛迪,现在我必须安装pygame和python进行测试。我已经用这个XP击中了脚。Lerping很简单,lerp我的答案中的功能就是它的全部。(Lerp表示“线性插值”)。
法拉普2014年

@MestreLion摆弄了一下,结果我的python版本不喜欢在没有括号的情况下调用print。修复了这个问题,但是现在我从pygame遇到错误,无法找到正确的库。我将重写代码以适应您所拥有的内容,但是直到修复pygame后我才能对其进行测试。如果您还没有的话,我建议将该文件追加到问题中(以防万一有人感觉很好,可以为您编写一些python)。
法拉普2014年

print有所不同,因为您使用的是Python 3,而我仍在使用Python2。而且我不确定是否pygame已移植到Py3。底线:不要打扰:)问题是关于颜色的,您不需要实际的球反弹:)
MestreLion 2014年

1

对于RGB格式,这似乎可以很好地工作并且很简单:

Diversity_score =(abs(r1-r2)+ abs(g1-g2)+ abs(b1-b2))/ 765.0

这将为您提供0.0到1.0之间的分数。越低,区分两种颜色就越难。

这应该很明显,但是为了完整性:必须先将r,g,b值强制转换为浮点数,并假定其最大值为255。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.