【图像处理】常见图像插值算法与应用
参考文章:
https://blog.csdn.net/Datawhale/article/details/105697264
一、常见插值算法
1.1 最近邻插值算法
原理简介
将目标图像像素映射到原图像后,取距离最近的整数坐标像素值作为该像素的输出值。
例如:目标图像某点投影到原图像的位置为P,与P距离最近的点为Q11,则该点像素值 f(P) = f(Q11)
。
例子说明
将3×3原图像(用f(x,y)
表示)放大到4×4目标图像(用h(x,y)
表示),映射公式如下:
缺点
- 放大图像会出现明显马赛克/块状效应
- 缩小图像会产生严重失真
本质是目标像素仅由原图像单个像素决定,未考虑周围像素的影响。
1.2 双线性插值算法
双线性插值是线性插值在二维空间的推广,通过3次线性插值(X方向2次、Y方向1次),用周围4个像素的加权值确定目标像素,效果更平滑。
1. 线性插值基础
线性插值通过连接两个已知量的直线,计算两者之间未知量的值,公式如下:
f(x)=a1x+a0f(x) = a_1x+a_0 f(x)=a1x+a0
线性插值多项式:
插值余项(误差):
函数曲率越大,插值误差越大。
2. 双线性插值步骤
假设已知原图像中4个点 f(x0,y0)、f(x1,y1)、f(x0,y1)、f(x1,y0)
的值,目标是计算这4个点构成的矩形内任意点 f(x,y)
的值:
-
X方向两次线性插值:分别计算y0和y1行上x处的插值结果
-
Y方向一次线性插值:用X方向的两个结果计算y处的最终值
综合公式:
若4个点为单位正方形顶点(坐标(0,0)、(0,1)、(1,0)、(1,1)),公式可简化为:
3. 原图像与目标图像的几何中心对齐
直接映射会导致原图像部分像素未参与计算(如9×9缩小到3×3时,右下角像素未被使用),需添加调节因子使两者中心对齐,OpenCV默认采用此方式。
-
普通映射公式:
-
中心对齐映射公式(添加调节因子
0.5*(src_width/dst_width - 1)
):
4. OpenCV中cv.resize()的计算逻辑
根据目标像素在原图像中的位置,分三种情况处理:
- 中间点:正常双线性插值(取周围4个点)
- 边界点(非顶点):线性插值(如目标点映射到原图像外时,取最近两个点)
- 四个顶点:最近邻插值(直接取原图像顶点值)
计算示例(3×3原图像放大到4×4):
- 中间点公式:
-
边界点公式:
-
顶点公式:
2.3 三次样条插值算法
三次样条插值通过分段三次多项式拟合原图像像素,满足“连续可导”条件,插值效果更平滑,但计算复杂度更高。
核心思想
给定n+1个点 a=x0<x1<...<xn=b
及对应函数值 f(xi)
,在每个区间 [xi, xi+1]
构造三次多项式 Si(x) = aix³ + bix² + cix + di
,需满足4n个条件:
- 插值条件:
Si(xi) = f(xi)
、Si(xi+1) = f(xi+1)
(共2n个) - 连续可导条件:
Si'(xi+1) = Si+1'(xi+1)
、Si''(xi+1) = Si+1''(xi+1)
(共2n-2个) - 边界条件(3选1):
- 自然边界:
S0''(x0) = 0
、Sn-1''(xn) = 0
- 一阶导数已知:
S0'(x0) = f'(x0)
、Sn-1'(xn) = f'(xn)
- 周期边界:
S0'(x0) = Sn-1'(xn)
、S0''(x0) = Sn-1''(xn)
- 自然边界:
通过求解4n个方程组,得到每个多项式的系数,最终计算目标像素值。
三、两种映射方法
图像几何变换的本质是像素坐标映射,分为向前映射和向后映射两种方式。
3.1 向前映射(像素移交映射)
过程
- 坐标变换:由原图像像素坐标
(x,y)
推算其在目标图像的坐标(x',y')
(通常为非整数)。 - 像素分配:将原图像像素值按权重分配给目标图像中
(x',y')
周围的4个整数坐标点。
缺点
- 目标图像像素值需叠加多个原图像像素的分配权重,需额外记录权重总和并归一化。
- 可能出现目标图像“漏点”(部分像素未被分配到值)。
3.2 向后映射(像素填充算法)
过程
- 坐标变换:由目标图像像素坐标
(x',y')
反推其在原图像的坐标(x,y)
(通常为非整数)。 - 插值计算:通过最近邻、双线性等插值算法,用原图像中
(x,y)
周围的像素值计算目标像素值。
优点
- 每个目标像素仅需一次插值计算,无需遍历所有原图像像素。
- 无“漏点”问题,是主流的映射方式(前文插值算法均基于向后映射)。
四、动手实现(基于OpenCV)
OpenCV的cv.resize()
函数支持多种插值方式,可直接用于图像缩放,以下是C++和Python的实现示例。
4.1 C++实现
1. cv.resize()函数原型
void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR);
参数说明:
参数 | 含义 |
---|---|
src | 输入原图像 |
dst | 输出目标图像 |
dsize | 目标图像尺寸(Size(cols, rows),cols为列数,rows为行数) |
fx/fy | 水平/垂直方向的缩放因子(dsize为None时生效) |
interpolation | 插值方式(默认双线性插值INTER_LINEAR) |
2. 常用插值方式
插值方式 | 说明 | 适用场景 |
---|---|---|
INTER_NEAREST | 最近邻插值 | 速度快,对精度要求低的场景 |
INTER_LINEAR | 双线性插值(默认) | 放大/缩小,平衡速度与效果 |
INTER_CUBIC | 三次样条插值 | 放大图像,追求高平滑度 |
INTER_AREA | 区域插值 | 缩小图像,减少失真 |
3. 完整代码
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main(int argc, char* argv[])
{// 读取原图像(需替换为你的图像路径)Mat img = imread("C:/Users/94890/Desktop/picture/luelue.jpg");if (img.empty()){cout << "无法读取图像" << endl;return 0;}// 获取原图像尺寸int height = img.rows; // 行数(垂直方向)int width = img.cols; // 列数(水平方向)// 1. 缩小图像(0.2倍,双线性插值)Size dsize_shrink = Size(round(0.2 * width), round(0.2 * height));Mat shrink;resize(img, shrink, dsize_shrink, 0, 0, INTER_LINEAR);// 2. 放大图像(在缩小图像基础上放大1.5倍,对比两种插值)float fx = 1.5, fy = 1.5;Mat enlarge_nearest, enlarge_linear;resize(shrink, enlarge_nearest, Size(), fx, fy, INTER_NEAREST); // 最近邻插值resize(shrink, enlarge_linear, Size(), fx, fy, INTER_LINEAR); // 双线性插值// 显示图像imshow("原图像", img);imshow("0.2倍缩小(双线性)", shrink);imshow("1.5倍放大(最近邻)", enlarge_nearest);imshow("1.5倍放大(双线性)", enlarge_linear);// 保存图像imwrite("C:/Users/94890/Desktop/picture/shrink2.jpg", shrink);imwrite("C:/Users/94890/Desktop/picture/INTER_NEAREST2.jpg", enlarge_nearest);imwrite("C:/Users/94890/Desktop/picture/INTER_LINEAR2.jpg", enlarge_linear);waitKey(0); // 等待按键关闭窗口destroyAllWindows();return 0;
}
4. 实验结果
- 原图像:
- 0.2倍缩小(双线性):
- 1.5倍放大(最近邻):
- 1.5倍放大(双线性):
更多资料:https://github.com/0voice