非线性颜色插值?

jma*_*erx 8 c c++ algorithm

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

谢谢

wal*_*lky 30

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

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

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

偏移到插值

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

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

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

y = f(x)

通过这些点.

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

一些可能的插值

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

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

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

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

f(x) = a0 + a1 * x + a2 * x * x + a3 * x * x * x + ....
Run Code Online (Sandbox Code Playgroud)

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

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

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

y = y1 + (x - x1) * (y2 - y1) / (x2 - x1)
Run Code Online (Sandbox Code Playgroud)

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

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

回到问题

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

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

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

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

随机条纹

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

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

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

RGB中的分段线性插值

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

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

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

RGB中的二次插值

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

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

但是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中的分段线性插值

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

  • 哇,非常非常有趣.我能不能给你这个+5? (2认同)

Pat*_*ick 7

尝试将其转换为另一种颜色表示,例如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左右

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