有没有一种像混合真实颜色一样工作的颜色混合算法?

81
常见的RGB颜色混合与绘画中颜色混合非常不同,它是光线混合而不是颜料混合。
例如:
Blue (0,0,255) + Yellow (255,255,0) = Grey (128,128,128)

(应该是蓝色 + 黄色 = 绿色)

是否有已知的颜色混合算法,可以像混合真实颜色一样工作?


我的方法

我已经尝试过以下方法:

将两种颜色转换为HSV,并混合色相(乘以从饱和度计算出的系数),对于饱和度和值通道使用简单平均值。然后,我从两种颜色中计算出平均亮度,并调整结果颜色以匹配此亮度。这个方法效果不错,但是有时色相混合会出现问题,例如:

Red (Hue 0°) + Blue (Hue 240°) = Green (Hue 120°)

我发现有时需要将色相值向360°移动(当色相之间的差异大于180°时)。

Red (Hue 360°) + Blue (Hue 240°) = Magenta/fuchsia (Hue 300°)

但是这种转变也不是很好,例如:

Cyan (Hue 179°) + Red (Hue 0°) = Hue 89.5°
Cyan (Hue 181°) + Red (Hue 0°) --> shifting is performed (the difference is greater than 180°)
Cyan (Hue 181°) + Red (Hue 360°) = Hue 270.5°

将(Hue 179+红色)和(Hue 181+红色)混合会得到两种完全不同的颜色。


然后我尝试了CIE Lab颜色空间(就像在Photoshop中使用的那样),它被设计成更接近人类感知颜色。我只是对应两个通道的简单平均值,但结果并不理想,例如,从蓝色(98,-16,93)和黄色(30,68,-112)中得到了粉色(64,26,-9.5)。这些系数来自Photoshop。也许如果我使用了一些不同于平均值的操作,它可能会起作用,但我不知道该怎么做。

CMYK也不起作用,结果与RGB或LAB相同。


看起来在这些颜色空间中,既不是简单的加性混合也不是减性混合能够产生自然的结果。


可用实现

Krita – 画家混合器

光栅图形编辑器Krita曾经有一个更加逼真的颜色混合的可用实现:http://commit-digest.org/issues/2007-08-12/ (Painterly mixer插件)。

他们称这是第一个使用Kubelka和Munk方程描述颜料行为的特殊技术的公共应用。

这里是Krita颜色混合的视频演示:https://www.youtube.com/watch?v=lyLPZDVdQiQ

Paper by FiftyThree

iOS上的Paper应用程序中也有FiftyThree开发的颜色混合article about color blending in the Paper app for iOS。他们描述了他们在这一领域创新和实验的方式,并展示了将蓝色和黄色混合成绿色的样本。然而,实际的过程或算法并没有被真正描述。

引用:

"在寻找一个好的混合算法时,我们最初尝试了在各种颜色空间中进行插值:RGB、HSV 和 HSL,然后是 CieLAB 和 CieLUV。结果令人失望," Chen 说道。"我们知道红色和黄色应该产生橙色,或者红色和蓝色应该制造紫色,但无论使用哪种颜色空间,都没有办法得到这些颜色。有一条工程学原则:做能够起作用的最简单的事情。我们已经尝试了最简单的方法,但它们感觉根本不对。"
看起来和 Krita 一样,Paper 实现了 Kubelka-Munk 模型:
"[...] Kubelka-Munk 模型对于每种颜色至少有六个值,包括每个 RGB 颜色的反射和吸收值。" FiftyThree 的联合创始人兼 CEO Georg Petschnigg 解释道:"虽然屏幕上颜色的外观可以用三维描述,但颜色的混合实际上是在一个六维空间中发生的。" Kubelka-Munk 论文使团队能够将审美问题转化为数学框架。
从所有这些信息来看,基于 Kubelka-Munk 模型的实现可能是未来的发展方向,并提供更接近真实的结果。
尽管看起来是一个复杂的过程,但我还没有看到很好的关于如何实现这样的东西的信息。

相关问题

这些问题是在此问题之后发布的,都与同一件事有关。

