非线性颜色插值?

如果我有一条从0到1的直线,那么我在线上的颜色为0(255,0,0),然后在0.3我有colorB(20,160,0)然后在线上我有colorC (0,0,0)。 我怎么能找到0.7的颜色?

谢谢

[详细说明我对帕特里克的评论 – 这一切都有点失控!]

这真是一个有趣的问题,其中一个有趣的原因是没有“正确”的答案。 颜色表示和数据插值都可以以许多不同的方式完成。 您需要针对您的问题域适当地定制您的方法。 由于我们没有获得关于该领域的先验信息,我们只能探索一些可能性。

颜色的业务有点混乱,所以让我们暂时把它放在一边,首先想一想简单标量的插值。

偏移到插值

假设我们有一些像这样的数据点:

我们可能希望适应一个功能的三点

我们想要找到的是这个图上的y值是沿着x轴的点,而不是我们已知值的点。 也就是说,我们正在寻找一个function

y = f(x)

通过这些点。

显然,我们可以选择许多不同的方式来加入这些点。 我们可能只是将它们与直线段链接起来。 或者我们可能想要一条平滑的曲线,这条曲线可能很简单或任意复杂:

一些可能的插值

在这里,很容易理解红线的来源 – 我们只是从一个已知点到下一个点绘制直线。 绿线看起来也很合理,尽管我们对曲线的行为方式进行了一些假设。 另一方面,蓝线很难仅根据数据点来certificate,但在某些情况下我们有理由使用这种形状对系统进行建模 – 我们稍后会看到。

还要注意每条曲线两侧的虚线。 这些超出了已知的点,称为外推而不是插值。 这通常比插值更有问题。 至少当你从一个已知点到另一个点时,你有一些证据certificate你是正确的。 虽然你从已知的点越远,你就越有可能偏离路径。 但是,它仍然是一种有用的技术,它可能很有意义。

好的,所以图片很漂亮,但我们如何生成这些线?

我们需要找到一个能给我们所需yf(x) 。 根据具体情况我们可以使用许多不同的函数,但到目前为止最常见的是使用多项式函数,即:

 f(x) = a0 + a1 * x + a2 * x * x + a3 * x * x * x + .... 

现在,给定N不同的数据点,总是可以使用N-1多项式找到一个完美拟合的曲线 – 两个点的直线,三个抛物线,四个立方体等等 – 但是除非数据exception良好,否则曲线会随着程度的提高而变得混乱。 因此,除非有充分的理由相信数据的行为是由高次多项式很好地建模的,否则通常是分段内插数据,在每个连续的点对之间拟合不同的曲线段。

通常,每个片段被建模为13度的多项式。 前者只是一条直线,因为它非常容易,并且不需要两个数据点本身的信息。 后者是一个三次样条函数,因为它是最简单的多项式,可以让您在每个点上平滑过渡。 但是,计算它有点复杂,并且每个段需要两条额外的信息(确切地说这些部分取决于您使用的特定样条forms)。

线性插值很容易进入一行代码。 如果我们的第一个数据点是(x1, y1)而第二个数据点是(x1, y1) (x2, y2) ,那么任何中间x的线性插值y都是:

 y = y1 + (x - x1) * (y2 - y1) / (x2 - x1) 

(对此的变化出现在其他一些答案中。)

立方样条有点过于牵涉到这里,但谷歌应该提出一些不错的参考。 无论如何,他们在这种情况下可能有点过分,因为你只有三分。

回到问题

尽管如此,让我们来看看所描述的问题:

带有三个已知色点的灰色条

我们有一条线(此处显示为灰色),其颜色仅在三个点处已知,我们被要求计算其他点的颜色。 由于我们不知道如何分配颜色,我们必须做出一些假设。

一个关键的假设是沿着线的颜色变化将是连续的 ,至少是一些近似值。 显然,如果这条线真的像这样:

随机条纹

然后所有关于插值的早期内容都会消失。 我们没有任何依据来决定任何部分应该是什么颜色,应该放弃并回家。 让我们假设情况并非如此,我们有一些内插。

