双线性插值
1.线性插值
已知坐标(x 0, y 0)与(x 1, y 1),要得到[ x 0, x 1 ]区间内某一位置 x 在直线上的值。
由于 X 值已知,所以可以从公式得到ÿ的值
已知 ÿ 求 X 的过程与以上过程相同,只是 X 与 ý 要进行交换。
2.双线性插值(Bilinear Interpolation)
在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
图中:红色的数据点与待插值得到的绿色点
假如我们想得到未知函数 ˚F 在点 P =(X, ÿ)的值,假设我们已知函数 ˚F 在 Q 11 =(X 1, ÿ 1),Q 12 =(X 1, ÿ 2), Q 21 =(x 2, y 1)以及 Q 22 =(x 2, y 2)四个点的值。
首先在 X 方向进行线性插值,得到
然后在 ÿ 方向进行线性插值,得到
这样就得到所要的结果 f(x, y),
-
-
最近在编程时用到了双线性插值算法,对图像进行缩放。网上有很多这方面的资料,介绍的也算明白。但是,这些文章只介绍了算法,并没有具体说怎么实现以及怎么实现最好,举个例子,你可以按照网上文章的算法自己写一个双线性插值程序,用它对一张图片进行处理,然后再用MATLAB或者OpenCV的中的大小调整函数对同一张图片进行处理,得到的结果是不一样的,如果源图片较小,效果差距就更大以下是对于双线性插值的讲解以及上述现象的解释:
1.双线性插值
假设源图像大小为m×n个,目标图像为AXB那么两幅图像的边长比分别为:M / A和N / B注意,通常这个比例不是整数,编程存储的时候要用浮点型目标。图像的第(I,J)个像素点(I行列)可以通过边长比对应回源图像。其对应坐标为(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的的对应的调整大小()函数得到的结果完全不一样。
那这个究竟是怎么回事呢?
其实答案很简单,就是坐标系的选择问题,或者说源图像和目标图像之间的对应问题。
按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的的的图像缩小成3× 3的,那么源图像和目标图像各个像素之间的对应关系如下:
只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。
那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。
最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是MATLAB和OpenCV的中的做法如下图:
如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,
int x =(i + 0.5)* m / a-0.5
int y =(j + 0.5)* n / b-0.5
代替
int x = i * m / a
int y = j * n / b
利用上述公式,将得到正确的双线性插值结果
总结:总结一下,我得到的教训有这么几条。
1.网上的一些资料有的时候并不靠谱,自己还是要多做实验。
2.不要小瞧一些简单的,基本的算法,让你写你未必会写,而且其中可能还藏着一些玄妙。
3.要多动手编程,多体会算法,多看大牛写的源码(虽然有的时候很吃力,但是要坚持看)。
由于图像双线性插值只会用相邻的4个点,因此上述公式的分母都是1.opencv中的源码如下,用了一些优化手段,比如用整数计算代替浮子(下面代码中的* 2048就是变11位小数为整数,最后有两个连乘,因此>> 22位),以及源图像和目标图像几何中心的对齐
SrcX =(dstX + 0.5)*(srcWidth / dstWidth)-0.5SrcY =(dstY + 0.5)*(srcHeight / dstHeight)-0.5 ,
在图像处理的时候,我们先根据
srcX = dstX *(srcWidth / dstWidth),
srcY = dstY *(srcHeight / dstHeight)
来计算目标像素在源图像中的位置,这里计算的srcX和srcY一般都是浮点数,比如f(1.2,3.4)这个像素点是虚拟存在的,先找到与它临近的四个实际存在的像素点
(1,3)(2,3)
(1,4)(2,4)
写成F(1 + U,J + v)的形式,则U = 0.2,v = 0.4,I = 1,J = 3
在沿着X方向差插值时,F(R 1)= U(F(Q 21)-f(Q 11))+ f(Q 11)
沿着Y方向同理计算或直接
整理一步计算,f(i + u,j + v)=(1-u)(1-v)F (1 + 1,J)+ UVF第(i + 1,J + 1)
,其中 F(I,J + 1)+ U(1-v)加速以及优化策略
。单纯按照上文实现的插值算法只能勉强完成插值的功能,速度和效果都不会理想,在具体代码实现的时候有些小技巧参考的OpenCV的源码以及网上博客整理如下两点:- 源图像和目标图像几何中心的对齐。
- 将浮点运算转换成整数运算
3.1源图像和目标图像几何中心的对齐
方法:在计算源图像的虚拟浮点坐标的时候,一般情况:
srcX = dstX *(srcWidth / dstWidth),
srcY = dstY *(srcHeight / dstHeight)
中心对齐(OpenCV也是如此):
SrcX =(dstX + 0.5 )*(srcWidth / dstWidth)-0.5
SrcY =(dstY + 0.5)*(srcHeight / dstHeight)-0.5
原理:双线性插值算法及需要注意事项这篇博客解释说“如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。“有点我保持
疑问。将公式变形,SRCX = dstX *(srcWidth / dstWidth)+ 0.5 *(srcWidth / dstWidth-1)
相当于我们在原始的浮点坐标上加上了0.5 *(srcWidth / dstWidth-1)这样一个控制因子,这项的符号可正负,与srcWidth / dstWidth的比值也就是当前插值是扩大还是缩小图像有关,有什么作用呢看一个例子:假设源图像是3×3,中心点坐标(1,1)目标图像是9×9,中心点坐标(4,4),我们在进行插值映射的时候,尽可能希望均匀(4,4)现在直接计算SRCX = 4 * 3/9 = 1.3333!= 1,也就是我们在插值的时候所利用的像素集中在 图像的右下方,而是均匀分布整个图像。现在考虑中心点对齐,SRCX =(4 + 0.5)* 3 / 9-0.5 = 1,刚好满足我们的要求。
3.2浮点将运算转换分类中翻译整数运算
参考图像处理界双线性插值算法的优化直接
进行计算的话教育,由于计算的SRCX和srcY都是浮点数,后续会进行大量的乘法,而图像数据量又大,速度不会理想,解决思路是:浮点运算→→整数运算→→“<<左右移按位运算”。
放大的主要对象是U,V这些浮点数,OpenCV的选择的放大倍数是2048“如何取这个合适的放大倍数呢,要从三个方面考虑,第一:精度问题,如果这个数取得过小,那么经过计算后可能会导致结果出现较大的误差第二,这个数不能太大,太大会导致计算过程超过长整形所能表达的范围第三:速度考虑假如放大倍数取为12,那么算式在最后的结果中应该需要除以12 * 12 = 144,但是如果取为16,则最后的除数为16 * 16 = 256,这个数字好,我们可以用右移来实现,而右移要比。普通的整除快多了”我们利用左移11位操作就可以达到放大目的。
-