但它们中没有一个真正地回答了问题。


其他相关链接和资源


我认为这是一个重复的问题:https://dev59.com/1HRB5IYBdhLWcg3wFz4g - Ólafur Waage
2
http://www.youtube.com/watch?v=lyLPZDVdQiQ - Ólafur Waage
Krita画家式挖掘:https://github.com/KDE/krita/tree/7036146c5a75e53196a20a3e4232313d796de9d9/krita/plugins/extensions/painterlyframework - CyberFox
10个回答

30
< p>正确的答案是NO,因为没有关于"真实世界中的颜色混合"如何运作的正确的 工作模型。这个过程过于复杂和有条件限制,并不像我们在学校里学到的简单的红-蓝-黄那样(事实上需要化学、物理和生物学来解决)。

然而,简单的答案是:YES,使用减法混合而不是加法混合。

我们在小学里学习的颜色混合是基于颜料组合的形式,这是一种减法颜色混合(非常简单地说)。也就是说,我们添加的颜色越多,它就会变得更暗,因为每种颜料都会减少一点光线。

另一方面,几乎所有计算机颜色方案都是加法的,因为它们基于合并光波(非常简单地说),所以它们变得更亮,因为每种颜色都会增加一点光线。

RGB+方案在某种程度上是我们在大多数美国小学里学到的减法方案的加法补充(即RBY-)。然而,它们并不完全匹配,转换它们之间可能会很困难(现在正在研究...)


好的,如果您只想从RGB中的加法组合切换到减法组合,则可以使用以下反向贝叶斯类型公式来组合两种颜色:

NewColor.R = (Color1.R * Color2.R)/255
NewColor.G = (Color1.G * Color2.G)/255
NewColor.B = (Color1.B * Color2.B)/255

调整色轴极点的差异(从G到Y,然后返回G)要困难得多......


已经指出这会在示例问题中产生黑色,在真正的减色系统中技术上来说是正确的,但是,如果你想要更多的稀释/减色系统,你可以尝试这个:

NewColor.R = 255 - SQRT(((255-Color1.R)^2 + (255-Color2.R)^2)/2)
NewColor.G = 255 - SQRT(((255-Color1.G)^2 + (255-Color2.G)^2)/2)
NewColor.B = 255 - SQRT(((255-Color1.B)^2 + (255-Color2.B)^2)/2)
这将产生深灰色而不是黑色。但是,要获得黄色或任何接近的颜色,你仍然需要解决配色方案中极点对齐的问题。

这个公式在所提到的情况下会给出黑色,不是吗? - Dolphin
染料混合是减法的,但许多染料实际上并不是基本颜色。油漆混合不是减法的,但根据油漆的不同,可能是加法、减法或奇怪而古怪的任意组合。 - supercat
颜料混合可以是减法或平均法,这取决于“混合”所指的含义。它永远不可能是加法,因为颜料不能产生自己的光。(你可以用荧光和磷光来争论这一点,但这些不是主要讨论的内容)。 - RBarryYoung
我们学习的混合有点像CMYK,因为(如果你想一想)加一点“k”就可以得到红/蓝/黄。毫不奇怪这是打印机使用的颜色方案(据我所知)。 - user1086498
我用这些算法制作了一个简单的测试界面。它运行得相当不错! - albfan
显示剩余2条评论

6

颜色组合有两种不同的可能性:

  1. 加法混合(例如RGB)

  2. 减法混合(例如CMYK)

因此,在减法混合中,结果是您期望的,但没有蓝色,而是青色:

黄色+青色=绿色

一般来说,减法混合只是从白色中“拿走”(过滤),而加法混合则是从黑色中添加。 (减法的基础颜色与加法相反:红色->青色;绿色->洋红色;蓝色->黄色)

因此,如果您从白色屏幕开始应用滤镜:

min(白色(255,255,255),黄色(255,255,0),青色(0,255,255))=绿色(0,255,0)