已知颜色指定为RGB 。 在此表示中,每个通道都是单独的标量值,我们可以选择将其视为完全独立于其他通道。 因此,一种完全合理的方法是对每个通道进行分段线性插值,然后重新组合结果。

这样做会给我们这样的事情:

RGB中的分段线性插值

这样就可以了,但我们可能不喜欢这些结果。 一个是从红色到绿色的过渡通过一个非常模糊的灰褐色。 另一个是0.3处的绿色峰值有点尖锐。

重要的是要注意,在没有更全面的规范的情况下,这些只是美学问题。 我们的技术非常完美,但它并没有给出我们想要的那种结果。 这种事情取决于我们的具体问题领域,最终这都是一个选择问题。

由于我们只有三个数据点 – 而且自从Hans Passant建议 – 或许我们可以尝试使用抛物线来模拟每个通道的整个曲线? 确实,我们没有任何理由认为这是一个模型,但尝试:

RGB中的二次插值

这个梯度和最后一个梯度之间的差异是有益的。 二次方使事情变得平滑,但也大大超过了。 请记住,绿色通道的开始和结束都是0.抛物线是对称的,所以它的最大值必须在中间。 它可以适应绿色上升到0.3的唯一方法是继续上升到0.5。 (在红色通道中有类似的效果,但它不太明显,因为在这种情况下,它是一个下冲,并且该值被钳制为0.)

我们是否有任何证据certificate这种形状确实存在于我们的色线中? 不,我们通过选择模型明确地介绍了它。 这并没有使它失效 – 我们可能有充分的理由希望它以这种方式工作 – 再一次这是一个选择问题。

但是HSV怎么样?

到目前为止,我们已经坚持原始的RGB色彩空间,但是随着各种人们急于指出这可能不太理想的插值:颜色和强度在RGB中绑定在一起,因此在两种不同的全强度颜色之间进行插值通常需要你通过一些单调的低强度中间体。

HSV表示中,颜色和强度在不同的维度上,因此我们不会遇到这个问题。 为什么不转换并在该空间中进行插值呢?

这立刻导致了另一个决定的困难 – 或者无论如何。 HSV和RGB之间的映射不是双射的 ; 特别是黑色 ,它恰好是我们的三个数据点之一,是RGB空间中的单个点,但在HSV中占据整个平面。 我们不能在点和平面之间进行插值,因此我们需要选择一个特定的HSV黑点。

这是Patrick巧妙解决方案的基础,其中H和S专门用于使整个颜色渐变线性。 结果看起来像这样:

帕特里克在HSV中的线性内插/外推

这看起来比以前的尝试更漂亮,更丰富多彩,但有一些我们可能会狡辩的事情。

一个重要的问题是,在V的情况下,我们仍然在所有三个点都有确定的数据,这些数据实际上不是线性的,因此线性拟合只是一个近似值。 这意味着我们在这里看到的值为0.3并不是它应该是的。

另一个,在我看来更大,更狡辩: 蓝色来自哪里? 我们所有三个已知的RGB数据点都具有B = 0。 突然引入一大堆蓝色似乎有点奇怪,我们似乎根本没有任何证据。 查看Patrick的HSV插值中的RGB分量图:

Patrick版本中RGB组件的图表

当然,蓝色的原因在于,在切换色彩空间时,我们专门选择了一个模型,如果你继续从绿色开始,你不可避免地会变成蓝色。 与此同时,我们不得不丢弃我们的一个色调数据点,并选择通过其他两个的线性外推填充它,这意味着我们只是继续前进,从绿色到蓝色到山顶和远处。

再一次,这不是无效的,我们可能有充分的理由这样做。 但是在我看来,由于这种推断,它比前一个分段线性的例子更多但更多。

那么这是HSV的唯一方法吗? 当然不是。 总有更多的选择。

例如,我们不是选择1.0处的H和S值来最大化线性度,而是选择它们来最小化分段线性插值的变化? 碰巧的是,对于S来说,这两个策略是一致的:两个点都是100,所以我们最后也是100。 这两个部分是共线的。 但是对于H,我们只是在第二段中保持相同。 这给出了这样的结果:

