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

时间:2012-01-12 14:15:38

标签: image math image-processing color-space

figure

给定黑色像素坐标,我可以通过数学方程y = mx + c插值蓝色像素的坐标值。但是新的RGB像素值呢?如果在图中给出黑色像素RGB值,我该如何获得蓝色像素的加权平均RGB值?

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

3 个答案:

答案 0 :(得分:12)

(这可能会变长。我会尽量保持简短,在这种情况下,我可能需要回到我的回答来回答问题。)RGB中的颜色空间插值通常使用三线性插值,它可以构建在一对双线性插值之上。但是没有要求使用三线性插值。事实上,其他插值通常更好,例如单纯(或四面体)插值通常优于三线性的各种原因。可以使用几种这样的晶格四面体解剖。一个是相当标准的。 (我不会在那里进入太多细节,至少现在还没有。)此外,没有理由为什么必须在RGB中插入而不是在其他空间内插入,尽管有人可能认为RGB有其自身的特殊问题,通常围绕中性线和近中性点的插值。

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

那么我们如何在三维中进行插值?我假设一个是在颜色空间中的点的规则点阵中进行插值。在这种情况下,可以识别包含任何单个点的立方体。如果要在一组分散的点内插值,那么最简单的解决方案通常是建立这些点的三角剖分,然后在任何给定的四面体内进行单纯(线性)插值。无论如何,高阶插值在这里是有问题的,因为它们在某些情况下会引起颜色问题。例如,人们不希望看到沿着渐变的逆转。这可能发生,因为振铃是在具有相对高曲率的区域中基于样条的插值的严重问题。如果涉及到色域映射,那么这种转换肯定会成为一个问题。即使不需要色域映射,仍然需要处理色域问题。

有几种方法可以从分散的数据中构建域的三角测量。 Alpha形状基于Delaunay三角剖分,是一种合理的选择。但是假设你有一个规则的格子并且希望进行三线性插值,那么问题就会减少到3-d中简单立方体内的插值。

请注意,三线性插值不是真正的线性插值,不仅仅是双线性插值。这些方案仅沿着晶格的轴是线性的,但是沿着通过颜色空间的任何其他路径,它们具有多项式特征。因此,三线性插值将沿主对角线或沿着通过立方体的大多数通用路径显示三次多项式行为。我们可以说服自己三线性插值不是真正的线性,因为我们之间插入了8个点。在3-d中,4个点确定一个真正的线性插值,作为这些自变量的函数,但我们有8个点来定义一个立方体。也就是说,我们将查看从一个RGB空间到另一个RGB空间的映射,实际上是3个独立的映射,因此RGB - > UVW(我在这里选择UVW来表示一些通用的其他颜色空间,其中可能是RGB,也可能不是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]

当然,任何一般的立方体也可以工作,并且没有理由将它作为一个真正的立方体。任何3-d右,矩形4面棱镜也适用于此。您总是可以将事物转换为单位立方体。

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

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

正如你所看到的,当r是1/2,因此沿着边缘的中间,我们的插值将减少到

Q(1/2,0,0) = (Q1 + Q2)/2

逻辑上,中点值将是两个端点的平均值。您可以单独执行EACH输出通道的插值。

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个)角的加权组合来完成。这可以留给未来。

答案 1 :(得分:9)

您可以独立插值,为R,G和B分别执行计算。例如,在(200,50,10)和(0,0,0)之间插值(100,25,5)

答案 2 :(得分: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