在Matlab imresize函数中用于插值的算法是什么?

时间:2011-06-22 14:08:50

标签: matlab interpolation octave

我正在使用Matlab / Octave imresize()函数重新采样给定的2D数组。我想了解imresize中使用的特定插值算法是如何工作的。

(我在Windows上使用八度音阶)

e.g。

A =  1 2 
     3 4

是一个2D数组。然后我使用命令

b=imresize(a,2,'linear'); 

基本上将行和列上采样2。

输出

1.0000   1.3333   1.6667   2.0000
1.6667   2.0000   2.3333   2.6667
2.3333   2.6667   3.0000   3.3333
3.0000   3.3333   3.6667   4.0000

我不明白这种线性插值是如何工作的。据说使用 bi 线性插值,但它如何填充边界处的数据以及它如何获得它所获得的输出?

第二个例子: 对于

A = 

1   2   3   4
5   6   7   8
0   1   2   3
1   2   3   4

imresize(a,1.5,'linear')如何提供以下输出?

1.00000   1.60000   2.20000   2.80000   3.40000   4.00000
3.40000   4.00000   4.60000   5.20000   5.80000   6.40000
4.00000   4.60000   5.20000   5.80000   6.40000   7.00000
1.00000   1.60000   2.20000   2.80000   3.40000   4.00000
0.40000   1.00000   1.60000   2.20000   2.80000   3.40000
1.00000   1.60000   2.20000   2.80000   3.40000   4.00000

4 个答案:

答案 0 :(得分:2)

以下代码显示了如何使用bilinear interpolation执行INTERP2

A = [1 2; 3 4];
SCALE = 2;

xi = linspace(1,size(A,2),SCALE*size(A,2));  %# interpolated horizontal positions
yi = linspace(1,size(A,1),SCALE*size(A,1));  %# interpolated vertical positions
[X Y] = meshgrid(1:size(A,2),1:size(A,1));   %# pixels X-/Y-coords
[XI YI] = meshgrid(xi,yi);                   %# interpolated pixels X-/Y-coords
B = interp2(X,Y,A, XI,YI, '*linear');        %# interp values at these positions

结果与您的Octave代码输出一致:

B =
            1       1.3333       1.6667            2
       1.6667            2       2.3333       2.6667
       2.3333       2.6667            3       3.3333
            3       3.3333       3.6667            4

我应该提一下,我在MATLABOctave IMRESIZE输出之间得到了不同的结果。例如,这是我在矩阵A=[1 2; 3 4]上的MATLAB中执行以下内容时得到的结果:

>> B = imresize([1 2; 3 4], 2, 'bilinear')
B =
            1         1.25         1.75            2
          1.5         1.75         2.25          2.5
          2.5         2.75         3.25          3.5
            3         3.25         3.75            4

这表明MATLAB的实现正在做一些额外的事情......不幸的是,读取IMRESIZE源代码并不容易,特别是因为在某些时候它调用了MEX编译的函数(没有可用的源代码形式)。

作为旁注,似乎还有这个函数的旧版本:IMRESIZE_OLD(纯粹用m代码实现)。根据我的理解,它对图像进行某种仿射变换。也许更熟悉这种技术的人可以对这个问题有所了解......

答案 1 :(得分:2)

TL;DR

结果是错误的,因为 GNU Octave 的 imresize 中存在错误。最近修复了双线性插值 (bug report, commit)。向下滚动到“像素插值”以了解如何插入图像的正确说明。

样本插值

让我们从样本的线性插值开始:

  0   1/3  2/3   1
  |    |    |    |
a=1----+----+----2=b

您可以通过使用从 a 混合到 b

f(x) = (1 - x) * a + x * b。

一些例子:

  • f(0) = (1 - 0) * a + 0 * b = a = 1
  • f(1/3) = (1 - 1/3) * a + 1/3 * b = 2/3 + 2/3 = 4/3 = 1.3333
  • f(1/2) = (1 - 1/2) * a + 1/2 * b = (a + b) / 2 = 1.5
  • f(2/3) = (1 - 2/3) * a + 2/3 * b = 1/3 + 4/3 = 5/3 = 1.6667
  • f(1) = (1 - 1) * a + 1 * b = b = 2

这对应于您的第一个示例的第一行。在双线性插值中,简单的线性插值用于 x 或 y 方向。一般来说,对角线或任何其他方向都不会使用简单的线性插值(您的第一个示例是退化情况)。

       0   1/3        1
       |    |    |    |
 0   a=1---f(x)--+----2=b
       |    |    |    |
      -+----+----+----+-
       |    |    |    |
