2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 双线性插值实现图像缩放详解

双线性插值实现图像缩放详解

时间:2018-09-03 05:03:34

相关推荐

双线性插值实现图像缩放详解

目录

双线性插值(Bilinear Interpolation)线性插值双线性插值深入理解双线性插值我的插值理解代码官方插值办法源图像和目标图像几何中心的对齐将浮点运算转换成整数运算代码效果对比再次深入理解引用

双线性插值(Bilinear Interpolation)

双线性插值是实现上采样的一种手段,关于它的知识其实非常非常简单,但是很多博客写的比较晦涩难懂,这里简单介绍一下双线性插值究竟做了什么。首先双线性插值是用来放大图像的,但是我们都知道既然要放大图像,其实是对图像的像素进行扩充,而双线性插值就是使用已有的像素点来计算其他像素点从而实现像素的扩充,也就是说双线性插值是在像素层面的计算。下面让我们先来看看什么是线性插值:

线性插值

先讲一下线性插值:已知数据 (x0, y0) 与 (x1, y1),要计算 [x0, x1] 区间内某一位置 x 在直线上的y值(反过来也是一样,略):

上面比较好理解吧,仔细看就是用x和x0,x1的距离作为一个权重,用于y0和y1的加权。双线性插值本质上就是在两个方向上做线性插值。上面讲的实在是很清楚了,再来让我们看看双线性插值,其实没什么了不起的,就是从两个方向进行了插值。

双线性插值

这种方法特点是不需要进行学习,运行速度快,操作简单。只需要设置好固定的参数值即可,设置的参数就是中心值需要乘以的系数。双线性插值,这个名字咋一听很高大上的样纸,再在维基百科上一查(见文末,我去,一堆的公式吓死人),像俺这种半文盲,看到公式脑子就懵的类型,真心给跪。虽然看着好复杂,但仔细一看道理再简单不过了,所以还是自己梳理一下好。双线性插值,顾名思义就是两个方向的线性插值加起来(这解释过于简单粗暴,哈哈)。所以只要了解什么是线性插值,分别在x轴和y轴都做一遍,就是双线性插值了。如A点坐标(0,0),值为3,B点坐标(0,2),值为5,那要对坐标为(0,1)的点C进行插值,就让C落在AB线上,值为4就可以了。但是如果C不在AB的线上肿么办捏,所以就有了双线性插值。如图,

已知Q12,Q22,Q11,Q21,但是要插值的点为P点,这就要用双线性插值了,首先在x轴方向上,对R1和R2两个点进行插值,这个很简单,然后根据R1和R2对P点进行插值,这就是所谓的双线性插值。方法:

咱们看一下完整公式推导:

下面重点来了:

由于咱们的像素间隔都是1,所以括号外面的分母(x2-x1)(y2-y1)=1,所以

上式=f(Q11)(x2−x)(y2−y)+f(Q21)(x−x1)(y2−y)+f(Q12)(x2−x)(y−y1)+f(Q22)(x−x1)(y−y1)上式=f(Q11)(x2-x)(y2-y)+f(Q21)(x-x1)(y2-y)+f(Q12)(x2-x)(y-y1)+f(Q22)(x-x1)(y-y1)上式=f(Q11)(x2−x)(y2−y)+f(Q21)(x−x1)(y2−y)+f(Q12)(x2−x)(y−y1)+f(Q22)(x−x1)(y−y1)

我们注意观察(x2−x)(y2−y)(x2-x)(y2-y)(x2−x)(y2−y)这个,其实x2−x=1−(x−floor(x))x2-x=1-(x-floor(x))x2−x=1−(x−floor(x)),floor是向下取整,细细品味一下你就会明白这个等式。同理,x−x1=x−floor(x)x-x1=x-floor(x)x−x1=x−floor(x)

所以我们专门用u、vu、vu、v两个变量记录x−floor(x),y−floor(y)x-floor(x),y-floor(y)x−floor(x),y−floor(y),替换进去后,最终

上式=f(Q11)(1−u)(1−v)+f(Q21)u(1−v)+f(Q12)(1−u)v+f(Q22)uv上式=f(Q11)(1-u)(1-v)+f(Q21)u(1-v)+f(Q12)(1-u)v+f(Q22)uv上式=f(Q11)(1−u)(1−v)+f(Q21)u(1−v)+f(Q12)(1−u)v+f(Q22)uv

