我找到了一些放大图像的方法,但没有缩小图像的方法。我目前正在使用最近邻法。如何在不使用MATLAB中的imresize
函数的情况下使用双线性插值来实现这一点?
答案 0 :(得分:56)
在您的评论中,您提到要使用双线性插值调整图像大小。请记住,双线性插值算法与大小无关。您可以很好地使用相同的算法来放大图像以及缩小图像。采样像素位置的正确比例因子取决于您指定的输出尺寸。这个不会改变核心算法。
在开始使用任何代码之前,我会将您推荐给Richard Alan Peters' II digital image processing slides on interpolation,特别是幻灯片#59。对于如何进行MATLAB友好的双线性插值,它有很好的说明和伪代码。为了自成一体,我将把他的幻灯片包含在这里,以便我们可以跟进并编写代码:
让我们写一个能为我们做这件事的功能。此功能将采用可以是彩色或灰度的图像(通过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)
坐标的2D网格,其中图片中的每个位置都有一对(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
的输出也是与输出图像大小相同的2D数组。这里的关键是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.这是我得到的:
我在实践中注意到的一件事是,与其他方案(如bicubic ......甚至Lanczos)相比,使用双线性进行上采样具有不错的性能。但是,当您缩小图像时,因为您要删除细节,最近的邻居就足够了。我发现双线性或双三次有点矫枉过正。我不确定您的应用程序是什么,但可以使用不同的插值算法,并从结果中查看您喜欢的内容。 Bicubic是另一个故事,我会把它留给你作为练习。如果你感兴趣的话,我推荐你的那些幻灯片确实有关于双三次插值的材料。