2/3   -+---???---+----+-
       |    |    |    |
 1   c=3---g(x)--+----4=d
       |    |    |    |

那其他点是怎么计算的呢?我们在 x 方向对顶行和底行使用简单的线性插值,然后在 y 方向对结果进行插值:

  • 在 x 方向,顶行:f(x) = (1 - x) * a + x * b,例如:f(1/3) = 4/3 = 1.3333
  • 在 x 方向,底行:g(x) = (1 - x) * c + x * d,例如:g(1/3) = 10/3 = 3.3333
  • 在y方向,插值列:h(y) = (1 - y) * f(x) + y * g(x),示例:h(2/3) = 8/3 = 2.6667

看最后一个等式,我们也可以把 f(x) 和 g(x) 放进去,得到:

h(x, y) = (1 - x) * (1 - y) * a + x * (1 - y) * b + (1 - x) * y * c + x * y * d< /p>

这就是你得到的。

在第二个示例中,点略有不同,因为您在每个方向上从 4 个点转换为 6 个点:

old: 0         1         2         3 (sample grid)
     |         |         |         |
     +-----+---+-+-----+-+---+-----+
     |     |     |     |     |     |
new: 0    3/5   6/5   9/5  12/5    3 (interpolation grid)

这对第二个示例中的 x 和 y 方向有效。要使用上面的公式,您必须将每个正方形映射到 [0, 1] x [0, 1]。

这是理论。 Octave 在内部使用 interp2 进行双线性插值。要使用 interp2,您需要指定包含样本的矩阵和定义插值点的网格:

A = [1, 2;
     3, 4];
xi = linspace(1, size(A, 2), 4);
yi = linspace(1, size(A, 1), 4)';
B = interp2(A, xi, yi)

这给出了你得到的结果,但它们是错误的

像素插值

上面解释的双线性插值的基础仍然有效,但插值网格是错误的。这是因为图像不是由样本点组成,而是由像素组成。像素是以其平均值表示的区域。所以实际上图像的像素是这样的:

    0.5        1        1.5        2        2.5
0.5  +-------------------+-------------------+
     |                   |                   |
     |                   |                   |
     |                   |                   |
 1   |         o         |         o         |
     |                   |                   |
     |                   |                   |
     |                   |                   |
1.5  +-------------------+-------------------+
     |                   |                   |
     |                   |                   |
     |                   |                   |
 2   |         o         |         o         |
     |                   |                   |
     |                   |                   |
     |                   |                   |
2.5  +-------------------+-------------------+

因此左上角像素的区域是 [0.5, 1.5] x [0.5, 1.5],其中心位于 (1, 1)。而你想要通过放大 2 得到以下新像素(在旧网格的坐标空间中,因为图像仍然覆盖相同的区域):

    0.5  0.75  1   1.25 1.5  1.75  2   2.25 2.5
0.5  +---------+---------+---------+---------+
     |         |         |         |         |
0.75 |    x    |    x    |    x    |    x    |
     |         |         |         |         |
 1   +---------o---------+---------o---------+
     |         |         |         |         |
1.25 |    x    |    x    |    x    |    x    |
     |         |         |         |         |
1.5  +---------+---------+---------+---------+
     |         |         |         |         |
1.75 |    x    |    x    |    x    |    x    |
     |         |         |         |         |
 2   +---------o---------+---------o---------+
     |         |         |         |         |
2.25 |    x    |    x    |    x    |    x    |
     |         |         |         |         |
2.5  +---------+---------+---------+---------+

现在您将新中心 x 作为插值网格,将旧中心 o 作为样本网格。你看,新的边界像素实际上需要外推。我们假设它外推常数,因此我们可以填充数组以再次进行插值或限制插值网格。作为使用 interp2 的代码,您可以:

A = [1, 2;
     3, 4];
xi = linspace(0.75, 2.25, 4);
yi = linspace(0.75, 2.25, 4)';
xi(xi < 1) = 1; xi(xi > 2) = 2;
yi(yi < 1) = 1; yi(yi > 2) = 2;
B = interp2(A, xi, yi)

Here is a more general solution(仅对整数输出大小有效),灵感来自 Amro 在 his answer 下面的评论。如果您允许导致浮点输出大小的比例因子。

在非整数输出尺寸上,新像素的数量将使得最后一个像素重叠。因此,例如比例因子为 5/4 = 1.25,像素大小将为 1 / (5/4) = 4/5 = 0.8。因此,用 1.25 缩放 2x2 图像会产生 3x3 图像。旧像素中心(样本网格)位于 1 和 2,新像素中心(插值网格)位于 0.9、1.7 和 2.5。

    0.5                 1.5                 2.5
     |         1         |         2         |
