简而言之
结果是错误的,因为GNU Octave的imresize
存在一个bug。最近已经修复了双线性插值(错误报告,提交代码)。向下滚动到“像素插值”中获取正确的图像插值方法。
样本插值
让我们从样本的线性插值开始:
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)代入其中,得到:
这就是你得到的结果。
在第二个例子中,点略有不同,因为你从每个方向的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---------+
| | | | |
1.25 | x | x | x | x |
| | | | |
1.5 +---------+---------+---------+---------+
| | | | |
1.75 | x | x | x | x |
| | | | |
2 +---------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)
这里是一个更一般的解决方案(仅适用于整数输出大小),受到下面Amro评论
他的答案的启发。如果允许缩放因子导致浮点输出大小。
在非整数输出大小上,新像素的数量将使最后一个像素重叠。例如,使用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];
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
xi(xi < 1) = 1; xi(xi > size(img, 2)) = size(img, 2)
yi(yi < 1) = 1; yi(yi > size(img, 1)) = size(img, 1)
scaled_interp = interp2(img, xi, yi', 'linear')
scaled_resize_octave = imresize(img, scale, 'bilinear')
scaled_resize_matlab = imresize(img, scale, 'bilinear', 'Antialiasing', false)
这就是使用双线性插值调整大小的全部内容。对于双三次插值,Matlab使用对称填充和一种卷积算法,该算法加权4x4邻域。Octave的行为不同(
即将到来的补丁)。
xi
和yi
中的内容。请参见此错误报告:https://savannah.gnu.org/bugs/?51769 - John