如果我尝试将蓝色作为青色+洋红色(100%, 100%, 0, 0),当我将其与黄色混合时,它似乎不太真实和自然。 - Tom Pažourek
1
“黄色+蓝色=绿色”这个规则是一个误解。在减法颜色中,你必须混合青色和黄色才能得到蓝色。 - Peter Parker
CMYK 也不是“正确”的。它只是一个近似模型,就像 RBY- 一样。你可能会认为它比 RBY- 更“正确”。 - RBarryYoung
1
@RBarryYoung:CMYK模型只有在应用于有意制定使其相对准确的油墨或染料时才是准确的模型。相比之下,RGB加法合成无论颜色是否“纯净”,都相对准确。 - supercat

3
“Mixbox”是一种新的技术(2022年2月)在2分钟论文中展示。作者称其为“Mixbox”,这项技术是开源的(算法可以在GitHub上找到,但要注意许可证)。目前,Rebelle 5正在实现这种颜色混合技术,更多的程序可能会在不久的将来跟进。我认为它最强的一点是实现的简单性以及你可以轻松地使用它。据说他们的论文也写得很好。

谢谢!这看起来非常有前途。一旦我找到一点时间,我会试试它。 - Tom Pažourek

2

谢谢提供链接。我在我的问题中提到了Krita,只是找不到相关的源代码。但是真的是光波长驱动算法吗?蓝光的波长约为475纳米,红光约为650纳米。如何将波长组合以得到紫外线?它一定是使用比光波长更复杂的东西。我相信算法本身非常复杂,我仍在寻找至少简要的解释。 - Tom Pažourek
你需要回到2.4.x版本,因为似乎代码已被移除,因为它没有得到维护。 - Ben Holland
现在源代码已经被埋在历史记录中,可以在Github上找到:https://github.com/KDE/krita/tree/7036146c5a75e53196a20a3e4232313d796de9d9/krita/plugins/extensions/painterlyframework - CyberFox

1
想知道计算RGB值的反转是否可行。由于涉及到光线的减法,技术上可以通过简单的数学运算来计算减法部分。
例如青色+黄色
青色=0x00ffff 黄色=0xffff00
它们的反转是0xff0000和0x0000ff,意味着它们完全吸收了红色和蓝色光线。它们的1:1混合物应该吸收一半的红色和蓝色光(因为混合物的另一半仍然可以反射一些红色和蓝色光),这与(0xff0000 + 0x00ffff) / 2 = 0x7f007f一致。现在我们从0xffffff中减去该值,得到0x80ff80,即绿色!

青色是一种不同寻常的颜色。尝试将蓝色(0x0000ff)与黄色混合,你会得到灰色。但如果你在现实世界中用颜料混合这些完全相同的色调,你永远不会得到灰色。 - Tom Pažourek

1
一种进行RGB颜色的减法混合的方法是先将RGB颜色转换为光谱反射曲线。转换相对简单,一旦完成,您就可以真正地对反射曲线进行减法混合,然后将结果转换回RGB。还有一个类似的问题:stackoverflow.com/questions/10254022/,其中更详细地讨论了这个过程。

1
我认为你在合并色调方面的问题是通过将两个角度相加然后除以二来完成的。正如你所注意到的,结果通常没有意义。我认为你最好将角度转换为单位圆上的笛卡尔坐标,对它们进行平均,并找到结果点的角度(忽略大小)。

1
这样计算:青色(色调179°)+ 红色(色调0°)= 89.5°,而青色(色调181°)+ 红色(色调0°)= -89.5° = 270.5°。 - Tom Pažourek

1
我想补充一下,我在Python中创建了一个库,可以以一种省去了实现复杂插值和颜色系统转换函数的方式来处理这个问题。它被称为colorir

蓝色+黄色的例子:

PolarGrad(["0000ff", "ffff00"]).perc(0.5)

这句话的意思是:“给你:#00c5a1”。

enter image description here

