4. 细节增强基础:拉普拉斯金字塔、高斯金字塔、细节层提取
好,咱们进入第四章。前面几章我们把边缘保留滤波的几种主流方法都过了一遍,从双边滤波到引导滤波,再到加权最小二乘。说实话,那些滤波器本身就能做一定程度的细节增强,但效果比较「糙」。
真正精细的细节增强,靠的是多尺度分解。说白了,就是把图像拆成不同「粗细」的成分,然后只对中间那些「不大不小」的细节下手。这章要讲的金字塔方法,就是最经典、最直观的多尺度工具。
4.1 高斯金字塔:图像的「降维打击」
先说说高斯金字塔。你想想看,一张高分辨率 HDR 图像,细节多到爆炸。我们怎么一层层把它变模糊?
高斯金字塔的思路很简单:先模糊,再下采样。每一步生成一个分辨率减半的版本。
- 用高斯核对当前层图像做卷积(模糊)
- 隔行隔列采样,扔掉一半像素
- 得到下一层(尺寸减半)
- 重复直到达到目标层数
我在项目中遇到过一个问题:直接用 5×5 高斯核,模糊半径太大,小细节被抹得太干净。后来我习惯用 3×3 核,迭代多次,效果反而更可控。
// 高斯金字塔构建(简化版)
function buildGaussianPyramid(img, levels) {
let pyramid = [img];
let current = img.clone();
for (let i = 1; i < levels; i++) {
// 1. 高斯模糊
let blurred = current.gaussianBlur(3, 1.0);
// 2. 下采样(隔行隔列)
let down = new Mat(
current.rows / 2,
current.cols / 2,
current.type()
);
for (let r = 0; r < down.rows; r++) {
for (let c = 0; c < down.cols; c++) {
down.at(r, c) = blurred.at(r*2, c*2);
}
}
pyramid.push(down);
current = down;
}
return pyramid;
}
4.2 拉普拉斯金字塔:提取「差值」的艺术
高斯金字塔存的是「模糊版」,那拉普拉斯金字塔存的是什么?是差值。
每一层拉普拉斯金字塔,等于当前层高斯图像减去上一层上采样回来的预测图像。这个差值,就是该尺度下丢失的细节。
为什么会这样?因为高斯金字塔每降一层,信息就损失一部分。损失掉的那些高频成分——边缘、纹理、噪点——正好被拉普拉斯金字塔捕获。
Li = Gi − expand(Gi+1)
其中 expand() 是上采样+高斯插值
嗯,这里要注意:上采样不是简单放大像素。直接放大会出现马赛克效应。必须先用插值算法(双线性或双三次)把图像撑大,再做一次高斯平滑。
// 拉普拉斯金字塔构建
function buildLaplacianPyramid(gaussianPyramid) {
let laplacianPyramid = [];
let levels = gaussianPyramid.length;
for (let i = 0; i < levels - 1; i++) {
let current = gaussianPyramid[i];
let next = gaussianPyramid[i + 1];
// 上采样到当前层尺寸
let upsampled = next.resize(current.cols, current.rows);
// 高斯平滑消除锯齿
upsampled = upsampled.gaussianBlur(3, 1.0);
// 差值 = 当前层 - 上采样结果
let diff = current.subtract(upsampled);
laplacianPyramid.push(diff);
}
// 最顶层直接放高斯金字塔的最顶层(低频残差)
laplacianPyramid.push(gaussianPyramid[levels - 1]);
return laplacianPyramid;
}
4.3 细节层提取:从金字塔到可操作分量
有了拉普拉斯金字塔,细节层提取就水到渠成了。我们把金字塔分成三部分:
| 层级范围 | 对应分量 | 物理含义 | 增强策略 |
|---|---|---|---|
| 顶层(最低频) | 基础层(Base Layer) | 大尺度光照、整体色调 | 不动或轻微压缩 |
| 中间层(中频) | 细节层(Detail Layer) | 纹理、边缘、局部对比 | 放大 1.5~3 倍 |
| 底层(最高频) | 噪声层(Noise Layer) | 像素级噪声、极细纹理 | 抑制或保持不变 |
说白了,细节增强就是:把中间几层拉普拉斯金字塔的系数乘上一个大于 1 的增益,然后重建回去。
我个人习惯把金字塔分成三组:
- 底层(第 0~1 层):最高频,包含传感器噪声和极细纹理。增益设为 0.8~1.0,甚至降噪处理。
- 中层(第 2~3 层):核心细节区。增益设为 2.0~3.0,这是增强的主力。
- 顶层(第 4 层及以上):基础光照。增益设为 1.0,不动。
4.4 完整流程:从分解到重建
把上面所有步骤串起来,就是一套完整的细节增强管线:
- 输入:HDR 图像(建议先做色调映射,把动态范围压缩到 8-bit 或 16-bit 线性空间)
- 构建高斯金字塔:4~5 层,3×3 高斯核,σ=1.0
- 构建拉普拉斯金字塔:逐层计算差值
- 分层增益:对中间层乘以增益系数(2.0~3.0)
- 重建:从顶层开始,逐层上采样+叠加拉普拉斯层
- 输出:细节增强后的图像
下面这张图展示了整个流程的结构:
4.5 代码实战:一个完整的细节增强函数
最后,给你一个可以直接用的函数骨架。我在多个 HDR 项目里都用过这个结构,效果稳定:
function detailEnhancement(img, levels=4, detailGain=2.5) {
// 1. 构建高斯金字塔
let gaussPyr = buildGaussianPyramid(img, levels);
// 2. 构建拉普拉斯金字塔
let laplacePyr = buildLaplacianPyramid(gaussPyr);
// 3. 分层增益
// 底层(0~1):噪声层,增益 0.8
// 中层(2~3):细节层,增益 detailGain
// 顶层(4):基础层,增益 1.0
for (let i = 0; i < laplacePyr.length; i++) {
if (i <= 1) {
laplacePyr[i] = laplacePyr[i].multiply(0.8);
} else if (i <= 3) {
laplacePyr[i] = laplacePyr[i].multiply(detailGain);
}
// 顶层不动
}
// 4. 重建
let result = laplacePyr[laplacePyr.length - 1]; // 从顶层开始
for (let i = laplacePyr.length - 2; i >= 0; i--) {
// 上采样
result = result.resize(
laplacePyr[i].cols,
laplacePyr[i].rows
);
result = result.gaussianBlur(3, 1.0);
// 叠加细节
result = result.add(laplacePyr[i]);
}
return result;
}
好了,这一章的核心就这些。拉普拉斯金字塔是细节增强的「基本功」,后面讲更高级的滤波器(比如局部拉普拉斯滤波、导向滤波的细节层提取)时,你会发现它们本质上都是在做类似的事——只是分解方式更聪明了。