深入理解双线性插值

我的插值理解

通过上面的例子,其实我们能够有所感觉,所谓的插值,不过是通过周围已知的像素值来计算一些原本没有给出的像素值;所谓的线性,是指我们通过线性的方式来用已知像素值计算未知的像素值。这样的话像素值会增多,也就实现了图像的放大。当然我们也可以进行图像的缩小,一样的道理,还是进行插值,只不过需要的像素点少了而已。这样看来的话,给我们一个像素矩阵,我们应当也是会根据要求来进行插值的。现在我们来看一个图像放大的例子:

上图是原来的图像,是3*3的矩阵,而我们的任务是放大到4*4,我们首先需要建立一个坐标系,左上角为(0,0)。通过图像我们可以看到,我们要放大图像的话,其实边界都是之前图像的边界,我们只不过是将原图的边划分成更小的粒度而已,比如上图原来元素之间间隔1,共有3个元素,但我们要想在原来的长度下构成4个元素,那每个元素间隔就是2/3,新矩阵间隔计算公式为**(每边原来的元素数-1)/(目标每边的元素数-1)**,所以我们就可以画出下面的新矩阵:

黑点的间隔就是2/3,然后利用双线性插值的公式来计算上图每个黑点的像素值就好了。

代码

from PIL import Imageimport matplotlib.pyplot as pltimport numpy as npimport mathdef BiLinear_interpolation(img,dstH,dstW):scrH,scrW,_=img.shapeprint(img.shape)img=np.pad(img,((0,1),(0,1),(0,0)),'constant')print(img.shape)retimg=np.zeros((dstH,dstW,3),dtype=np.uint8)for i in range(dstH):for j in range(dstW):scrx=i*(scrH-1)/(dstH-1)scry=j*(scrW-1)/(dstW-1)x=math.floor(scrx)y=math.floor(scry)u=scrx-xv=scry-yretimg[i,j]=(1-u)*(1-v)*img[x,y]+u*(1-v)*img[x+1,y]+(1-u)*v*img[x,y+1]+u*v*img[x+1,y+1]return retimgim_path='testPict.png'image=np.array(Image.open(im_path))image2=BiLinear_interpolation(image,image.shape[0]*10,image.shape[1]*10)image2=Image.fromarray(image2.astype('uint8')).convert('RGB')image2.save('outmy_x10.png')

官方插值办法

emmm,怎么说呢,如果你读懂了我的理解之后,你应该知道,我的方法其实是相当于在原图的内部提取出更多的点,从而扩大图像。下面我用同样的例子来展示官方差值的作法:下面是将3*3扩大成4*4的图像:

有人可能会说,那边际的红点怎么推测呀,周围的蓝色的点不够4个啊,请看下图:

猜一下深蓝色的像素点是什么,没错,就是padding的像素点,像素值为0,也就是说只需要padding就好了。那么如何根据原图计算目标图的像素点分别对应原图的坐标呢?

srcX=dstX* (srcWidth/dstWidth),

srcY = dstY* (srcHeight/dstHeight)

其中(dstX,dsY)是目标图像的像素点的逻辑下标,左上角的点规定为(0,0),srcWidth是原图像的每边像素点的个数,dstWidth是目标图像的每条边像素点的个数,srcX是目标图像像素点(dstX,dstY)对应在原图像的坐标系下的实际坐标,可能不是整数,那就需要插值了。

经过几何中心对齐后的优化后的公式为:

SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5

SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5

源图像和目标图像几何中心的对齐

这是优化措施之一,针对官方的双线性插值方法,在我的方法中双线性插值一开始就是几何中心对齐的。按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下:

只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。

那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。

最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlab和openCV的做法。如下图:

