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 之间,问题就解决了。