如何对RGB值执行双线性插值?

10

figure

如果给出黑色像素的坐标,我可以通过数学公式y = mx + c插值计算出蓝色像素的坐标值。但是新的RGB像素值怎么办?考虑到黑色像素RGB值在图中如此给出,那么如何得到蓝色像素加权平均RGB值呢?

非常感谢任何帮助。提前致谢。


你可能需要将颜色空间转换为适当的格式(例如HSV),在那里进行插值,然后再转换回RGB。 - Paul R
3
不需要转换为HSV或任何其他颜色空间。虽然不同的颜色空间具有不同的属性,但这并不是必需的。 - user85109
3个回答

12
(这可能会很长。我会尽量简短,如果需要回答问题,我可能需要返回我的回复。)RGB中的颜色空间插值通常使用三线性插值,可以在一对双线性插值的基础上构建。但是,并没有要求必须使用三线性插值。实际上,其他插值方法通常更好,例如简单(或四面体)插值通常比三线性插值更好,因为有各种原因。有几种这样的晶格四面体分解可以使用。其中一个是相当标准的。(我不会在那里详细介绍太多,至少现在还没有。)此外,没有理由为什么必须在RGB中进行插值而不是其他空间,尽管人们可能会认为RGB有其自己的特殊问题,通常围绕着中性和近中性的插值。

与RGB和插值相关的特征是,中性被定义为R = G = B的点。三线性插值器沿着该中性轴具有最大误差,并且通常具有沿颜色空间中中性路径的错误的特征(呈扇形)。

那么如何在三维空间中进行插值呢?我假设你是在颜色空间的一组规则点上进行插值。在这种情况下,可以确定包含任何单个点的立方体。如果你要在一组散点内进行插值,则最简单的解决方案通常是构建这些点的三角剖分,然后在任何给定的四面体内进行简单(线性)插值。高阶插值在这里是有问题的,因为它们可能会在某些情况下导致颜色问题。例如,人们不希望看到梯度沿着反向变化。这可能会发生,因为在相对高曲率的区域中,基于样条的插值器存在严重的振铃问题。如果涉及到色域映射,那么这样的转换肯定会成为一个问题。即使不需要进行色域映射,仍然需要处理色域问题。
有几种方法可以从散乱数据中构建域的三角剖分。Alpha形状基于Delaunay三角剖分,是一个合理的选择。但是,假设你有一个规则的网格,并希望进行三线性插值,则问题将简化为在三维简单立方体内进行插值。
请注意,三线性插值并不是真正的线性插值器,就像双线性插值一样。这些方案仅沿着晶格的轴线是线性的,但沿着颜色空间中的任何其他路径,它们具有多项式特征。因此,三线性插值器将显示出主对角线或大多数通用路径沿着立方体的立方多项式行为。我们可以通过插值点的数量来证明三线性插值并不是真正的线性插值,因为在三维空间中,4个点确定了一个真正的线性插值器,作为这些独立变量的函数,但我们有8个定义立方体的点。也就是说,我们将从一个RGB空间到另一个RGB空间视为3个独立的映射,因此RGB --> UVW(我选择UVW来表示某些通用的其他颜色空间,可能或可能不是RGB)。
诀窍在于,我们通过在一对双线性插值之间进行插值来构建三线性插值器。我们通过在线性插值一对沿着一个边缘的点之间,然后在它们之间进行第三次插值来构建这些双线性插值器。因此,实际上,我们可以将三线性插值器视为由7个简单的线性插值组成。有趣的是,可以证明,无论我们首先沿哪个轴进行插值,都不会影响结果。因此,我们可以首先沿R轴进行插值,然后是B轴,然后是G轴,或者选择任何其他顺序 - 三线性插值器将是唯一且相同的,无论选择哪种顺序。(双线性插值器也是如此。)
所以问题是,我们如何在两个三元组点之间进行线性插值?首先,我们需要确定我们位于这些点之间的线段上的位置。例如,考虑我们颜色空间中沿着立方体的红(R)边缘处的两个点。我将使用您展示的相同值,即:
Q1 = [66, 51, 77]
Q2 = [55, 66, 77]

这些是我们要插值的值,基本上是我们映射的输出,但我们还需要知道这些点在输入RGB空间中的位置。因此,请假设基于它们来自的立方体的坐标,这些坐标为:

P1 = [0, 0, 0]
P2 = [1, 0, 0]