在幕布后面,Colorir插值使用HCLuv色彩系统中的颜色,这是一种感知均匀的颜色系统。这不会模拟“真实”颜色混合,因为它不考虑真正的颜料的减法属性(即最终混合的颜色不会比原始颜色更暗)。但是,它将混合颜色,就像找到两种颜色视觉属性的几乎完全平均值一样。以下步骤都由 PolarGrad 类自动处理:
  1. 解释输入颜色的值(在本例中为十六进制rgb值"0000ff" - 蓝色和"ffff00" - 黄色)
  2. 将这些颜色转换为感知均匀的颜色坐标系(默认为HCLuv,但可以使用其他颜色空间)
  3. 从渐变中间采样颜色(0.5)
  4. 将采样的颜色转换回RGB或其他您可能想要的格式

1

如何像调色一样混合颜色

  1. 从所有的颜色中去除白色,保留白色部分和彩色部分
  2. 计算从颜色中去除的白色部分的RGB值的平均值
  3. 计算彩色部分的RGB值的平均值
  4. 从计算出的平均颜色值中去除白色
  5. 将去除的白色值减半并加入到平均颜色部分的绿色值中
  6. 添加计算出的平均白色部分并使其成为整数

Javascript 实现

// rgbColor is an array of colors,
// where each color is an array with the three color values (RGB, 0 to 255)
function mixRGB(rgbColors) {
        function sumColors(summedColors, nextColor) {
            return summedColors.map(
                (summedColor, i) => nextColor[i] + summedColor,
            );
        }

        function averageColors(colors) {
            return colors
                .reduce(sumColors, [0, 0, 0])
                .map(c => c/colors.length);
        }

        // Remove white from all colors
        const whiteParts = [];
        const colorParts = [];
        rgbColors.forEach(color => {
            const whiteVal = Math.min(...color);
            whiteParts.push([whiteVal, whiteVal, whiteVal]);
            colorParts.push(color.map(val => val - whiteVal));
        });
        
        // Average the whites from each selection
        const averagedWhite = averageColors(whiteParts);
        // Average all non-white colors
        let averagedColor = averageColors(colorParts);

        // Take out the white from the averaged colors
        const whitePart = Math.min(...averagedColor);
        averagedColor = averagedColor.map(color => color - whitePart);

        // Half the white value removed and add that value to the Green
        averagedColor[1] += (whitePart / 2);
        
        // Add the averaged white back in and make whole number
        averagedColor = averagedColor.map((color, i) => Math.floor(color + averagedWhite[i]));
    
        return averagedColor;
  }

我基于这个想法制作了一个颜色游戏,你可以试试


这似乎运行得相当不错。我猜可能会有一些颜色空间的部分缺失或其他问题,但我还没有遇到过。 - lucidquiet
当你混合示例Blue (0,0,255) + Yellow (255,255,0)时,你会得到一个非常暗的Green (0,63,0)。感觉绿色甚至比原来的蓝色更暗...但是既然我们添加了黄色,我期望结果会比蓝色更亮,而不是更暗。不过,这是一个有趣的算法...谢谢分享。至少它很简单 :-) - Tom Pažourek
@TomPažourek 我明白你的意思。更改第5步中添加的绿色部分应该会有所帮助。我认为0.75或0.8看起来比0.5好看。 - Cameron

0

请查看此实现,其中包括加法、减法和其他混合算法。

它是完全功能的(用Java编写),因此您可以测试需要混合的任何颜色,并查看是否符合您的需求。

正如其他回答所指出的那样,在减法CMYK算法中,蓝色+黄色(确切地说是青色+黄色)等于绿色。亲自试一试吧。


是的,但无论是加色混合还是减色混合,在所有情况下都不是很有效。尝试混合真正的蓝色(#0000ff)和黄色(#ffff00),这两种颜色都不能得到绿色。 - Tom Pažourek
那么,哪个是真正的蓝色?这是一个真正的混合 http://www.youtube.com/watch?v=OQ0qEBMC5xs 蓝色和黄色,而视频中的蓝色看起来像青色。你觉得呢? - albfan
视频中的颜色看起来对我来说是蓝色而不是青色。但这并不是关键,即使你混合强烈的蓝色和黄色,你也不会得到灰色,而是一些绿色的阴影。 - Tom Pažourek

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