HSV中的分段线性插值

这不像前一个那么可爱,但对我来说似乎更合理一些。 不过,这在很大程度上是一种审美判断。 这并不能使这成为“正确”的答案,而不是让帕特里克或任何其他人“错误”。 正如我在开始时说的那样,没有“正确”的答案。 所有这一切都需要根据您在特定问题中的需求做出选择 – 并意识到您做出了这些选择以及它们如何影响您的结果。

尝试将其转换为另一种颜色表示,例如HSV(请参阅http://en.wikipedia.org/wiki/HSL_and_HSV )。

  • 颜色A的色调为0,饱和度为1,值为1。
  • 颜色C的色调为?,饱和度为? 和值为0。

? 意味着它实际上无关紧要(因为颜色C只是黑色)。

现在还将颜色B转换为HSV(对不起我不能这样做,对不起),然后为颜色C的色调和饱和度选择漂亮的值,以便Hue,Value和Saturation在HSV空间的一行上。 然后从中扣除0.7的颜色。

编辑:使用http://www.csgnetwork.com/csgcolorsel4.html上的RGB-HSV计算器,我计算了以下内容:

  • 颜色A:H:0,V:100,S:100
  • 颜色B:H:113,V:100,S:63

现在我们计算颜色C的H和V,如下所示:

  • H:113 / 0.3 = 376.6
  • V:100(因为A和B都有V或100)

这给我们的颜色为0.7:

  • H = 376.66 * 0.7 = 263.66
  • V = 100
  • S =大约30左右

不幸的是,这并不完全适合饱和度,但如果你以这种方式进行插值,你会得到一些非常接近你想要的东西。

在摘要中,假设f(0.0)=(255,0,0),f(0.3)=(20,160,0)和f(1.0)=,则要求f(0.7)的值(0,0,0)。 如上所述,这没有明确定义,因为f可以是无数个函数中的任何一个。

f的一些可能选择是:

  1. RGB空间中的一系列线段
  2. 映射到HSV或HSL空间后的一系列线段
  3. RGB空间中的三次样条。
  4. HSV / L空间中的三次样条。

但是应该如何定义f的具体选择应该取决于你正在做什么,或者如何为你的应用定义颜色停止。

插入RGB的正确方法是首先校正伽玛,使颜色分量线性化,执行插值,然后转换回来。 否则,由于值是非线性的,您将收到错误。 有关详细信息,请参阅http://www.4p8.com/eric.brasseur/gamma.html (尽管文章是关于缩放的,但缩放通常通过插值像素值来完成)。

如果你有sRGB值,你可以使用下面的公式在线性和非线性之间进行转换: http : //en.wikipedia.org/wiki/SRGB (使用Csrgb和Clinear公式)

另一方面,错误并不大,所以如果它们重要,它取决于您的应用程序。

对于插值本身,简单的线性插值应该足够(正如其他人已经注意到的那样)。 有关详细信息,请参见http://en.wikipedia.org/wiki/Linear_interpolation 。

看起来你正试图在错误的色彩空间中进行插值。 首先转换为HSV,然后你的颜色变为

 RGB -> HSV 255,0,0 -> 0, 255,255 20,160,0 -> 80,255,160 0,0,0 -> 0,255,0 

因此它仍然令人困惑,但至少我们可以看到V值插值为零,H值令人困惑,但在意识到HSV被建模为圆柱体后,我们可以看到它实际上只是从0到256的插值256,所以它只是回到零。

所以你的等式将是(在HSV中)

 nH = frac*256 mod 256 nS = 255 nV = (1-frac)*255 

因此,如果你用0.7代替frac你会在HSV坐标中得到正确的计算,如果你需要回到RGB,你应该看看你的库是否提供了这样的function(我知道java的Color类支持这个),但如果不是,你可以从这里抓取代码。 它显示了如何在C中进行色彩空间转换。

祝好运