这是一个三维单位立方体,我已经写好了,所以其他点会位于

P3 = [0, 1, 0]
P4 = [1, 1, 0]
P5 = [0, 0, 1]
P6 = [1, 0, 1]
P7 = [0, 1, 1]
P8 = [1, 1, 1]

当然,任何一般的立方体也可以使用,没有理由它必须是一个真正的立方体。任何三维直角四边形棱柱在这里也适用。你总是可以将事物转化为单位立方体。

现在,假设我们希望在立方体的这条边上,在由Q1和Q2定义的域中,在P1和P2之间进行插值?选择沿着该边的某个点。你可以看到只有R在这些点之间沿着该边变化,因此我们只关心我们插值点处的R值。从沿着该边从0到1的红色通道的r值的百分比来考虑它。插值仅仅是两个端点的加权平均值,即线性组合。因此,对于沿着从0到1的红色通道的边缘具有红色值r的点,我们的插值将是

Q(r) = Q1*(1-r) + Q2*r

如你所见,当 r 为 1/2 时,即在边的中间位置,我们的插值函数将简化为
Q(1/2,0,0) = (Q1 + Q2)/2

从逻辑上讲,中点值将是两个端点的平均值。您需要独立为每个输出通道执行插值操作。

Q(1/2,0,0) = ([66, 51, 77] + [55, 66, 77])/2 = [60.5, 58.5, 77]

这个能恢复端点吗?当然可以。当r = 0或r = 1时,你可以看到它恰好返回相应的Q1或Q2。

同样,你可以沿着每个红色边缘进行插值,得到三线性插值器的四个结果。然后,你需要进行两次插值,可能是沿着我们上面得到的四个结果的绿色边缘。最后,你需要进行一次插值,沿着蓝色边缘得到三线性插值器。同样,选择插值轴的顺序并不重要。结果在数学上是相同的。

如果你停留在双线性插值上,那么就有三个这样的线性插值。是的,双线性插值或三线性插值也可以作为矩形(或立方体)的所有4(或8)个角落的加权组合来完成。这可以留待未来。


嗨Woodchips,我自己编写的双线性插值算法目前只允许图像按2x因子进行缩放。我想将其扩展到像3x、4x这样的整数倍。我现在苦恼于如何将原始图像的可用像素适配到新图像的缓冲区位置,并确定它们之间的空白间隔。目前还是硬编码,你能做我的导师一段时间吗?我真的很迷茫。 :) 希望能尽快收到你的回复。如果你想看代码,我可以发送给你。 - f0rfun

10

你需要独立地进行值的插值计算,对于R、G和B分别执行一次计算。例如,在(200,50,10)和(0,0,0)之间进行一半插值得到(100,25,5)。


在 RGB 颜色空间中,这样做会正确工作吗?难道你不应该在 HSV 空间中进行这种操作吗? - Paul R
在RGB模式下,它对我来说运行良好;我过去曾将其用于图像处理。 - Esoteric Screen Name
3
@Paul - 不需要转换到另一个色彩空间。RGB是任何有效的颜色空间之一,我是一位颜色插值方法专家,可以证实这一点。在RGB空间进行插值的唯一问题是必须关注中性颜色会发生什么情况,而这只与所使用的插值类型有关。例如,三线性插值对于RGB空间的中性颜色会造成问题。 - user85109

