3. 传统插值方法:最近邻、双线性、双三次插值
说到超分辨率重建,很多人一上来就奔着深度学习去了。但我个人习惯,先带学员把传统插值方法吃透。为什么?因为它是所有上采样操作的基石,你后面学的转置卷积、亚像素卷积,本质上都是在学习如何做更好的插值。
说白了,插值就是“猜像素”。给你一张低分辨率图,要把它放大,中间缺的那些像素值怎么填?三种经典方法给出了不同的答案。
核心认知: 传统插值方法虽然简单,但在工业落地中仍有广泛应用。我曾在医疗影像项目中,因为客户硬件限制无法跑模型,最后用双三次插值配合锐化,硬是把效果提到了可接受的水平。
3.1 最近邻插值
这是最粗暴的方法。目标像素的值,直接取原图中离它最近的像素值。不做任何计算,纯粹是“复制粘贴”。
举个例子:原图 2×2 要放大到 4×4。每个目标像素找到原图中最近的邻居,把它的值拿过来。坐标映射公式很简单:
src_x = dst_x * (src_width / dst_width)
src_y = dst_y * (src_height / dst_height)
# 然后四舍五入取整
你想想看,这样做会有什么问题?放大后的图像会出现明显的锯齿和马赛克块。尤其是边缘部分,看起来像像素格子在跳舞。
| 项目 | 说明 |
|---|---|
| 计算量 | 几乎为零,只需一次取整操作 |
| 图像质量 | 差,严重锯齿,边缘不连续 |
| 适用场景 | 实时预览、缩略图生成、对质量无要求的场景 |
避坑: 我曾经在嵌入式设备上做实时视频放大,一开始用了双三次插值,结果帧率只有 15fps。后来换成最近邻,直接跑到 60fps。虽然画质糙了点,但客户要的是流畅度。记住:不是所有场景都需要高质量。
3.2 双线性插值
这个方法就聪明多了。它不再只取一个点,而是考虑目标像素在原图中最近的 2×2 邻域,做两次线性插值(先水平方向,再垂直方向)。
具体来说:
- 找到目标像素在原图中对应的浮点坐标 (x, y)
- 取它周围的四个整数像素点 Q11, Q12, Q21, Q22
- 先在 x 方向做一次线性插值,得到 R1 和 R2
- 再在 y 方向做一次线性插值,得到最终结果 P
// 伪代码示意
float dx = x - floor(x);
float dy = y - floor(y);
float R1 = Q11 * (1-dx) + Q21 * dx;
float R2 = Q12 * (1-dx) + Q22 * dx;
float P = R1 * (1-dy) + R2 * dy;
效果比最近邻好很多。锯齿消失了,边缘变得平滑。但问题也随之而来——图像变模糊了。因为插值本质上是低通滤波,高频细节被抹掉了。
| 项目 | 说明 |
|---|---|
| 计算量 | 中等,每次需 4 次乘法 + 若干加法 |
| 图像质量 | 较好,边缘平滑,但细节模糊 |
| 适用场景 | 通用图像缩放、视频处理、对速度和质量都有要求的场景 |
我的经验: 双线性插值是很多图像处理库的默认缩放方法。OpenCV 的 cv::resize 默认就是双线性。但如果你要做超分的前后处理,我建议慎用——它会把高频信息抹掉,影响后续模型的效果。
3.3 双三次插值
这是传统插值方法中的“天花板”。它不再用 2×2 邻域,而是扩展到 4×4 邻域(16 个像素点)。插值函数也不再是简单的线性,而是用三次多项式来拟合。
核心公式是基函数:
W(x) = {
(a+2)|x|³ - (a+3)|x|² + 1, for |x| ≤ 1
a|x|³ - 5a|x|² + 8a|x| - 4a, for 1 < |x| < 2
0, otherwise
}
// 常用 a = -0.5 或 a = -0.75
嗯,这里要注意:参数 a 的选择会影响锐度。a = -0.5 是 OpenCV 的默认值,效果比较平滑。a = -0.75 会稍微锐利一些,但可能出现 overshoot(过冲)。
计算过程分三步:先在 x 方向对每行做 4 次三次插值,得到 4 个中间值;再在 y 方向对这 4 个值做一次三次插值。
| 项目 | 说明 |
|---|---|
| 计算量 | 高,每次需 16 次基函数计算 + 加权求和 |
| 图像质量 | 优秀,边缘保留较好,细节损失少 |
| 适用场景 | 高质量图像缩放、印刷、医学影像、遥感图像 |
注意: 双三次插值不是万能的。我做过一个实验:把一张 100×100 的文本图像放大到 400×400,双三次插值后的文字边缘还是会有模糊。对于这种强边缘场景,最近邻反而更清晰(虽然锯齿严重)。选哪种,要看你的具体需求。
3.4 三种方法对比总结
为了让你一目了然,我整理了一张对比表:
| 对比维度 | 最近邻 | 双线性 | 双三次 |
|---|---|---|---|
| 邻域大小 | 1×1 | 2×2 | 4×4 |
| 计算复杂度 | O(1) | O(4) | O(16) |
| 边缘表现 | 锯齿严重 | 平滑但模糊 | 较清晰 |
| 细节保留 | 差 | 中等 | 较好 |
| 运行速度 | 极快 | 快 | 较慢 |
| PSNR 典型值 | 低 | 中等 | 较高 |
3.5 实际落地中的选择建议
讲了这么多理论,最后聊聊我在项目中的真实选择逻辑:
- 移动端/嵌入式: 优先考虑最近邻或双线性。算力宝贵,留给后面的模型推理用。
- PC端/服务器端: 默认用双三次。如果对速度有要求,可以降级到双线性。
- 作为超分模型的预处理: 我建议用双线性。为什么?因为双三次的平滑效果可能会让模型学到错误的先验。双线性保留的“模糊感”反而更接近真实低分辨率图像的退化过程。
- 作为后处理: 如果模型输出有棋盘格伪影,可以试试用双三次做一次轻微的下采样再上采样,能起到一定的平滑作用。
一个小技巧: 在 OpenCV 中,cv::resize 的 interpolation 参数可以设置为 cv::INTER_LINEAR 或 cv::INTER_CUBIC。但如果你用 GPU 加速(比如 CUDA 的 cv::cuda::resize),双线性插值的速度优势会更明显。我建议你在项目初期就把插值方法做成可配置参数,方便后期调优。