在您的 cmets 中,您提到您想使用双线性插值调整图像大小。请记住,双线性插值算法与大小无关。您可以很好地使用相同的算法来放大图像以及缩小图像。对像素位置进行采样的正确比例因子取决于您指定的输出尺寸。顺便说一下,这不会改变核心算法。
在开始编写任何代码之前,我将向您推荐Richard Alan Peters' II digital image processing slides on interpolation,特别是幻灯片#59。它有一个很好的说明以及关于如何进行 MATLAB 友好的双线性插值的伪代码。为了独立起见,我将在此处包含他的幻灯片,以便我们可以跟随并对其进行编码:
请注意,这只会重新采样图像。如果您确实想匹配 MATLAB 的输出,则需要禁用抗锯齿。
MATLAB by default will perform anti-aliasing 在图像上以确保输出看起来令人愉悦。如果您想比较苹果和苹果,请确保在比较此实现和 MATLAB 的 imresize 函数时禁用抗锯齿功能。
让我们编写一个函数来为我们做这件事。此函数将接收可以是彩色或灰度的图像(通过imread 读取)以及两个元素的数组 - 您要调整大小的图像和输出尺寸在一个二元素数组中您想要的最终调整大小的图像。该数组的第一个元素将是行,该数组的第二个元素将是列。我们将简单地通过这个算法并使用这个伪代码计算输出像素颜色/灰度值:
function [out] = bilinearInterpolation(im, out_dims)
%// Get some necessary variables first
in_rows = size(im,1);
in_cols = size(im,2);
out_rows = out_dims(1);
out_cols = out_dims(2);
%// Let S_R = R / R'
S_R = in_rows / out_rows;
%// Let S_C = C / C'
S_C = in_cols / out_cols;
%// Define grid of co-ordinates in our image
%// Generate (x,y) pairs for each point in our image
[cf, rf] = meshgrid(1 : out_cols, 1 : out_rows);
%// Let r_f = r'*S_R for r = 1,...,R'
%// Let c_f = c'*S_C for c = 1,...,C'
rf = rf * S_R;
cf = cf * S_C;
%// Let r = floor(rf) and c = floor(cf)
r = floor(rf);
c = floor(cf);
%// Any values out of range, cap
r(r < 1) = 1;
c(c < 1) = 1;
r(r > in_rows - 1) = in_rows - 1;
c(c > in_cols - 1) = in_cols - 1;
%// Let delta_R = rf - r and delta_C = cf - c
delta_R = rf - r;
delta_C = cf - c;
%// Final line of algorithm
%// Get column major indices for each point we wish
%// to access
in1_ind = sub2ind([in_rows, in_cols], r, c);
in2_ind = sub2ind([in_rows, in_cols], r+1,c);
in3_ind = sub2ind([in_rows, in_cols], r, c+1);
in4_ind = sub2ind([in_rows, in_cols], r+1, c+1);
%// Now interpolate
%// Go through each channel for the case of colour
%// Create output image that is the same class as input
out = zeros(out_rows, out_cols, size(im, 3));
out = cast(out, class(im));
for idx = 1 : size(im, 3)
chan = double(im(:,:,idx)); %// Get i'th channel
%// Interpolate the channel
tmp = chan(in1_ind).*(1 - delta_R).*(1 - delta_C) + ...
chan(in2_ind).*(delta_R).*(1 - delta_C) + ...
chan(in3_ind).*(1 - delta_R).*(delta_C) + ...
chan(in4_ind).*(delta_R).*(delta_C);
out(:,:,idx) = cast(tmp, class(im));
end
获取上面的代码,将其复制并粘贴到一个名为bilinearInterpolation.m 的文件中并保存。确保更改保存此文件的工作目录。
除了sub2ind 和也许meshgrid,一切似乎都符合算法。 meshgrid 很容易解释。您所做的只是指定(x,y) 坐标的二维网格,其中图像中的每个位置都有一对(x,y) 或列和行坐标。通过meshgrid 创建网格可避免任何for 循环,因为我们将在继续之前从算法中生成所有正确的像素位置。
sub2ind 的工作原理是它在 2D 矩阵中获取行和列位置(嗯...它实际上可以是任何您想要的维度),并输出单一线性索引。如果您不知道 MATLAB 如何索引矩阵,可以通过两种方式访问矩阵中的元素。您可以使用行和列来获得所需的内容,也可以使用 column-major 索引。看看我在下面的这个矩阵示例:
A =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
如果我们想访问数字 9,我们可以使用A(2,4),这是大多数人倾向于默认的。还有另一种使用单个数字访问数字 9 的方法,即 A(11)... 现在情况如何? MATLAB 以 column-major 格式布置其矩阵的内存。这意味着,如果您要获取此矩阵并将其所有 列 堆叠在一个数组中,它将如下所示:
A =
1
6
11
2
7
12
3
8
13
4
9
14
5
10
15
现在,如果您想访问第 9 个元素,则需要访问该数组的第 11 个元素。回到插值位,sub2ind 是至关重要的,如果您想矢量化访问图像中的元素以进行插值而不执行任何 for 循环。因此,如果您查看伪代码的最后一行,我们希望访问 r、c、r+1 和 c+1 处的元素。请注意,所有这些都是 2D 数组,其中所有这些数组中每个匹配位置中的每个元素都告诉我们我们需要从中采样的四个像素,以便生成最终输出像素。 sub2ind 的输出将也是与输出图像大小相同的二维数组。这里的关键是r、c、r+1 和 c+1 的 2D 数组的每个元素都会为我们提供我们想要的图像的 column-major 索引访问,并将其作为输入输入到图像中进行索引,我们将准确地获得我们想要的像素位置。
在实现算法时,我想补充一些重要的细节:
-
您需要确保在图像外部进行插值时访问图像的任何索引都设置为 1 或行数或列数,以确保不会超出范围。实际上,如果您延伸到图像的右侧或下方,则需要将其设置为 低于 最大值,因为插值要求您将像素访问到右侧或下方的上方。这将确保您仍在范围内。
-
您还需要确保将输出图像转换为与输入图像相同的类。
-
我运行了一个for 循环来单独插入每个通道。您可以使用bsxfun 智能地执行此操作,但为了简单起见,我决定使用for 循环,以便您能够跟随算法。
作为展示此工作的示例,让我们使用作为 MATLAB 系统路径一部分的 onion.png 图像。此图像的原始尺寸为135 x 198。让我们通过放大这张图片来插入这张图片,转到270 x 396,它是原始图片大小的两倍:
im = imread('onion.png');
out = bilinearInterpolation(im, [270 396]);
figure;
imshow(im);
figure;
imshow(out);
上面的代码将通过将每个维度增加两倍来对图像进行插值,然后显示一个带有原始图像的图形和另一个带有放大图像的图形。这是我得到的:
同样,让我们将图像缩小一半:
im = imread('onion.png');
out = bilinearInterpolation(im, [68 99]);
figure;
imshow(im);
figure;
imshow(out);
请注意,135 的一半是 67.5 的行,但我四舍五入到 68。这是我得到的:
我在实践中注意到的一件事是,与其他方案(如双三次......甚至Lanczos)相比,使用双线性进行上采样具有不错的性能。但是,当您缩小图像时,因为您要删除细节,所以最近邻就足够了。我发现双线性或双三次过于矫枉过正。我不确定你的应用程序是什么,但可以尝试不同的插值算法,看看你喜欢什么结果。双三次是另一个故事,我将把它留给你作为练习。如果你有兴趣,我推荐给你的那些幻灯片确实有关于双三次插值的材料。
祝你好运!