分别使用上述提到的公式和几何中心对齐之后的公式进行处理后,就会分别得到上述对应的两幅图,显然使用几何中心对齐的公式更为合理。看个例子:假设源图像是33,中心点坐标(1,1)目标图像是99,中心点坐标(4,4),我们在进行插值映射的时候,尽可能希望均匀的用到源图像的像素信息,最直观的就是(4,4)映射到(1,1)现在直接计算srcX=4*3/9=1.3333!=1,也就是我们在插值的时候所利用的像素集中在图像的右下方,而不是均匀分布整个图像。现在考虑中心点对齐,srcX=(4+0.5)*3/9-0.5=1,刚好满足我们的要求。

将浮点运算转换成整数运算

这也是优化方法之一。参考图像处理界双线性插值算法的优化,直接进行计算的话,由于计算的srcX和srcY 都是浮点数,后续会进行大量的乘法,而图像数据量又大,速度不会理想,解决思路是:

浮点运算→→整数运算→→”<<左右移按位运算”。

放大的主要对象是u,v这些浮点数,OpenCV选择的放大倍数是2048“如何取这个合适的放大倍数呢,要从三个方面考虑,

第一:精度问题,如果这个数取得过小,那么经过计算后可能会导致结果出现较大的误差。

第二,这个数不能太大,太大会导致计算过程超过长整形所能表达的范围。

第三:速度考虑。假如放大倍数取为12,那么算式在最后的结果中应该需要除以1212=144,但是如果取为16,则最后的除数为1616=256,这个数字好,我们可以用右移来实现,而右移要比普通的整除快多了。”我们利用左移11位操作就可以达到放大目的。

代码

from PIL import Imageimport matplotlib.pyplot as pltimport numpy as npimport mathdef BiLinear_interpolation(img,dstH,dstW):scrH,scrW,_=img.shapeprint(img.shape)img=np.pad(img,((0,1),(0,1),(0,0)),'constant')print(img.shape)retimg=np.zeros((dstH,dstW,3),dtype=np.uint8)for i in range(dstH):for j in range(dstW):scrx=(i+0.5)*(scrH/dstH)-0.5scry=(j+0.5)*(scrW/dstW)-0.5x=math.floor(scrx)y=math.floor(scry)u=scrx-xv=scry-yretimg[i,j]=(1-u)*(1-v)*img[x,y]+u*(1-v)*img[x+1,y]+(1-u)*v*img[x,y+1]+u*v*img[x+1,y+1]return retimgim_path='testPict.png'image=np.array(Image.open(im_path))image2=BiLinear_interpolation(image,image.shape[0]*10,image.shape[1]*10)image2=Image.fromarray(image2.astype('uint8')).convert('RGB')image2.save('out0.5_x10.png')

效果对比

我分别运行了下,其实几乎分辨不出来任何区别……不过用我的方法得到的图像占空间比较大。

再次深入理解

在图像的仿射变换中,很多地方需要用到插值运算,常见的插值运算包括最邻近插值,双线性插值,双三次插值,兰索思插值等方法,OpenCV提供了很多方法,其中,双线性插值由于折中的插值效果和运算速度,运用比较广泛。

越是简单的模型越适合用来举例子,我们就举个简单的图像:3*3 的256级灰度图。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source):

234 38 22

67 44 12

89 65 63

这 个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常用的坐标系。

如果想把这副图放大为 4*4大小的图像,那么该怎么做呢?那么第一步肯定想到的是先把4*4的矩阵先画出来再说,好了矩阵画出来了,如下所示,当然,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination):

? ? ? ?

? ? ? ?

? ? ? ?

? ? ? ?

然后要往这个空的矩阵里面填值了,要填的值从哪里来来呢?是从源图中来,好,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)

好了,套用公式,就可以找到对应的原图的坐标了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0),找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去目标图的(0,0)这个位置了。

接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标,套用公式:

(1*0.75,0*0.75)=>(0.75,0) 结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,象素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,好,那么按照四舍五入的方法就得到坐标(1,0),完整的运算过程就是这样的:(1*0.75,0*0.75)=>(0.75,0)=>(1,0) 那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填入目标图中的坐标。

依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示:

234 38 22 22

67 44 12 12

89 65 63 63

89 65 63 63

这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。

双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。

双线性内插值算法描述如下:

对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)

其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。

比如,象刚才的例子,现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点。

引用

/qq_37577735/article/details/80041586/never__say__no/article/details/109081233/wojianxin/p/12516029.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。