1. 立方插值
立方插值算法也被称为双三次、双立方插值算法。
1.1 三次插值 (Cubic Interpolation)
先介绍一下三次插值算法,它是一种使用三次多项式拟合一组数据的插值方法。三次插值通常用于图像缩放和重采样。
三次插值的实现方式有很多种,例如牛顿多项式插值、拉格朗日多项式插值、Hermite 三次多项式插值、三次样条插值,每种方式都有其自身的优点和复杂性。
插值多项式的形式
- 牛顿多项式插值: 使用差商形式来构造插值多项式。
- 拉格朗日多项式插值: 使用乘积形式来构造插值多项式。
- Hermite 三次多项式插值: 使用分段三次多项式来构造插值多项式,每个分段多项式满足插值点处的函数值和一阶导数值。
- 三次样条插值: 也使用分段三次多项式来表示插值多项式,将插值区间划分为多个子区间,在每个子区间上使用三次多项式插值,并要求相邻子区间插值函数在连接处的一阶导数和二阶导数连续。
适用范围
- 牛顿多项式插值: 适用于数据量较小、插值精度要求不高的场景,如数据拟合、数值计算等。
- 拉格朗日多项式插值: 适用于理论分析、教学演示等场景。
- Hermite 三次多项式插值: 适用于需要高精度插值和光滑曲线的场景,如计算机图形学、曲线拟合等。
- 三次样条插值: 适用于数据量较大、要求插值精度和曲线光滑的场景,如图像处理、工程设计等。
1.2 三次样条插值 (Cubic Spline Interpolation)
我们以三次样条插值为例进行详细说明。
在数值分析这个数学分支中,样条插值 是使用一种名为样条 的特殊分段多项式 进行插值的形式。由于样条插值可以使用低阶多项式样条实现较小的插值误差,这样就避免了使用高阶多项式所出现的龙格现象,所以样条插值得到了流行。
三次样条函数的定义:
函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> S ( x ) ∈ C 2 [ a , b ] S(x)\in C^2[a, b] </math>S(x)∈C2[a,b],且在每个小区间 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi, <math xmlns="http://www.w3.org/1998/Math/MathML"> x i + 1 x_{i+1} </math>xi+1] 上是三次多项式 ,其中 a= <math xmlns="http://www.w3.org/1998/Math/MathML"> x 0 x_0 </math>x0 < <math xmlns="http://www.w3.org/1998/Math/MathML"> x 1 x_1 </math>x1 < . . . < <math xmlns="http://www.w3.org/1998/Math/MathML"> x n x_n </math>xn= b是给定节点,则称 S(x) 是节点 <math xmlns="http://www.w3.org/1998/Math/MathML"> x 0 x_0 </math>x0, <math xmlns="http://www.w3.org/1998/Math/MathML"> x 1 x_1 </math>x1, . . . , <math xmlns="http://www.w3.org/1998/Math/MathML"> x n x_n </math>xn上的三次样条函数。
三次样条函数满足:
- 在每个分段小区间 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi, <math xmlns="http://www.w3.org/1998/Math/MathML"> x i + 1 x_{i+1} </math>xi+1] 上, <math xmlns="http://www.w3.org/1998/Math/MathML"> S ( x ) = S i ( x ) S(x) = S_i(x) </math>S(x)=Si(x),且是一个三次方程
- 满足插值条件, <math xmlns="http://www.w3.org/1998/Math/MathML"> S ( x i ) = y i S(x_i) = y_i </math>S(xi)=yi(i=0,1,...,n)
- S(x) 曲线是光滑的,S(x)、S'(x)、S''(x) 在[a,b] 是连续的
要求出 S(x),在每个小区间 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi, <math xmlns="http://www.w3.org/1998/Math/MathML"> x i + 1 x_{i+1} </math>xi+1] 上要确定 4 个待定系数,共有 n 个区间,共 4n 个参数要构建 4n 个方程。
- 根据插值条件,在节点 <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi(i=1,2,...n-1) 处满足:
<math xmlns="http://www.w3.org/1998/Math/MathML"> S i ( x i + 1 ) = y i + 1 S_i(x_{i+1}) = y_{i+1} </math>Si(xi+1)=yi+1
<math xmlns="http://www.w3.org/1998/Math/MathML"> S i + 1 ( x i + 1 ) = y i + 1 S_{i+1}(x_{i+1}) = y_{i+1} </math>Si+1(xi+1)=yi+1
以及第一个和最后一个端点分别满足三次方程,总共 2n 个方程
- 根据 S(x) 在 [a,b] 上一阶导和二阶导数连续,在节点 <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi(i=1,2,...n-1) 处满足连续条件:
n-1 个内部点的一阶导数应该是连续的,即在第 i 区间的末点和第 i+1 区间的起点是同一个点,它们的一阶导数也相等,即: <math xmlns="http://www.w3.org/1998/Math/MathML"> S i ′ ( x i + 1 ) = S i + 1 ′ ( x i + 1 ) S'i(x{i+1}) = S'{i+1}(x{i+1}) </math>Si′(xi+1)=Si+1′(xi+1)
同理: <math xmlns="http://www.w3.org/1998/Math/MathML"> S i ′ ′ ( x i + 1 ) = S i + 1 ′ ′ ( x i + 1 ) S_i''(x_{i+1}) = S_{i+1}''(x_{i+1}) </math>Si′′(xi+1)=Si+1′′(xi+1)
总共 2n-2 个方程。
加起来一共 4n-2 个方程,还需再加上 2 个方程就可以确定 S(x)。通常可在区间 [a,b] 端点 a = <math xmlns="http://www.w3.org/1998/Math/MathML"> x 0 x_0 </math>x0, b = <math xmlns="http://www.w3.org/1998/Math/MathML"> x n x_n </math>xn 处各加一个条件(称为边界条件),可根据实际问题的要求给定。常见有以下三种:
- 固定边界(Clamped):已知两端的一阶导数值 A 和 B
<math xmlns="http://www.w3.org/1998/Math/MathML"> S 0 ′ ( x 0 ) = A S_0'(x_0)=A </math>S0′(x0)=A
<math xmlns="http://www.w3.org/1998/Math/MathML"> S n − 1 ′ ( x n ) = B S_{n-1}'(x_n)=B </math>Sn−1′(xn)=B
- 自然边界(Natural):已知两端的二阶导数为 0
<math xmlns="http://www.w3.org/1998/Math/MathML"> S ′ ′ ( x 0 ) = S ′ ′ ( x n ) = 0 S''(x_0)=S''(x_n)=0 </math>S′′(x0)=S′′(xn)=0
- 非扭结边界(Not-A-Knot): 强制第一个插值点的三阶导数值等于第二个点的三阶导数值,最后第一个点的三阶导数值等于倒数第二个点的三阶导数值。
<math xmlns="http://www.w3.org/1998/Math/MathML"> S 0 ′ ′ ′ ( x 0 ) = S 1 ′ ′ ′ ( x 1 ) S_0'''(x_0)=S_1'''(x_1) </math>S0′′′(x0)=S1′′′(x1)
<math xmlns="http://www.w3.org/1998/Math/MathML"> S n − 2 ′ ′ ′ ( x n − 1 ) = S n − 1 ′ ′ ′ ( x n ) S_{n-2}'''(x_{n-1})=S_{n-1}'''(x_n) </math>Sn−2′′′(xn−1)=Sn−1′′′(xn)
1.3 双三次样条插值 (Bicubic Spline Interpolation)
双三次样条插值是在二维空间中使用三次样条函数对图像进行插值。它将图像划分为一个网格,并在每个网格点处使用一个三次样条函数来拟合图像数据。在未知点处,通过对相邻网格点的三次样条函数进行插值来获得插值值。
构造 Bicubic 函数:
<math xmlns="http://www.w3.org/1998/Math/MathML"> W ( x ) = { ( a + 2 ) ∣ x ∣ 3 − ( a + 3 ) ∣ x ∣ 2 + 1 for |x| ≤ 1 a ∣ x ∣ 3 − 5 a ∣ x ∣ 2 + 8 a ∣ x ∣ − 4 a for 1 <|x|<2 0 otherwise W(x) = \begin{cases} (a+2)|x|^3-(a+3)|x|^2+1 & \text{for |x|} \le 1\\ a|x|^3-5a|x|^2+8a|x|-4a & \text{for 1 <|x|<2}\\ 0 & \text{otherwise} \end{cases} </math>W(x)=⎩ ⎨ ⎧(a+2)∣x∣3−(a+3)∣x∣2+1a∣x∣3−5a∣x∣2+8a∣x∣−4a0for |x|≤1for 1 <|x|<2otherwise
对待插值的像素点(x,y),取其附近的 4*4 邻域点( <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi, <math xmlns="http://www.w3.org/1998/Math/MathML"> y i y_i </math>yi) (i,j=0,1,2,3)。其插值公式如下:
<math xmlns="http://www.w3.org/1998/Math/MathML"> f ( x , y ) = ∑ i = 0 3 ∑ j = 0 3 f ( x i , y j ) W ( x − x i ) W ( y − y j ) f(x,y)=\sum_{i=0}^{3}\sum_{j=0}^{3}f(x_i,y_j)W(x-x_i)W(y-y_j) </math>f(x,y)=∑i=03∑j=03f(xi,yj)W(x−xi)W(y−yj)
Lanczos 插值 (Lanczos Interpolation)
Lanczos 插值使用 Lanczos 核函数来计算插值后的像素值。Lanczos 核函数是一种低通滤波器,可以消除缩放过程中产生的混叠现象。
Lanczos 核函数定义如下:
<math xmlns="http://www.w3.org/1998/Math/MathML"> L ( x ) = { s i n c ( x ) s i n c ( x / a ) , if − a < x < a , 0 , o t h e r w i s e . L(x) = \begin{cases} sinc(x)sinc(x/a), & \text{if } -a < x < a, \\ 0, & otherwise. \end{cases} </math>L(x)={sinc(x)sinc(x/a),0,if −a<x<a,otherwise.
其中,sinc(x) = sin(πx) / (πx),a 是核函数的宽度。
Lanczos 插值的过程如下:
- 确定插值点的位置。
- 以插值点为中心,在原图像中取一个窗口。
- 对窗口中的每个像素,使用 Lanczos 核函数计算其权重。
- 将窗口中所有像素的权重和插值值相乘,得到插值点的最终值。
Lanczos 插值公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> S ( x ) = ∑ i = ⌊ x ⌋ − a + 1 ⌊ x ⌋ + a s i L ( x − i ) S(x) = \sum_{i=\lfloor x \rfloor-a+1}^{\lfloor x \rfloor+a} s_iL(x-i) </math>S(x)=∑i=⌊x⌋−a+1⌊x⌋+asiL(x−i)
Lanczos 插值的优点
- 与其他插值方法相比,Lanczos 插值可以产生更清晰、更平滑的图像。
- Lanczos 插值可以有效地抑制混叠现象,尤其是在图像缩小的情况下。
Lanczos 插值的缺点
- 与其他的插值方法相比,Lanczos 插值的计算量更大。
- Lanczos 插值可能会产生轻微的振铃效应,尤其是在图像放大边缘处。
3. OpenCV 中的 resize() 函数使用示例
OpenCV 封装好了很多图像缩放方法的算法。在 OpenCV C++ 中的 resize()
函数用于调整图像大小,它可以根据指定的尺寸和插值方法对图像进行缩放。
cpp
void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );
第四个参数 fx: 缩放比例,沿 x 轴的缩放因子。 第五个参数 fx: 缩放比例,沿 y 轴的缩放因子。 第六个参数 interpolation: 插值方法,常用的插值方法包括:
- INTER_NEAREST: 最近邻插值
- INTER_LINEAR: 双线性插值
- INTER_CUBIC: 4*4 邻域双三次样条插值
- INTER_AREA: 区域插值
- INTER_LANCZOS4: 8*8 邻域 Lanczos 插值
下面的例子使用四种插值方法分别对图像进行缩放:
cpp
#include <chrono>
#include <opencv2/opencv.hpp>
#define millisecond 1000000
#define DEBUG_PRINT(...) printf( __VA_ARGS__); printf("\n")
#define DEBUG_TIME(time_) auto time_ =std::chrono::high_resolution_clock::now()
#define RUN_TIME(time_) (double)(time_).count()/millisecond
using namespace std;
using namespace cv;
int main() {
Mat src = imread(".../grass.jpg");
imshow("src", src);
Mat nearest,linear,cubic,lanczos;
double scale = 1.5;
DEBUG_PRINT("image size[%d,%d],scale=%3.1f", src.rows,src.cols, scale);
DEBUG_TIME(T0);
cv::resize(src, nearest, Size(), scale, scale, cv::INTER_NEAREST);//最近邻插值
DEBUG_TIME(T1);
cv::resize(src, linear, Size(), scale, scale, cv::INTER_LINEAR); //双线性插值(默认)
DEBUG_TIME(T2);
cv::resize(src, cubic, cv::Size(), scale, scale, cv::INTER_CUBIC); //双三次样条插值
DEBUG_TIME(T3);
cv::resize(src, lanczos, cv::Size(), scale, scale, cv::INTER_LANCZOS4); //Lanczos 插值
DEBUG_TIME(T4);
DEBUG_PRINT("INTER_NEAREST :%3.3fms", RUN_TIME(T1 - T0));
DEBUG_PRINT("INTER_LINEAR :%3.3fms", RUN_TIME(T2 - T1));
DEBUG_PRINT("INTER_CUBIC :%3.3fms", RUN_TIME(T3 - T2));
DEBUG_PRINT("INTER_LANCZOS4:%3.3fms", RUN_TIME(T4 - T3));
imshow("nearest",nearest);
imshow("linear",linear);
imshow("cubic",cubic);
imshow("lanczos",lanczos);
waitKey(0);
return 0;
}
执行结果:
ini
image size[427,640],scale=1.5
INTER_NEAREST :0.567ms
INTER_LINEAR :0.582ms
INTER_CUBIC :1.433ms
INTER_LANCZOS4:2.002ms
4. 总结
三次样条插值、双三次样条插值和 Lanczos 插值都是常用的图像缩放插值方法。
三次样条插值是一种分段插值方法,在每个分段内使用三次多项式进行插值。它具有较高的插值精度,但计算量较大。双三次样条插值是三次样条插值的二维扩展,在两个方向上都使用三次样条进行插值,它具有更高的插值精度和计算量。Lanczos 插值是一种基于 Lanczos 滤波器的插值方法,它使用 Lanczos 滤波器对图像进行卷积,从而获得更平滑、更清晰的插值结果。
三种方法各有优缺点,选择哪种方法取决于实际应用场景和需求。