old: +---------o---------+---------o---------+
new: +-------x-------+-------x-------+-------x-------+
     |      0.9      |      1.7      |      2.5      |
    0.5             1.3             2.1             2.9

以下是一些代码来显示这一点:

img = [1, 2;
       3, 4];

% make interpolation grid
scale = 1.25
pixel_size = 1 / scale
out_size = ceil(size(img) / pixel_size)
xi = 0.5 + pixel_size / 2 + (0:out_size(1)-1) / scale
yi = 0.5 + pixel_size / 2 + (0:out_size(2)-1) / scale

% limit interpolation grid to sample grid bounds
xi(xi < 1) = 1;   xi(xi > size(img, 2)) = size(img, 2)
yi(yi < 1) = 1;   yi(yi > size(img, 1)) = size(img, 1)

% interpolate
scaled_interp = interp2(img, xi, yi', 'linear')

% Octave's imresize does not support anti-aliasing yet
scaled_resize_octave = imresize(img, scale, 'bilinear')
% Matlab's imresize uses anti-aliasing for downscaling, switch off to keep is simple
scaled_resize_matlab = imresize(img, scale, 'bilinear', 'Antialiasing', false)

% yields:
%    1.0000    1.7000    2.0000
%    2.4000    3.1000    3.4000
%    3.0000    3.7000    4.0000

这就是使用双线性插值调整大小的全部内容。对于双三次插值,Matlab 使用对称填充和对 4x4 邻域加权的卷积算法。 Octave 表现不同 (patch incoming)。

答案 2 :(得分:1)

我为Java改编了MATLAB的imresize函数:

import java.util.ArrayList;
import java.util.List;

public class MatlabResize {
    private static final double TRIANGLE_KERNEL_WIDTH = 2;

    public static double[][] resizeMatlab(double[][] data, int out_y, int out_x) {
        double scale_x = ((double)out_x)/data[0].length;
        double scale_y = ((double)out_y)/data.length;

        double[][][] weights_indizes = contribution(data.length, out_y, scale_y, TRIANGLE_KERNEL_WIDTH);
        double[][] weights = weights_indizes[0];
        double[][] indices = weights_indizes[1];

        final double[][] result = new double[out_y][data[0].length];
        double value = 0;

        for (int p=0; p<result[0].length; p++) {
            for (int i=0; i<weights.length; i++) {
                value = 0;

                for (int j=0; j<indices[0].length; j++) {
                    value += weights[i][j] * data[(int)indices[i][j]][p];
                }

                result[i][p] = value;
            }
        }

        weights_indizes = contribution(data[0].length, out_x, scale_x, TRIANGLE_KERNEL_WIDTH);
        weights = weights_indizes[0];
        indices = weights_indizes[1];

        final double[][] result2 = new double[result.length][out_x];
        for (int p=0; p<result.length; p++) {
            for (int i=0; i<weights.length; i++) {
                value = 0;

                for (int j=0; j<indices[0].length; j++) {
                    value += weights[i][j] * result[p][(int)indices[i][j]];
                }

                result2[p][i] = value;
            }
        }

        return result2;
    }

    public static double[][] resizeMatlab(double[][] data, double scale) {
        int out_x = (int)Math.ceil(data[0].length * scale);
        int out_y = (int)Math.ceil(data.length * scale);

        double[][][] weights_indizes = contribution(data.length, out_y, scale, TRIANGLE_KERNEL_WIDTH);
        double[][] weights = weights_indizes[0];
        double[][] indices = weights_indizes[1];

        final double[][] result = new double[out_y][data[0].length];
        double value = 0;

        for (int p=0; p<result[0].length; p++) {
            for (int i=0; i<weights.length; i++) {
                value = 0;

                for (int j=0; j<indices[0].length; j++) {
                    value += weights[i][j] * data[(int)indices[i][j]][p];
                }

                result[i][p] = value;
            }
        }

        weights_indizes = contribution(data[0].length, out_x, scale, TRIANGLE_KERNEL_WIDTH);
        weights = weights_indizes[0];
        indices = weights_indizes[1];

        final double[][] result2 = new double[result.length][out_x];
        for (int p=0; p<result.length; p++) {
            for (int i=0; i<weights.length; i++) {
                value = 0;

                for (int j=0; j<indices[0].length; j++) {
                    value += weights[i][j] * result[p][(int)indices[i][j]];
                }

                result2[p][i] = value;
            }
        }

        return result2;
    }


    private static double[][][] contribution(int length, int output_size, double scale, double kernel_width) {
        if (scale < 1.0) {
            kernel_width = kernel_width/scale;
        }

        final double[] x = new double[output_size];
        for (int i=0; i<x.length; i++) {
            x[i] = i+1;
        }

        final double[] u = new double[output_size];
        for (int i=0; i<u.length; i++) {
            u[i] = x[i]/scale + 0.5*(1 - 1/scale);
        }

        final double[] left = new double[output_size];
        for (int i=0; i<left.length; i++) {
            left[i] = Math.floor(u[i] - kernel_width/2);
        }

        int P = (int)Math.ceil(kernel_width) + 2;

        final double[][] indices = new double[left.length][P];
        for (int i=0; i<left.length; i++) {
            for (int j=0; j<=P-1; j++) {
                indices[i][j] = left[i] + j;
            }
        }

        double[][] weights = new double[u.length][indices[0].length];
        for (int i=0; i<u.length; i++) {
            for (int j=0; j<indices[i].length; j++) {
                weights[i][j] = u[i] - indices[i][j];
            }
        }

        if (scale < 1.0) {
            weights = triangleAntiAliasing(weights, scale);
        } else {
            weights = triangle(weights);
        }

        double[] sum = Matlab.sum(weights, 2);
        for (int i=0; i<weights.length; i++) {
            for (int j=0; j<weights[i].length; j++) {
                weights[i][j] = weights[i][j] / sum[i];
            }
        }

        for (int i=0; i<indices.length; i++) {
            for (int j=0; j<indices[i].length; j++) {
                indices[i][j] = Math.min(Math.max(indices[i][j], 1.0), length);
            }
        }

        sum = Matlab.sum(weights, 1);
        int a = 0;

        final List<Integer> list = new ArrayList<Integer>();
        for (int i=0; i<sum.length; i++) {
            if (sum[i] != 0.0) {
                a++;
                list.add(i);
            }
        }

        final double[][][] result = new double[2][weights.length][a];
        for (int i=0; i<weights.length; i++) {
            for (int j=0; j<list.size(); j++) {
                result[0][i][j] = weights[i][list.get(j)];
            }
        }
        for (int i=0; i<indices.length; i++) {
            for (int j=0; j<list.size(); j++) {
                result[1][i][j] = indices[i][list.get(j)]-1; //java indices start by 0 and not by 1
            }
        }

        return result;
    }

    private static double[][] triangle(final double[][] x) {
        for (int i=0; i<x.length; i++) {
            for (int j=0; j<x[i].length; j++) {
                if (-1.0 <= x[i][j] && x[i][j] < 0.0) {
                    x[i][j] = x[i][j] + 1;
                } else if (0.0 <= x[i][j] &&  x[i][j] < 1.0) {
                    x[i][j] = 1 - x[i][j];
                } else {
                    x[i][j] = 0;
                }
            }
        }

        return x;
    }

    private static double[][] triangleAntiAliasing(final double[][] x, final double scale) {
        for (int i=0; i<x.length; i++) {
            for (int j=0; j<x[i].length; j++) {
                x[i][j] = x[i][j] * scale;
            }
        }

        for (int i=0; i<x.length; i++) {
            for (int j=0; j<x[i].length; j++) {
                if (-1.0 <= x[i][j] && x[i][j] < 0.0) {
                    x[i][j] = x[i][j] + 1;
                } else if (0.0 <= x[i][j] &&  x[i][j] < 1.0) {
                    x[i][j] = 1 - x[i][j];
                } else {
                    x[i][j] = 0;
                }
            }
        }

        for (int i=0; i<x.length; i++) {
            for (int j=0; j<x[i].length; j++) {
                x[i][j] = x[i][j] * scale;
            }
        }

        return x;
    }
}

答案 3 :(得分:0)

如您所见,在您的示例中,每个角点都是您的原始输入值之一。

中间值在每个方向上通过linear interpolation导出。例如,计算b(3,2)

  • b(1,2)b(1,1)b(1,4)之间的1/3。所以:

    b(1,2) = (1/3)*b(1,4) + (2/3)*b(1,1)
    
  • b(4,2)b(4,1)b(4,4)之间的1/3。所以:

    b(4,2) = (1/3)*b(4,4) + (2/3)*b(4,1)
    
  • b(3,2)b(1,2)b(4,2)之间的2/3。所以:

    b(3,2) = (2/3)*b(4,2) + (1/3)*b(1,2)