一、插值与图像缩放
首先举个例子说明插值过程,先看看matlab的插值函数
interp()
吧:
x = -2 : 1 : 2;
y = -2 : 1 : 2;
[X, Y] = meshgrid(x, y);
Z = -X.^2 - Y.^2;
p = -2 : 0.33 : 2;
q = -2 : 0.33 : 2;
[P, Q] = meshgrid(p, q);
V1 = interp2(X, Y, Z, P, Q, 'nearest'); % 最邻近插值
V2 = interp2(X, Y, Z, P, Q, 'linear'); % 双线性插值
V3 = interp2(X, Y, Z, P, Q, 'spline'); % 球面插值
figure
subplot(221)
surf(X, Y, Z);
colormap('autumn')
title('z = x^2 + y^2')
subplot(222)
surf(P, Q, V1);
colormap('autumn')
title('z = x^2 + y^2,最邻近插值')
subplot(223)
surf(P, Q, V2);
colormap('autumn')
title('z = x^2 + y^2,是双线性插值')
subplot(224)
surf(P, Q, V3);
colormap('autumn')
title('z = x^2 + y^2,球面插值')
我们先绘制一个粗糙的曲面,然后通过不同的方式插入一些点,得到的图像如下:
可见,不同的插值效果不一样,图一很粗糙,绘图点数少,图二为最邻近插值,得到类似 ‘阶跃’ 的效果,而图三图四的曲面过去看起来更加圆滑。类比到图像,使用最邻近插值得到的图像相当于把像素点放大了,而使用双线性插值和球面插值相当于让图像像素点之间过度更加平滑。
二、最近邻插值
1、原理
最近邻插值,是将距离目标点最近的像素点的值作为插值的值
如图为将一张MxN的图像src放大到PxQ的图像dst,我们要创建一个新的PXQ的数组,像素点中随便取一个点A(i, j),假设我们使用的灰度图,那么A点的灰度值应该是多少呢?
首先我们把P点的坐标映射到原图像中(不知道说映射贴不贴切 😂),假设是B点,那么我们可以类比相似三角形的相似性质计算出B点的坐标(x, y):
(x, y) = (M/P*i, N/Q*j)
由于数组的索引只能是整数,所以需要将x和y化为整数,使用最近邻插值时,我们可以四舍五入:
x = round(M/P*i);
y = round(N/Q*j));
dst(i, j) = src(x, y);
于是我们得到了计算新图像每个像素点灰度值的方法,来一个循环就可以构建新图像。(对于rgb类型的图像,我们可以使用矩阵运算,一次性对rgb三个分量进行插值)
2、代码实现
话不多说,我们看看代码效果吧~
注意
: 代码的第十行
dst = zeros(row, col, color, class(src));
创建了一个row*col的彩色图像,类型是uint8
clear all
src = imread('lena_color_256.tif');
ratio = 3;
[row, col, color] = size(src);
row = round(ratio * row);
col = round(ratio * col);
% 生成新的图片
dst = zeros(row, col, color, class(src));
for i = 1 : row
for j = 1 : col
x = round(i / ratio);
y = round(j / ratio);
if x == 0
x = x + 1;
end
if y == 0
y = y + 1;
end
dst(i, j, : ) = src(x, y, :);
end
end
figure
imshow(src)
figure
imshow(dst)
原图像为256×256的彩色图像:
使用最邻近插值后变成大小为768×768的彩色图(建议点击放大观察):
三、双线性插值
1、原理
双线性插值不是只考虑距离最近的像素点,而是考虑周围四个像素点的加权值
如图,假设ABCD均为相邻的像素点,而(x, y)落在中间
插值时考虑距离的影响,离像素点越远,则权值越小,比如说图中的A点,考虑到ABCD为边长为1的正方形,可以用(1-dx)(1-dy)表示A点灰度值的权值(详细的证明可以参考大佬的博客),其中dx和dy分别表示x和y方向的距离
假设ABCD处的灰度分别是a, b, c和d,于是我们可以得到(x, y)处的灰度应该是:
i
n
t
e
n
s
i
t
y
=
(
1
−
d
x
)
(
1
−
d
y
)
a
+
(
1
−
d
x
)
d
y
b
+
d
x
(
1
−
d
y
)
c
+
d
x
d
y
d
)
intensity = (1-dx)(1-dy)a + (1-dx)dy b + dx(1-dy) c + dxdy d)
i
n
t
e
n
s
i
t
y
=
(
1
−
d
x
)
(
1
−
d
y
)
a
+
(
1
−
d
x
)
d
y
b
+
d
x
(
1
−
d
y
)
c
+
d
x
d
y
d
)
在matlab中,ABCD的坐标可以勇敢取整函数来获得,
注意在matlab中,图像的数组从1开始,应该用向上取整,并且考虑数组越界
设 A(x1, y1), B(x1, y2), C(x2, y1), D(x2, y2)
x1 = ceil(M/P*i);
y1 = ceil(N/Q*j);
x2 = ceil(M/P*i) + 1;
y2 = ceil(N/Q*j) + 1;
2、代码实现
原图像为256×256的彩色,使用双线性插值:
需要注意的点
- 数组越界判断
- 图像下标是从1开始的,所以直接使用向上取整
clear all
src = imread('lena_color_256.tif');
ratio = 3;
[row, col, color] = size(src);
row = round(ratio * row);
col = round(ratio * col);
% 生成新的图片
dst = zeros(row, col, color, class(src));
for i = 1 : row
for j = 1 : col
x = i / ratio;
y = j / ratio;
x1 = ceil(x); % 向上取整
y1 = ceil(y);
x2 = ceil(x) + 1;
y2 = ceil(y) + 1;
if x2 >= size(src, 1) % 溢出检查
x2 = x2 - 1;
end
if y2 >= size(src, 2)
y2 = x2 - 1;
end
du = (x+1) - x1;
dv = (y+1) - y1;
dst(i, j, :) = (1-du)*(1-dv)*src(x1, y1, :) + (1-du)*dv*src(x1, y2, :) + du*(1-dv)*src(x2, y1, :) + du*dv*src(x2, y2, :);
end
end
figure
imshow(src)
figure
imshow(dst)
结果为768×768的图像(建议点击放大观察):
注意对比两种插值方式的差别
- 使用最近邻插值得到的图像由于相邻像素点的值可能一样,所以图像类似加上了马赛克,可以类比函数interp2对曲面的插值效果
- 使用双线性插值法得到的图像看起来更光滑,但是由于双线性插值后,图像失去了部分高频成分,所以图像看起来有点模糊
完结 🍻~
发现数字图像处理还是很有趣的 ( ̄︶ ̄)↗