对于一个图像,要进行放大和缩小,在我们实际的工作中看起来好像很简单。但是实际上,会遇到失真等问题特别是放大的时候。那么如果通过算法来处理放大和缩小的问题呢?这里介绍两种最基本的算法,最近邻算法和双线性内插算法。
最近邻内插
思路
在这个项目中,使用的是最近邻内插的方法。直接将放大后的坐标,通过与原图像的坐标比值一定这个条件来进行计算。直接将变化图像的每个坐标值除以放大/缩小因子,来找到在原图像中所对应位置。对于出现的小数的情况,直接进行四捨五入。
由于题目所给的图片是灰度图,所以在测试文件就就直接只进行了灰度通道的放大缩小实验。
核心
1 | for i = 1 : (row*scaleFactor) |
2 | for j= 1 : (col*scaleFactor) |
3 | x = round(i/scaleFactor); |
4 | y = round(j/scaleFactor); |
5 | if x == 0 |
6 | x = 1; |
7 | end |
8 | if y == 0 |
9 | y = 1; |
10 | end |
11 | resizedImage(i,j)=originalImage(x,y); |
12 | end |
13 | end |
效果
原始图片
缩小为1/10的图片
缩小为1/10之后的图片如下。会发现出现了许多锯齿,图片不清晰。主要问题就是这个算法不够精确。
缩小又放大的图片
将缩小为1/10的图片又放大了10倍,可以看见和原来的图片相比,多了许多锯齿,而且不清晰。主要原因就是通过这个算法在缩小和放大的过程中,都会损失一些像素点。从矩阵的角度来看,缩小为1/10再放大10倍,同时也损失了矩阵的大小,在这个过程中矩阵行列个位数大小丢失。
双线性内插
思路
在这个项目中,所使用的算法是双线性内插的方法。通过对行和列各进行一次线性内插。来得到变换之后的坐标应该对应原图片的哪个坐标。这个方法会比最近邻来的更加精确一些,但是仍然会产生误差。
双线性插值的计算公式:
$ f(i+u,j+v) = (1-u)(1-v)f(i,j)+u(1-v)f(i+1,j)+(1-u)vf(i,j+1)+ uvf(i+1,j+1) $
这个公式是通过矩阵中四个像素值来计算位于它们之间一点的新的像素值,是这个算法的关键。
相当于把放大后的图片再压回原来的大小,只是这之间会使得像素点之间的距离变小(或者相当于图片大小不变,但是像素点变多了),以及产生一些不在原来的像素点上的像素点,这些像素点就需要使用双线性插值的方法来进行计算。
核心
关键要计算出,放大/缩小之后的图片的每一个像素点,位于原来的像素点的位置,通过双线性插值的方法算出它们的新的像素值。在找到位于原来的像素点的位置的时候,就需要通过放大/缩小因子来计算新图片的每一个像素点之间的距离。
1 | ini_u = (row-1)/(scaleFactor*row-1); % 每一步相当于原图的比例 |
2 | ini_v = (col-1)/(scaleFactor*col-1); |
3 | |
4 | for i = 1:scaleFactor*row |
5 | z_u = floor((i-1)*ini_u+1); % 向左取整数 |
6 | u = (i-1)*ini_u+1 - z_u; % 小数部分 |
7 | for j=1:scaleFactor*col |
8 | z_v = floor((j-1)*ini_v+1); |
9 | v = (j-1)*ini_v+1 - z_v; |
10 | %==== bilinear interpolation formula==== |
11 | resizedImage(i,j) = (1-u) * (1-v) * originalImage(z_u, z_v) + ... |
12 | (1-u) * v * originalImage(z_u, z_v + 1) + ... |
13 | u * (1-v) * originalImage(z_u + 1, z_v) + ... |
14 | u * v * originalImage(z_u + 1, z_v+1); |
15 | end |
16 | end |
效果
缩小为1/12.5的图片
缩小后又放大的图片
可以发现,缩小之后又放大的图片还是不能像原来的照片一样。会出现一些锯齿。但是相比于最近邻内插的方法,已经有改善了,图片的清晰度有所上升。
左图为使用双线性插值方法进行缩小又放大的图片,相比于右图使用最近邻内插方法的图片明显锯齿更少。