如果我有一条直线,从0到1,那么我在该行的0处有colorA(255,0,0),那么在0.3处我有colorB(20,160,0),然后在1处我有colorC (0,0,0)。如何找到0.7的颜色?
谢谢
最佳答案
[详细说明我对Patrick的评论-一切都变得一发不可收拾!]
这确实是一个有趣的问题,有趣的原因之一是没有“正确”的答案。颜色表示和数据插值都可以以许多不同的方式完成。您需要针对您的问题领域适本地调整方法。由于我们没有获得有关该领域的大量先验信息,因此我们只能探索其中的一些可能性。
彩色业务会使水有些困惑,因此让我们暂时将其搁置一旁,首先考虑一下简单标量的插值。
游览插值
假设我们有一些像这样的数据点:
我们想要找到的是此图上的y
值在沿着x
轴的点(除了我们知道的值)之外的其他点。也就是说,我们正在寻找一个功能y = f(x)
通过这些点。
显然,我们可以选择许多不同的方法来连接点。我们可以将它们与直线段链接起来。或者我们可能想要一条平滑的曲线,而该曲线可能是简单的,也可能是任意复杂的:
在这里,很容易理解红线的来源-我们只是在从一个已知点到下一个已知点绘制直线。绿线看起来也很合理,尽管我们添加了一些有关曲线应如何表现的假设。另一方面,仅凭数据点很难证明蓝线的合理性,但是在某些情况下,我们有理由使用这种形状对系统进行建模-稍后我们会看到。
还要注意虚线从每条曲线的侧面。这些超出已知点,即外推而不是内插。这通常比插值要难得多。至少当您从一个已知点转到另一个已知点时,您有一些证据证明您的方法正确。距离已知点越远,您越有可能偏离路径。但这仍然是一种有用的技术,可能很有意义。
好的,图片很漂亮,但是如何生成这些线条?
我们需要找到一个f(x)
,它将为我们提供所需的y
。根据情况,我们可以使用许多不同的函数,但是到目前为止,最常见的是使用多项式函数,即:
f(x) = a0 + a1 * x + a2 * x * x + a3 * x * x * x + ....
现在,在给定
N
不同的数据点的情况下,始终可以使用N-1
度的多项式找到一条完美拟合的曲线-一条直线指向两个点,一条抛物线指向三个点,三次方指向四个点,依此类推-但除非数据异常良好,随着程度的升高,曲线趋于困惑。因此,除非有充分的理由相信数据的行为可以通过高次多项式很好地建模,否则通常是对数据分段进行插值,以在每对连续的点之间拟合不同的曲线段。通常,将每个段建模为度数为 1 或 3 的多项式。前者只是一条直线,之所以被使用是因为它确实很简单,并且除了这两个数据点本身之外,不需要任何信息。后者是三次样条曲线,因为它是最简单的多项式,可让您在每个点之间平滑过渡,所以使用了后者。但是,计算它要复杂一些,并且每个段都需要两个额外的信息(确切的信息取决于您使用的特定样条曲线形式)。
线性插值非常容易,只需一行代码即可。如果我们的第一个数据点是
(x1, y1)
,而第二个数据点是(x2, y2)
,那么在任何中间y
处的线性插值x
是:y = y1 + (x - x1) * (y2 - y1) / (x2 - x1)
(与此相关的变化出现在其他一些答案中。)
三次样条曲线涉及的内容过多,因此请参见Google,但Google应该提供一些不错的引用。在这种情况下,无论如何他们都是过分杀伤力的,因为您只有3分。
回到问题
掌握了所有这些内容之后,让我们看一下所描述的问题:
我们有一条线(这里显示为灰色),其颜色仅在三个点处已知,并且要求我们计算在其他点处的颜色。由于我们对比色如何分布一无所知,因此我们必须做一些假设。
一个关键的假设是,沿线的颜色变化将是连续,至少是某种近似值。显然,如果该行确实是这样的:
那么所有有关插值的早期知识都将出局。我们没有理由决定任何部分应该是什么颜色,应该放弃然后回家。假设情况并非如此,我们需要进行插值。
已知颜色指定为 RGB 。在这种表示形式中,每个 channel 都是一个单独的标量值,我们可以选择将其视为完全独立于其他 channel 。因此,一种完全合理的方法是对每个 channel 进行分段线性插值,然后重新组合结果。
这样做给我们这样的东西:
就目前而言,这很好,但是结果的某些方面我们可能不喜欢。一个是从红色到绿色的过渡穿过了一个暗淡的灰棕色。另一个是在0.3处的绿色峰值有点尖锐。
重要的是要注意,在没有更全面的规范的情况下,这些实际上只是美学问题。我们的技术非常完善,但是并没有提供我们可能想要的那种结果。这类事情取决于我们特定的问题领域,最终这完全是一个选择问题。
由于我们只有三个数据点-以及汉斯·帕桑特(Hans Passant)提出的建议-也许我们可以尝试拟合抛物线以对每个 channel 的整个曲线建模?的确,我们没有任何理由认为这是一个很好的模型,但是尝试也没有什么害处:
该梯度与最后一个梯度之间的差异是有启发性的。二次方使事情变得平滑,但也大大超出了范围。请记住,绿色 channel 的起点和终点均为0。抛物线是对称的,因此其最大值必须在中间。它能使绿色上升到0.3的唯一方法是继续一直上升到0.5。 (在红色 channel 中有类似的效果,但是它不太明显,因为在这种情况下,它是一个下冲,并且该值固定为0。)
我们是否有任何证据表明这种形状确实存在于我们的色线中?否:我们通过选择模型明确地介绍了它。但这并没有使它无效-我们可能有充分的理由希望它以这种方式工作-再次是选择的问题。
但是HSV呢?
到目前为止,我们仍停留在原始的RGB颜色空间上,但是由于各种人争先恐后指出,这对于插值而言可能并不理想:颜色和强度在RGB中绑定(bind)在一起,因此通常需要在两种不同的全强度颜色之间进行插值您通过一些单调的低强度中间体。
在 HSV 表示中,颜色和强度在不同的维度上,因此我们不会遇到这个问题。为什么不转换并在该空间中进行插值呢?
这立即引起了困难-或至少是另一个决定。 HSV和RGB之间的映射不是bijective;特别是黑色,它恰好是我们三个数据点之一,是RGB空间中的单个点,但在HSV中占据了整个平面。我们无法在点和平面之间进行插值,因此我们需要选择一个特定的HSV黑点。
这是帕特里克(Patrick)独创性解决方案的基础,在该解决方案中,特别选择了H和S以使整个颜色梯度呈线性。结果看起来像这样:
与以前的尝试相比,这看起来更漂亮,更丰富多彩,但是有些事情我们可能会争论不休。
一个重要的问题是,在V的情况下,我们仍然在所有三个点上都有确定的数据,这些数据实际上不是线性的,因此线性拟合只是一个近似值。这意味着我们在此处看到的0.3的值与应有的值不完全相同。
在我看来,另一个更大的问题是:那蓝色到底是哪里来的? 我们所有三个已知的RGB数据点的B = 0。突然引入一整束蓝色似乎有点奇怪,我们似乎根本没有任何证据。查看以下Patrick的HSV插值中的RGB分量图:
当然,蓝色的原因是,在切换色彩空间时,我们特别选择了一个模型,如果您继续从绿色前进,则不可避免地会变成蓝色。同时,我们不得不舍弃其中一个色相数据点,并选择通过线性推算从其他两个色相数据点来填充它,这意味着我们确实一直在继续进行,从绿色到蓝色再到山丘和远处。
再一次,这不是无效的,我们可能有充分的理由这样做。但是在我看来,由于外推,所以它比以前的分段线性示例稍微多了一点。
那么这是在HSV中做到这一点的唯一方法吗?当然不是。总会有更多选择。
例如,不是选择1.0的H和S值以最大化线性,而是选择它们以最小化分段线性插值的变化呢?碰巧的是,对于S,这两个策略是重合的:两个点都为100,因此我们最后也将其设为100。这两个段是共线的。但是,对于H,我们只是在第二段保持不变。这给出了这样的结果:
这不像上一个那样可爱,但是对我来说似乎更合理。再一次,这很大程度上是一种审美判断。那并没有使它成为“正确”的答案,反而使帕特里克或其他任何人都“错了”。正如我刚开始所说的那样,没有“正确”的答案。只是根据您在特定问题中的需求做出选择,并且要知道您已经做出了选择以及它们如何影响您的结果。
关于c++ - 非线性颜色插值?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3017019/