OpenCV图像处理——图像插值算法原理及实现
1.1 简介
在图像处理中,几何变换是将一幅图像映射到另外一幅图像内的操作,可以大概分为放缩、翻转、仿射(平移、旋转)、透视、重映射这几部分。
在几何变换时,无法给有些像素点直接赋值,例如,将图像放大两倍,必然会多出一些无法被直接映射的像素点,对于这些像素点,通过插值决定它们的值。且不同插值方式的结果不同。
在一幅输入图像 [ u , v ] [u,v] [u,v]中,灰度值仅在整数位置上有定义。然而,输出图像的坐标映射回原图像后,一般为非整数的坐标。所以输出图像 [ x , y ] [x,y] [x,y]的灰度值,一般由非整数坐标来决定,非整数坐标的像素值,就需要插值算法来进行处理。常见的插值算法有最近邻插值、双线性插值和三次样条插值。
1.2 学习目标
- 了解插值算法与常见几何变换之间的关系
- 理解插值算法的原理
- 掌握OpenCV框架下插值算法API的使用
1.3 内容介绍
- 插值算法原理介绍
-
最近邻插值算法
-
双线性插值算法
- 线性插值
- 双线性插值
- 几何中心对齐
- cv.resize()的计算过程
-
三次样条插值算法
-
- 两种映射方法
- 向前映射
- 向后映射
- OpenCV代码实践
- cv.resize()各项参数及含义
- 动手实现
- python实现
- c++实现
1.4 算法理论介绍
1.4.1 最近邻插值算法原理
最近邻插值,是指将目标图像中的点,对应到原图像中后,找到最相邻的整数坐标点的像素值,作为该点的像素值输出。
如上图所示,目标图像中的某点投影到原图像中的位置为点 P P P,与 P P P距离最近的点为 Q 11 Q11 Q11,此时易知, f ( P ) = f ( Q 11 ) f(P) = f(Q11) f(P)=f(Q11)。
一个例子:
如下图所示,将一幅 3 ∗ 3 3*3 3∗3的图像放大到 4 ∗ 4 4*4 4∗4,用 f ( x , y ) f(x, y) f(x,y)表示原图像, h ( x , y ) h(x, y) h(x,y)表示目标图像,我们有如下公式:
h ( d s t X , d s t Y ) = f ( d s t X ∗ s r c w i d t h d s t w i d t h , d s t Y ∗ s r c h e i g h t d s t h e i g h t ) \begin{array}{c} h(dstX, dstY) = f(\frac{dstX*src_{width}} {dst_{width}}, \frac{dstY*src_{height}} {dst_{height}}) \end{array} h(dstX,dstY)=f(dstwidthdstX∗srcwidth,dstheightdstY∗srcheight)
h ( 0 , 0 ) = f ( 0 , 0 ) h ( 0 , 1 ) = f ( 0 , 0.75 ) = f ( 0 , 1 ) h ( 0 , 2 ) = f ( 0 , 1.50 ) = f ( 0 , 2 ) h ( 0 , 3 ) = f ( 0 , 2.25 ) = f ( 0 , 2 ) . . . \begin{array}{c} h(0,0)=f(0,0) \\ h(0,1)=f(0,0.75)=f(0,1) \\ h(0,2)=f(0,1.50)=f(0,2) \\ h(0,3)=f(0,2.25)=f(0,2) \\ ...\\ \end{array} h(0,0)=f(0,0)h(0,1)=f(0,0.75)=f(0,1)h(0,2)=f(0,1.50)=f(0,2)h(0,3)=f(0,2.25)=f(0,2)...
缺点:
由最邻近插值法,放大后的图像有很严重的马赛克,会出现明显的块状效应;缩小后的图像有很严重的失真。
这是一种最基本、最简单的图像缩放方式。变换后的每个像素点的像素值,只由原图像中的一个像素点确定。例如上面,点 ( 0 , 0.75 ) (0,0.75) (0,0.75)的像素只由 ( 0 , 1 ) (0,1) (0,1)确定,这样的效果显然不好。点 ( 0 , 0.75 ) (0,0.75) (0,0.75)的像素不止和 ( 0 , 1 ) (0,1) (0,1)有关,和 ( 0 , 0 ) (0,0) (0,0)也有关,只是 ( 0 , 1 ) (0,1) (0,1)的影响更大。如果可以用附近的几个像素点按权重分配,共同确定目标图像某点的像素,效果会更好。下面的双线性插值就解决了这个问题。
1.4.2 双线性插值原理
1.4.2.1 线性插值
在讲双线性插值之前先看以一下线性插值。
线性插值:使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值。
线性插值形式:
f ( x ) = a 1 x + a 0 f(x)=a_{1} x+a_{0} f(x)=a1x+a0
a 1 a_{1} a1为由已知的两点连线的斜率
线性插值多项式:
y = y 0 + ( x − x 0 ) y 1 − y 0 x 1 − x 0 = y 0 + ( x − x 0 ) y 1 − ( x − x 0 ) y 0 x 1 − x 0 y=y_{0}+\left(x-x_{0}\right) \frac{y_{1}-y_{0}}{x_{1}-x_{0}}=y_{0}+\frac{\left(x-x_{0}\right) y_{1}-\left(x-x_{0}\right) y_{0}}{x_{1}-x_{0}} y=y0+(x−x0)x1−x0y1−y0=y0+x1−x0(x−x0)y1−(x−x0)y0
其实,即使 x x x不在 x 0 x_{0} x0到 x 1 x_{1} x1之间,这个公式也是成立的。在这种情况下,这种方法叫作线性外插。
线性插值的误差:线性插值其实就是拉格朗日插值有2个结点时的情况。
插值余项为:
R n ( x ) = f ′ ′ ( ξ ) ( x − x 0 ) ( x − x 1 ) 2 ! ≤ f ′ ′ ( ξ ) ( x 0 − x 1 ) 2 8 , ξ ∈ ( x 0 , x 1 ) R_{n}(x)=\frac{f^{''}(\xi)(x-x_{0})(x-x_{1})}{2!}\leq\frac{f^{''}(\xi)(x_{0}-x_{1})^2}{8} ,\xi\in(x_{0},x_{1}) Rn(x)=2!f′′(ξ)(x−x0)(x−x1)≤8f′′(ξ)(x0−x1)2,ξ∈(x0,x1)
从插值余项可以看出,随着二阶导数的增大,线性插值的误差增大。即函数的曲率越大,线性插值近似的误差也越大。
举个例子。下图中,左边为原图像,拉伸后,理想的输出图像的像素分布应该为绿色箭头指向的,但是按照线性插值,会得到红色箭头指向的结果。
1.4.2.2 双线性插值
双线性插值形式:
f ( x , y ) = a x + b y + c x y + d f(x,y)=ax+by+cxy+d f(x,y)=ax+by+cxy+d
双线性插值是线性插值在二维时的推广,在两个方向上共做了三次线性插值。定义了一个双曲抛物面与四个已知点拟合。
具体操作为在X方向上进行两次线性插值计算,然后在Y方向上进行一次插值计算。如下图所示:
首先, f ( x , y ) f(x,y) f(x,y)为二元函数,假设我们知道 f ( x 0 , y 0 ) f(x_{0},y_{0}) f(x0,y0), f ( x 1 , y 1 ) f(x_{1},y_{1}) f(x1,y1), f ( x 0 , y 1 ) f(x_{0},y_{1}) f(x0,y1), f ( x 1 , y 0 ) f(x_{1},y_{0}) f(x<