把一张 4000 像素宽的图像显示在 800 像素宽的窗口里,或者把 32×32 的图标放大到 128×128,都要重新计算每个输出像素的颜色。这个过程的输入是离散的样本栅格,输出是另一个采样密度下的样本栅格,两者之间不存在天然的一一对应关系,因此需要一套插值规则来决定如何取值。在图片浏览器里,这是发生频率较高的像素级操作。
规则的选择会直接改变结果的外观:有的方法让边缘保持锐利但引入锯齿,有的方法让画面平滑但丢失细节,还有的方法在强边缘旁引入亮暗带。这些差异都源于核函数的形状,而不是实现的优劣。
重采样的定义
设输入样本位于整数坐标 i,输出像素位于坐标 x(已映射到输入的坐标系中)。输出值可写成对输入样本的加权和:
- 权重由核函数
k给出:w(i) = k(x - i) - 只有落在支撑域内的样本权重非零
- 为保持整体亮度不变,通常先把权重归一化,使其总和为 1
坐标映射本身有两种常见约定:把输出像素中心对齐输入像素中心(多用于放大),或对齐输入样本的边界(多用于缩小)。约定不同,边缘像素的结果会略有差异。
核函数与支撑域
| 方法 | 核函数形状 | 支撑域半径(单位:样本间隔) | 频域表现 | 是否产生过冲 |
|---|---|---|---|---|
| 最近邻 | 矩形脉冲 | 0.5 | 通带平坦,旁瓣较高 | 否 |
| 双线性 | 三角形(帐篷) | 1 | 通带随频率较快衰减 | 否 |
| 双三次 | 分段三次多项式 | 2 | 过渡带较窄 | 可能 |
| Lanczos | sinc 窗函数乘以 sinc | 2 或 3 | 过渡带窄,阻带衰减较明显 | 可能 |
支撑域半径决定每个输出像素要参与计算的输入样本数量:半径 1 意味着一维上取 2 个样本、二维上取 4 个;半径 2 对应一维 4 个、二维 16 个;半径 3 对应一维 6 个、二维 36 个。计算量随半径的平方增长,这是各类方法开销差异的来源。
三种重采样方式
最近邻
取落在距 x 半个样本间隔以内的那个输入样本,权重为 1,其余为 0。计算开销较低,输出中出现的颜色必然是输入中出现过的颜色。放大时同一输入样本会被重复填充到多个输出像素,形成方块状边缘;缩小时若采样点分布不均,会直接丢弃部分输入样本。
双线性
在 x 与 y 方向上各做一次线性插值,等价于对 2×2 邻域做加权平均,权重由到样本的距离线性决定:
// 双线性取样:fx、fy 为落在 0..1 之间的小数部分
function bilinear(p00, p10, p01, p11, fx, fy) {
const top = p00 * (1 - fx) + p10 * fx;
const bot = p01 * (1 - fx) + p11 * fx;
return top * (1 - fy) + bot * fy;
}
结果比最近邻平滑,但核在频域的旁瓣依然存在,缩小时会产生混叠,放大时细节会被柔化。
双三次与 Lanczos
双三次使用分段三次多项式作为核,常见的形式在支撑域边界处取值为 0。Lanczos 则用 sinc 函数乘以一个窗函数,把 sinc 的无限支撑截断为有限区间。两者的核都带有取值区间为负的部分,这使它们能更贴近理想的低通特性,代价是强边缘附近出现过冲与下冲。
下采样与抗混叠
缩小时,输入中频率高于输出奈奎斯特频率的成分无法被正确表示,会折叠到低频,表现为摩尔纹或细密纹理上的伪影。只按固定支撑域取样无法抑制这一点,因为参与平均的样本数量太少。
抗混叠的通用做法是把核按缩放比例展宽:若缩小倍率为 s,就把核的支撑域放大到约 s 倍,等效于先做一次截止频率与输出采样率匹配的低通滤波,再取样。这也是同一套核函数能在放大与缩小两种场景下分别取不同半径的原因。
振铃与过冲
带负瓣的核在阶跃边缘附近会输出超出原值范围的结果,表现为边缘两侧的亮线与暗线,称为振铃。它不是实现缺陷,而是频域过渡陡峭带来的时域代价:通带与阻带之间的过渡越陡,时域上的波纹越明显。核的负瓣幅度越小、支撑域越短,振铃越弱,但通带边缘的衰减越缓和,细节保留越少。
gamma 与线性域缩放
若样本以编码域数值存储(如常见的 8 位 sRGB 数据),直接对其做加权平均,与在相对亮度域中做同样的运算结果不同,在高对比边缘上差异可见。更接近物理意义的做法是先把样本通过传递函数的逆映射转到线性域,加权求和后再转回编码域。是否需要这么做,取决于对结果一致性的要求与可接受的计算开销。
mipmap 与三线性过滤
GPU 采样纹理时使用归一化坐标,硬件可在相邻纹素之间按双线性规则插值。缩小比例较大时,一个输出像素覆盖的纹素数量增多,而双线性只读取 4 个纹素,仍会产生混叠。mipmap 预先按 1/2 逐级缩小并保存一组纹理层级,采样时依据屏幕空间导数选择合适的层级,并在相邻两层之间再做一次线性插值,即三线性过滤。它的作用是把参与平均的纹素数量随缩小比例一起增加,代价是额外的显存占用:完整金字塔的总量约为原图的 4/3。层级管理与上传路径属于 GPU 纹理上传与瓦片渲染的范畴。
小结
插值算法的差异集中在核函数上:支撑域决定参与运算的样本数量与计算量,频域形状决定过渡带宽度与混叠程度,是否带负瓣决定会不会出现振铃。选择哪种方法取决于缩放方向、缩放倍率与对细节的要求,没有一种核能在所有条件下都给出同样接近理想低通的结果。