最近邻插值和双线性插值的基本原理以及OpenCV和Matlab中的双线性插值

原文地址:http://blog.csdn.net/andrew659/article/details/4818988算法

http://blog.csdn.net/xjz18298268521/article/details/51220576
编程


图像的缩放很好理解,就是图像的放大和缩小。传统的绘画工具中,有一种叫作“放大尺”的绘画工具,画家经常使用它来放大图画。固然,在计算机上,咱们再也不须要用放大尺去放大或缩小图像了,把这个工做交给程序来完成就能够了。下面就来说讲计算机怎么来放大缩小图象;在本文中,咱们所说的图像都是指点阵图,也就是用一个像素矩阵来描述图像的方法,对于另外一种图像:用函数来描述图像的矢量图,不在本文讨论之列。
越是简单的模型越适合用来举例子,咱们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每一个象素的取值能够是 0-255,表明该像素的亮度,255表明最亮,也就是白色,0表明最暗,即黑色。假如图像的象素矩阵以下图所示(这个原始图把它叫作源图,Source):
234   38    22
67     44    12
89     65    63数据结构

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

  ---------------------->X
  |
  |
  |
  |
  |
∨Y函数

若是想把这副图放大为 4X4大小的图像,那么该怎么作呢?那么第一步确定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,以下所示,固然,矩阵的每一个像素都是未知数,等待着咱们去填充(这个将要被填充的图的叫作目标图,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  spa

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

双线性内插值算法描述以下:
  对于一个目的像素,设置坐标经过反向变换获得的浮点坐标为(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)所对应的周围四个像素的值决定,即:blog

  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)                          公式1ip

其中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也体现出了这一特色。


双线性插值

      假设源图像大小为mxn,目标图像为axb。那么两幅图像的边长比分别为:m/a和n/b。注意,一般这个比例不是整数,编程存储的时候要用浮点型。目标图像的第(i,j)个像素点(i行j列)能够经过边长比对应回源图像。其对应坐标为(i*m/a,j*n/b)。显然,这个对应坐标通常来讲不是整数,而非整数的坐标是没法在图像这种离散数据上使用的。双线性插值经过寻找距离这个对应坐标最近的四个像素点,来计算该点的值(灰度值或者RGB值)。

  若图像为灰度图像,那么(i,j)点的灰度值的数学计算模型是:

f(x,y)=b1+b2x+b3y+b4xy

其中b1,b2,b3,b4是相关的系数。关于其的计算过程以下以下:

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

clip_image001

 

附:维基百科--双线性插值:

      双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。

假如咱们想获得未知函数 f 在点 P=\left( x, y\right) 的值,假设咱们已知函数 f 在 Q_{11} = \left( x_1, y_1 \right)Q_{12} = \left( x_1, y_2 \right)Q_{21} = \left( x_2, y_1 \right), 及 Q_{22} = \left( x_2, y_2 \right) 四个点的值。

首先在 x 方向进行线性插值,获得

f(R_1) \approx \frac{x_2-x}{x_2-x_1} f(Q_{11}) + \frac{x-x_1}{x_2-x_1} f(Q_{21}) \quad\mbox{Where}\quad R_1 = (x,y_1),
f(R_2) \approx \frac{x_2-x}{x_2-x_1} f(Q_{12}) + \frac{x-x_1}{x_2-x_1} f(Q_{22}) \quad\mbox{Where}\quad R_2 = (x,y_2).

而后在 y 方向进行线性插值,获得

f(P) \approx \frac{y_2-y}{y_2-y_1} f(R_1) + \frac{y-y_1}{y_2-y_1} f(R_2).

这样就获得所要的结果 f \left( x, y \right),

f(x,y) \approx \frac{f(Q_{11})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y_2-y) + \frac{f(Q_{21})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y_2-y)
+ \frac{f(Q_{12})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y-y_1) + \frac{f(Q_{22})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y-y_1).

若是选择一个坐标系统使得 f 的四个已知点坐标分别为 (0, 0)、(0, 1)、(1, 0) 和 (1, 1),那么插值公式就能够化简为

f(x,y) \approx f(0,0) \, (1-x)(1-y) + f(1,0) \, x(1-y) + f(0,1) \, (1-x)y + f(1,1) xy.

或者用矩阵运算表示为

f(x,y) \approx \begin{bmatrix}1-x & x \end{bmatrix} \begin{bmatrix}f(0,0) & f(0,1) \\f(1,0) & f(1,1) \end{bmatrix} \begin{bmatrix}1-y \\y \end{bmatrix}

这种插值方法的结果一般不是线性的,线性插值的结果与插值的顺序无关。首先进行 y 方向的插值,而后进行 x 方向的插值,所获得的结果是同样的。

OpenCV和Matlab中的双线性插值

   这部分的前提是,你已经明白什么是双线性插值而且在给定源图像和目标图像尺寸的状况下,能够用笔计算出目标图像某个像素点的值。固然,最好的状况是你已经用某种语言实现了网上一大堆博客上原创或转载的双线性插值算法,而后发现计算出来的结果和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

 

利用上述公式,将获得正确的双线性插值结果。