4. 细节增强基础:拉普拉斯金字塔、高斯金字塔、细节层提取

好,咱们进入第四章。前面几章我们把边缘保留滤波的几种主流方法都过了一遍,从双边滤波到引导滤波,再到加权最小二乘。说实话,那些滤波器本身就能做一定程度的细节增强,但效果比较「糙」。

真正精细的细节增强,靠的是多尺度分解。说白了,就是把图像拆成不同「粗细」的成分,然后只对中间那些「不大不小」的细节下手。这章要讲的金字塔方法,就是最经典、最直观的多尺度工具。

4.1 高斯金字塔:图像的「降维打击」

先说说高斯金字塔。你想想看,一张高分辨率 HDR 图像,细节多到爆炸。我们怎么一层层把它变模糊?

高斯金字塔的思路很简单:先模糊,再下采样。每一步生成一个分辨率减半的版本。

核心操作流程:
  1. 用高斯核对当前层图像做卷积(模糊)
  2. 隔行隔列采样,扔掉一半像素
  3. 得到下一层(尺寸减半)
  4. 重复直到达到目标层数

我在项目中遇到过一个问题:直接用 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;
}
我的经验:金字塔层数一般取 3~5 层就够了。层数太多,最顶层只剩几个像素,信息量太少,反而引入伪影。HDR 图像动态范围大,我建议从 4 层起步。

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 完整流程:从分解到重建

把上面所有步骤串起来,就是一套完整的细节增强管线:

  1. 输入:HDR 图像(建议先做色调映射,把动态范围压缩到 8-bit 或 16-bit 线性空间)
  2. 构建高斯金字塔:4~5 层,3×3 高斯核,σ=1.0
  3. 构建拉普拉斯金字塔:逐层计算差值
  4. 分层增益:对中间层乘以增益系数(2.0~3.0)
  5. 重建:从顶层开始,逐层上采样+叠加拉普拉斯层
  6. 输出:细节增强后的图像

下面这张图展示了整个流程的结构:

拉普拉斯金字塔细节增强流程 输入 HDR 图像 ① 构建高斯金字塔(4~5层) ② 构建拉普拉斯金字塔 ③ 分层增益(中间层 ×2~3) ④ 从顶层逐层重建 输出增强图像 关键:拉普拉斯金字塔的中间层对应人眼最敏感的中频细节,是增强的主要目标

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;
}
注意:这个函数假设输入图像是浮点型(0.0~1.0)。如果是 8-bit 整数,记得先转成 float,处理完再转回来。否则增益乘完直接溢出,画面一片白。

好了,这一章的核心就这些。拉普拉斯金字塔是细节增强的「基本功」,后面讲更高级的滤波器(比如局部拉普拉斯滤波、导向滤波的细节层提取)时,你会发现它们本质上都是在做类似的事——只是分解方式更聪明了。