双线性插值法(bilinear interpolation)
前面讲解了最近邻插值法缩放图像以及不足之处,本篇介绍另外一种插值法,介绍双线性插值法之前先介绍线性插值。
1. 线性插值
线性插值是指插值函数为一次多项式的插值方式,其在插值节点上的插值误差为零。线性插值可以用来近似代替原函数,也可以用来计算得到查表过程中表中没有的数值。如图所示现在已知y=f(x)的两个点坐标分别是(x0,y0),(x1,y1),现在在区间(x0,x1)内给定任意x,如何求y,线性插值法采用图中红点的y值代替f(x)的y值。假设x处的直线上的红点坐标为(x,Y),那么Y约等于y。根据图可以得到公式:
用y0,y1表示得到
公式很好记,将分式看做权重系数。
2. 双线性插值法
双线性插值法也叫双线性内插,其核心思想是在两个方向分别进行一次线性插值。双线性插值作为数值分析中的一种插值算法,广泛应用在信号处理,数字图像和视频处理等方面。
如坐标图所示,用横纵坐标代表图像像素的位置,f(x,y)代表该像素点(x,y)的彩色值或灰度值。
将图像放大或缩小,目的像素dst对应的原像素src中的坐标转换公式如下,公式很好理解,可参考上一章最近邻插值法。
srcX=dstX*(srcWidth/dstWidth)
srcY=dstY*(srcHeight/dstHeight)
上式中,dstX与dstY为目标图像的某个像素的横纵坐标,dstWidth与dstHeight为目标图像的长与宽;srcWidth与srcHeight为原图像的宽度与高度。srcX,srcY为目标图像在该点(dstX,dstY)对应的原图像的坐标。
现在假设目标图像的像素点(x’,y’)映射到原图像中是(x,y),也就是图中的P点。设Q11 = (x1, y1)、Q12 = (x1, y2)、Q21 = (x2, y1) 、 Q22 = (x2, y2),图中Q11,Q12,Q21,Q22分别为距离P点的最近的四个点。分别在X方向进行两次插值,最后在y方向进行插值即可得到目标图像的像素值。公式如下:
先计算X方向(也可以先计算Y方向,再计算X方向)的线性插值:
再在y方向进行线性插值得到f(P):
经过公式的进一步化简可以得到:
由于是最近的点,所以Q的四个点的坐标之间相差1,故进一步化简为:
现假设x=i+u,y=j+v,i,j为整数,u,v为小数。得到下式:
下面为本人自己设计的例子与具体计算过程:
将图像放大两倍(对像素数扩大2倍),以?处的像素为例:
右边图中问号的坐标为(5,4),映射到原图像的像素点的坐标为
srcX=dstX*(srcWidth/dstWidth)=5*(4/8)=2.5
srcY=dstY*(srcHeight/dstHeight)=4*(4/8)=2
(srcX,srcY)=(2+0.5,2+0),u=0.5,v=0,映射的src坐标及对应的最近的四个Q点下如图所示。若x=i+u,y=j+v。则可以得到红色框的像素及其周围四个Q点(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1),即可以得到?处的像素值。
每个像素点的RGB对应如下:
204,255,153 153,255,153 102,255,153 0,255,153
204,255,102 153,255,102 102,255,102 0,255,0
204,255,51 153,255,51 102,255,51 51,204,51
204,204,0 153,204,0 102,153,0 0,153,0
Q11(2,2),Q21(2,3),Q12(3,2),Q22(3,3),P(2.5,2)
采用双线性插值法得到红色框处的像素值:
R=0.5*102+0.5*51=51+25.5=76.5
G=0.5*255+0.5*204=127.5+102=229.5
B=0.5*51+0.5*51=25.5+25.5=51
由于颜色表示范围为0-255整数,R可以用127代替或128代替,B也是。所以问号处的颜色应该为:
然后以同样的方式计算得到所有的像素值,放大后的图像对比如下:
当出现i或j为边界坐标时,会出现(i+1,j)、(i,j+1)、(i+1,j+1)的点在实际的图中不存在,此时可以用(i,j)、(i-1,j)、(i,j-1)、(i-1,j-1)。如果srcX与srcY是整数,则其中一个点是(i,j),剩余的3个点其实无所谓,因为u,v为0。f(p)=f(i,j)
3. 代码实现
python实现:
from matplotlib import pyplot as plt import numpy as np def bilinear_interpolation(srcimage_vector,dstwidthtimes ,dstheighttimes ): print("原始图像",srcimage_vector) srcHeight=srcimage_vector.shape[0] srcWidth=srcimage_vector.shape[1] print("srcHeight",srcHeight) print("srcWidth",srcWidth) dstWidth, dstHeight=int(dstwidthtimes*srcWidth),int(dstheighttimes*srcHeight) # 定义一个三维矩阵存储目标图像,每个像素由RGB三种颜色组成,shape接受一个元组,可以创建多维矩阵 dstVector = np.zeros(shape=(dstHeight, dstWidth, 3), dtype=int) # 默认是float64 # 遍历目标图像的每一个像素 for dstY in range(0,dstHeight): for dstX in range(0,dstWidth): Q = [(0, 0)] * 4 # Q11=Q12=Q21=Q22=0 Qdict = {} # 坐标换算 dstX_srcX = dstX * (srcWidth / dstWidth) # python中/表示除法,//表示整除 dstY_srcY = dstY * (srcHeight / dstHeight) u = round(dstX_srcX % 1, 2) v = round(dstY_srcY % 1, 2) if int(dstX_srcX)==srcWidth-1: positionX = [int(dstX_srcX)-1, int(dstX_srcX)] # 左右范围 else: positionX=[int(dstX_srcX),int(dstX_srcX)+1]#左右范围 if int(dstY_srcY)==srcHeight-1: positionY = [int(dstY_srcY)-1, int(dstY_srcY)] # 上下范围 else: positionY = [int(dstY_srcY), int(dstY_srcY) + 1] #上下范围 k=0 for m in range(0, 2): # 得到Q的四个点坐标,分别是Q11,Q12,Q21,Q22 for n in range(0, 2): Q[k] = (positionX[m], positionY[n]) Qdict[Q[k]] = srcimage_vector[positionY[n], positionX[m]] k = k + 1 # 通过四个点计算得到dst的像素值 dstVector[dstY][dstX] = Qdict[Q[0]] * (1 - u) * (1 - v) + Qdict[Q[1]] * (1 - u) * (v) + Qdict[Q[2]] * (u) * ( 1 - v) + Qdict[Q[3]] * (u) * (v) print(dstVector) ax = plt.gca() # 获取到当前坐标轴信息 ax.xaxis.set_ticks_position('top') # 将X坐标轴移到上面 plt.imshow(dstVector) # 显示数组 plt.show() plt.imsave('shaungxianxing.jpg', dstVector.astype(np.uint8)) # matplotlib保存图像的时候,只接受浮点数或者unit8的整数类型 if __name__=="__main__": # imagePath = r'songshu.jpg' imagePath = r'cup.jpg' # imagePath = r'aoyunwuhuan.jpg' image_vector = plt.imread(imagePath) # dstwidth,dstheight用倍数表示,表示是src图像的多少倍 # dstwidth, dstheight=1.5,1.5 dstwidth, dstheight=0.2,0.2 bilinear_interpolation(image_vector,dstwidth,dstheight)
C++实现:后面补充。
4.效果
自己制作的奥运五环
原图双线性插值法放大2倍后的图像
邻近法放大2倍后的图像
网上找的松鼠:
原图
放大2倍后
缩小0.2倍后
手机拍的图
原图
缩小0.2倍
结论:通过图可以看出,双线性插值法,锯齿状比邻近法效果好多了,但是也会伴随着图片的变模糊。
5.存在问题及改进:
关于中心问题,后面补充
小结:由于采用线性权重计算新的像素值,所以双线性插值法与最近邻不同,会产生中间值,也就是会产生新的像素值。