对图像进行放缩,实际上根据原图像的像素信息推导出放缩后图像的像素信息,实际上是通过插值实现了这一问题。常用的插值算法由以下四种:
最近像素插值算法是最简单的一种插值算法,当图片放大时,缺少的像素通过直接使用与之最接近的原有的像素的颜色生成,也就是说照搬旁边的像素,这样做的结果是产生了明显可见的锯齿。
2. 双线性插值(Bilinear interpolation)
这种算法输出的图像的每个像素都是原图中四个像素(2×2)运算的结果,这种算法极大地消除了锯齿现象
3. 双三次插值算法(Bicubic interpolation)
这种算法是上一种算法的改进算法,它输出图像的每个像素都是原图16个像素(16×16)运算的结果。这种算法是一种很常见的算法,普遍用在图像编辑软件、打印机驱动和数码相机上。
4. 分形算法(Fractal interpolation)
这是Altamira Group提出的一种算法,这种算法得到的图像跟其它算法相比更清晰、锐利。
1. 线性插值
已知坐标 (x0, y0) 与 (x1, y1),要得到 [x0, x1] 区间内某一位置 x 在直线上的值。
由于 x 值已知,所以可以从公式得到 y 的值
已知 y 求 x 的过程与以上过程相同,只是 x 与 y 要进行交换。
2. 双线性插值(Bilinear Interpolation)
在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
图中:红色的数据点与待插值得到的绿色点
假如我们想得到未知函数 f 在点 P = (x, y) 的值,假设我们已知函数 f 在 Q11 = (x1, y1)、Q12 = (x1, y2), Q21 = (x2, y1) 以及 Q22 = (x2, y2) 四个点的值。
首先在 x 方向进行线性插值,得到
然后在 y 方向进行线性插值,得到
这样就得到所要的结果 f(x, y),
双线性插值在三维空间的延伸是三线性插值。
实际问题:
1.双线性插值
假设源图像大小为mxn,目标图像为axb。那么两幅图像的边长比分别为:m/a和n/b。注意,通常这个比例不是整数,编程存储的时候要用浮点型。目标图像的第(i,j)个像素点(i行j列)可以通过边长比对应回源图像。其对应坐标为(i*m/a,j*n/b)。
显然,这个对应坐标一般来说不是整数,而非整数的坐标是无法在图像这种离散数据上使用的。双线性插值通过寻找距离这个对应坐标最近的四个像素点,来计算该点的值(灰度值或者RGB值)。如果你的对应坐标是(2.5,4.5),那么最近的四个像素是(2,4)、(2,5)、(3,4),(3,5)。
若图像为灰度图像,那么(i,j)点的灰度值可以通过一下公式计算:
f(i,j)=w1*p1+w2*p2+w3*p3+w4*p4;
其中,pi(i=1,2,3,4)为最近的四个像素点,wi(i=1,2,3,4)为各点相应权值。关于权值的计算,在维基百科和百度百科上写的很明白。
2.存在的问题
这部分的前提是,你已经明白什么是双线性插值并且在给定源图像和目标图像尺寸的情况下,可以用笔计算出目标图像某个像素点的值。当然,最好的情况是你已经用某种语言实现了网上一大堆博客上原创或转载的双线性插值算法,然后发现计算出来的结果和matlab、openCV对应的resize()函数得到的结果完全不一样。
那这个究竟是怎么回事呢?
其实答案很简单,就是坐标系的选择问题,或者说源图像和目标图像之间的对应问题。
按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下:
只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。
那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。
最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlab和openCV的做法。如下图:
如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,
int x=(i+0.5)*m/a-0.5
int y=(j+0.5)*n/b-0.5
instead of
int x=i*m/a
int y=j*n/b
利用上述公式,将得到正确的双线性插值结果
总结:
总结一下,我得到的教训有这么几条。
1.网上的一些资料有的时候并不靠谱,自己还是要多做实验。
2.不要小瞧一些简单的、基本的算法,让你写你未必会写,而且其中可能还藏着一些玄妙。
3.要多动手编程,多体会算法,多看大牛写的源码(虽然有的时候很吃力,但是要坚持看)。