在介绍双线性插值之前,先讲一下线性插值:已知数据(x0,y0),要计算[x0,x1]区间内某一位置x在直线上的y值:
上面这个比较好理解,仔细看就是用x和x0,x1的距离作为一个权重,用于y0和y1的加权。双线性插值本质就是在两个方向上做线性插值。
接下来介绍双线性插值,假设源图像大小为m*n,目标图像为a*b。那么两幅图像的边长比为:m/a和n/b。在程序设计时要用浮点型来保存。则目标图像的第(i,j)个像素点可以通过边长比回溯到源图像。其对应坐标为(i*m/a,j*n/b);
显然,这个坐标不是整数,而非整数坐标在图像这种离散数据类型上无法使用。所以,双线性插值通过取得距离这个坐标最近的四个像素点来计算该点的灰度值。例如:计算所得对应坐标是(1.2,2.6),那么醉经的四个像素为(1,2),(1,3),(2,2),(2,4)。
若图像为灰度图像,那么(i,j)点的灰度值可以通过以下公式计算:
f(i,j)=w1*p1+w2*p2+w3*p3+w4*p4; 公式(1-1)
其中,pi(i=1,2,3,4)为最近的四个像素点,wi(i=1,2,3,4)为各点相应权值,在下文中会介绍如何计算权值。
在上面中介绍了线性插值的数学原理,这里介绍下双线性插值的数学原理:(引用维基百科定义)
双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
假如我们想得到未知函数 在点 的值,假设我们已知函数 在 , , ,
及 四个点的值。
首先在 x 方向进行线性插值,得到
然后在 y 方向进行线性插值,得到
这样就得到所要的结果 ,
如果选择一个坐标系统使得 的四个已知点坐标分别为
(0, 0)、(0, 1)、(1, 0) 和 (1, 1),那么插值公式就可以化简为
合并有关项,f(x,y)=(f(0,0)(1-x)+f(1,0)x)
(1-y)+(f(0,1)(1-x)+f(1,1)x)y 公式(1-2)
这个时候就解释了公式(1-1)中的四个权重值:即(1-x)*(1-y)表示w1,x*(1-y)表示w2,同理w3,w4,。例如当坐标点为(0.75,0.75)时,明显该点更加靠近(1,1)点,所以(1,1)所起的作用更大一些。从权值w4
= x*y = 0.75 * 0.75就可以体现出来,而(0.75,0.75)离(0,0)最远。所以起作用就小一些,从权值w1 =
(1-x)*(1-y) = (1-0.75)(1-0.75)就可以体现出来。
或者用矩阵运算表示为
这种插值方法的结果通常不是线性的,线性插值的结果与插值的顺序无关。首先进行 y 方向的插值,然后进行 x 方向的插值,所得到的结果是一样的。
存在问题:
(1)当通过上述算法求解时,所得结果和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
代替
int
x=i*m/a
int
y=j*n/b
这样就可以得到理想的结果了。
(2)考虑到图像的特殊性,他的像素值的计算结果需要落在0到255之间,最多只有256种结果,由上式可以看出,一般情况下,计算出的f(x,y)是个浮点数,我们还需要对该浮点数进行取整。因此,我们可以考虑将该过程中的所有类似于1-x、1-y的变量放大合适的倍数,得到对应的整数,最后再除以一个合适的整数作为插值的结果。
如何取这个合适的放大倍数呢,要从三个方面考虑,第一:精度问题,如果这个数取得过小,那么经过计算后可能会导致结果出现较大的误差。第二,这个数不能太大,太大会导致计算过程超过长整形所能表达的范围。第三:速度考虑。假如放大倍数取为12,那么算式在最后的结果中应该需要除以12*12=144,但是如果取为16,则最后的除数为16*16=256,这个数字好,我们可以用右移来实现,而右移要比普通的整除快多了。
综合考虑上述三条,我们选择2048这个数比较合适。
参考代码:
NormalText Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include #include
#include
#include
using namespace cv;
using namespace std;
int main()
{
//读入图片,注意图片路径 Mat srcimage=imread("F:\\Matlab\\pictures\\Three.jpg"); cvtColor(srcimage,srcimage,CV_BGR2GRAY);
Mat dstimage(Size(200,200),srcimage.type(),Scalar(0));
//图片读入成功与否判定 if(!srcimage.data || !dstimage.data) { cout<
imshow("srcImage",srcimage);
float scaleX = 1.0*srcimage.cols/dstimage.cols;
float scaleY = 1.0*srcimage.rows/dstimage.rows;
float srcX,srcY;
int indexX,indexXs,indexY,indexYs,index,indexXss,indexYss;
int pixelOne,pixelTwo,pixelThree,pixelFour; //对应四个像素点灰度值
for (int j=0;j
{
srcX = (j+0.5)*scaleX - 0.5;
indexX = (int)srcX; //向下取整; indexXs = (srcX - indexX)*2048; //对应公式(1-2)中的x
indexXss = 2048 - indexXs; //对应公式(1-2)中的1-x
for (int i=0;i
{