2. 传统散景算法:高斯模糊、双边滤波、导向滤波的原理与实现

聊到手机人像模式的散景效果,很多人第一反应就是「把背景糊掉」。但怎么糊、糊到什么程度、边缘怎么处理,这里面门道可不少。今天我们就从最基础的三种传统算法说起。

这三种算法,说白了就是三种不同的「模糊策略」。我当年刚入行时,觉得高斯模糊就够了,后来被项目里的边缘光晕问题狠狠教育了一回。嗯,咱们一个一个来看。

2.1 高斯模糊:最朴素的模糊

高斯模糊是所有模糊算法的起点。它的核心思想很简单:每个像素的值,由它周围像素的加权平均决定。权重分布服从高斯函数,离中心越近权重越大。

核心公式:

G(x, y) = (1 / (2πσ²)) * exp(-(x² + y²) / (2σ²))

其中 σ 控制模糊程度,σ 越大越模糊。

我在项目中遇到过一个问题:用高斯模糊做背景虚化,结果人物边缘出现了一圈「光晕」。为什么?因为高斯模糊不考虑深度信息,它把前景和背景的像素混在一起了。

参数 作用 典型值
σ(标准差) 控制模糊半径 1.0 ~ 10.0
核大小 通常取 6σ+1 3x3 ~ 61x61

我的习惯:做高斯模糊时,核大小一定要取奇数。另外,σ 不要超过核半径,否则采样不足,效果会打折扣。

2.2 双边滤波:边缘保护的模糊

高斯模糊的痛点在于「一视同仁」。双边滤波加了一个「颜色权重」——像素值差异大的地方,权重就小。这样,边缘就被保留下来了。

公式长这样:

BF[I]_p = (1/W_p) * Σ G_s(||p - q||) * G_r(|I_p - I_q|) * I_q

其中 G_s 是空间权重,G_r 是颜色权重。两个权重相乘,就是双边滤波的核心。

我曾经在一个人像分割项目里用双边滤波做后处理,效果确实比高斯模糊好很多。但有个坑——参数调起来很麻烦。

参数 含义 调参经验
σ_space 空间标准差 越大越模糊,通常 5~20
σ_color 颜色标准差 越大边缘保留越弱,通常 10~50

避坑指南:我曾经把 σ_color 设得太大,结果边缘全糊了,跟高斯模糊没区别。反过来,设得太小,背景几乎没模糊。记住:σ_color 控制的是「什么算边缘」,不是模糊强度。

2.3 导向滤波:计算效率与质量的平衡

双边滤波有个硬伤——计算量太大。你想想看,每个像素都要算颜色权重,实时处理根本扛不住。导向滤波就是来解决这个问题的。

导向滤波的核心假设是:输出图像和导向图像之间存在局部线性关系。说白了,就是在一个小窗口内,输出是导向图的仿射变换。

关键公式:

q_i = a_k * I_i + b_k,  ∀i ∈ ω_k

其中 a_k 和 b_k 是窗口 ω_k 内的线性系数,通过最小化输出与输入之间的差异来求解。

我个人特别喜欢导向滤波的一点是:它可以用导向图来控制边缘保留的程度。比如用人像分割的置信度图做导向,就能实现「前景锐利、背景模糊」的效果。

算法 时间复杂度 边缘保留 适用场景
高斯模糊 O(N) 差 简单模糊、预处理
双边滤波 O(N·r²) 好 边缘保留平滑
导向滤波 O(N) 好 实时处理、抠图后处理

2.4 三种算法的对比与选择

你可能会问:那我到底该用哪个?我的建议是:

  • 高斯模糊:适合做预处理,比如降噪、下采样前的平滑。别用它做散景,效果太假。
  • 双边滤波:适合离线处理,或者对边缘质量要求极高的场景。注意控制参数,别让边缘崩了。
  • 导向滤波:适合实时系统,尤其是你有深度图或置信度图的时候。我现在的项目基本都用它。

我的经验:在实际项目中,我通常会用导向滤波做基础散景,再用双边滤波做边缘微调。这样既保证了效率,又兼顾了质量。

2.5 知识体系总览

下面这张图帮你理清三种算法的关系:

传统散景算法知识体系 传统散景算法 高斯模糊 双边滤波 导向滤波 特性 简单快速 无边缘保留 特性 边缘保留好 计算量大 特性 线性复杂度 可导向控制 预处理、降噪 离线高质量散景 实时系统首选

2.6 代码实现要点

最后,给你一个简单的实现思路。以 Python 为例:

# 高斯模糊
import cv2
img = cv2.imread('portrait.jpg')
gaussian = cv2.GaussianBlur(img, (15, 15), 5.0)

# 双边滤波
bilateral = cv2.bilateralFilter(img, d=9, sigmaColor=30, sigmaSpace=10)

# 导向滤波(使用 OpenCV 的引导滤波实现)
import numpy as np
def guided_filter(I, p, r, eps):
    # I: 导向图, p: 输入图, r: 窗口半径, eps: 正则化参数
    mean_I = cv2.boxFilter(I, cv2.CV_64F, (r, r))
    mean_p = cv2.boxFilter(p, cv2.CV_64F, (r, r))
    corr_I = cv2.boxFilter(I*I, cv2.CV_64F, (r, r))
    corr_Ip = cv2.boxFilter(I*p, cv2.CV_64F, (r, r))
    var_I = corr_I - mean_I*mean_I
    cov_Ip = corr_Ip - mean_I*mean_p
    a = cov_Ip / (var_I + eps)
    b = mean_p - a*mean_I
    mean_a = cv2.boxFilter(a, cv2.CV_64F, (r, r))
    mean_b = cv2.boxFilter(b, cv2.CV_64F, (r, r))
    return mean_a*I + mean_b

guided = guided_filter(img.astype(np.float64)/255.0, 
                       img.astype(np.float64)/255.0, r=8, eps=0.01)

注意:导向滤波的 eps 参数很关键。我刚开始用的时候,eps 设得太小,结果出现了「梯度反转」——边缘处出现了不自然的亮暗条纹。后来我把 eps 调到 0.01~0.1 之间,问题就解决了。