1
/*
  resize an image using bilinear interpolation
*/
void bilerp(unsigned char *dest, int dwidth, int dheight, unsigned char *src, int swidth, int sheight)
{
  float a, b;
  float red, green, blue, alpha;
  float dx, dy;
  float rx, ry;
  int x, y;
  int index0, index1, index2, index3;

  dx = ((float) swidth)/dwidth;
  dy = ((float) sheight)/dheight;
  for(y=0, ry = 0;y<dheight-1;y++, ry += dy)
  {
    b = ry - (int) ry;
    for(x=0, rx = 0;x<dwidth-1;x++, rx += dx)
    {
      a = rx - (int) rx;
      index0 = (int)ry * swidth + (int) rx;
      index1 = index0 + 1;
      index2 = index0 + swidth;     
      index3 = index0 + swidth + 1;

      red = src[index0*4] * (1.0f-a)*(1.0f-b);
      green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
      blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
      alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
      red += src[index1*4] * (a)*(1.0f-b);
      green += src[index1*4+1] * (a)*(1.0f-b);
      blue += src[index1*4+2] * (a)*(1.0f-b);
      alpha += src[index1*4+3] * (a)*(1.0f-b);
      red += src[index2*4] * (1.0f-a)*(b);
      green += src[index2*4+1] * (1.0f-a)*(b);
      blue += src[index2*4+2] * (1.0f-a)*(b);
      alpha += src[index2*4+3] * (1.0f-a)*(b);
      red += src[index3*4] * (a)*(b);
      green += src[index3*4+1] * (a)*(b);
      blue += src[index3*4+2] * (a)*(b);
      alpha += src[index3*4+3] * (a)*(b);

      red = red < 0 ? 0 : red > 255 ? 255 : red;
      green = green < 0 ? 0 : green > 255 ? 255 : green;
      blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
      alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

      dest[(y*dwidth+x)*4] = (unsigned char) red;
      dest[(y*dwidth+x)*4+1] = (unsigned char) green;
      dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
      dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
    }
    index0 = (int)ry * swidth + (int) rx;
    index1 = index0;
    index2 = index0 + swidth;     
    index3 = index0 + swidth;   

    red = src[index0*4] * (1.0f-a)*(1.0f-b);
    green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
    blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
    alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
    red += src[index1*4] * (a)*(1.0f-b);
    green += src[index1*4+1] * (a)*(1.0f-b);
    blue += src[index1*4+2] * (a)*(1.0f-b);
    alpha += src[index1*4+3] * (a)*(1.0f-b);
    red += src[index2*4] * (1.0f-a)*(b);
    green += src[index2*4+1] * (1.0f-a)*(b);
    blue += src[index2*4+2] * (1.0f-a)*(b);
    alpha += src[index2*4+3] * (1.0f-a)*(b);
    red += src[index3*4] * (a)*(b);
    green += src[index3*4+1] * (a)*(b);
    blue += src[index3*4+2] * (a)*(b);
    alpha += src[index3*4+3] * (a)*(b);

    red = red < 0 ? 0 : red > 255 ? 255 : red;
    green = green < 0 ? 0 : green > 255 ? 255 : green;
    blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
    alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

    dest[(y*dwidth+x)*4] = (unsigned char) red;
    dest[(y*dwidth+x)*4+1] = (unsigned char) green;
    dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
    dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
  }
  index0 = (int)ry * swidth + (int) rx;
  index1 = index0;
  index2 = index0 + swidth;     
  index3 = index0 + swidth;   

  for(x=0, rx = 0;x<dwidth-1;x++, rx += dx)
  {
    a = rx - (int) rx;
    index0 = (int)ry * swidth + (int) rx;
    index1 = index0 + 1;
    index2 = index0;     
    index3 = index0;

    red = src[index0*4] * (1.0f-a)*(1.0f-b);
    green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
    blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
    alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
    red += src[index1*4] * (a)*(1.0f-b);
    green += src[index1*4+1] * (a)*(1.0f-b);
    blue += src[index1*4+2] * (a)*(1.0f-b);
    alpha += src[index1*4+3] * (a)*(1.0f-b);
    red += src[index2*4] * (1.0f-a)*(b);
    green += src[index2*4+1] * (1.0f-a)*(b);
    blue += src[index2*4+2] * (1.0f-a)*(b);
    alpha += src[index2*4+3] * (1.0f-a)*(b);
    red += src[index3*4] * (a)*(b);
    green += src[index3*4+1] * (a)*(b);
    blue += src[index3*4+2] * (a)*(b);
    alpha += src[index3*4+3] * (a)*(b);

    red = red < 0 ? 0 : red > 255 ? 255 : red;
    green = green < 0 ? 0 : green > 255 ? 255 : green;
    blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
    alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

    dest[(y*dwidth+x)*4] = (unsigned char) red;
    dest[(y*dwidth+x)*4+1] = (unsigned char) green;
    dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
    dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
  }

   dest[(y*dwidth+x)*4] = src[((sheight-1)*swidth+swidth-1)*4];
   dest[(y*dwidth+x)*4+1] = src[((sheight-1)*swidth+swidth-1)*4+1];
   dest[(y*dwidth+x)*4+2] = src[((sheight-1)*swidth+swidth-1)*4+2];
   dest[(y*dwidth+x)*4+3] = src[((sheight-1)*swidth+swidth-1)*4+3];
}  

代码在此维护

https://github.com/MalcolmMcLean/babyxrc/blob/master/src/resize.c


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