第 05 章 — 图像滤波
第 05 章 — 图像滤波
5.1 滤波基础
图像滤波(Image Filtering)是通过卷积运算改变图像像素值的过程。
卷积原理
输入图像 I 卷积核 K (3×3) 输出图像 O
┌───┬───┬───┐ ┌────┬────┬────┐
│a │b │c │ │k00 │k01 │k02 │ O(x,y) = ΣΣ K(i,j) × I(x+i, y+j)
├───┼───┼───┤ ├────┼────┼────┤
│d │e │f │ * │k10 │k11 │k12 │
├───┼───┼───┤ ├────┼────┼────┤
│g │h │i │ │k20 │k21 │k22 │
└───┴───┴───┘ └────┴────┴────┘
卷积核大小: 通常 3×3, 5×5, 7×7(必须为奇数)
边界处理方式
| 方式 | 常量 | 说明 |
|---|---|---|
| 默认 | BORDER_DEFAULT | 反射填充 |
| 常数填充 | BORDER_CONSTANT | 用指定颜色填充 |
| 复制边缘 | BORDER_REPLICATE | 复制最边缘像素 |
| 反射 | BORDER_REFLECT | 镜像反射 |
| 包裹 | BORDER_WRAP | 周期性填充 |
5.2 均值滤波(Box Filter)
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 均值滤波 — 用邻域平均值替代中心像素
# 核: 1/9 * [[1,1,1],[1,1,1],[1,1,1]]
blur_3 = cv2.blur(img, (3, 3)) # 3×3 核
blur_5 = cv2.blur(img, (5, 5)) # 5×5 核
blur_15 = cv2.blur(img, (15, 15)) # 15×15 核(更强模糊)
# 归一化方框滤波(等价于 cv2.blur)
blur_box = cv2.boxFilter(img, -1, (5, 5), normalize=True)
# 非归一化方框滤波(求和,不平均)
sum_box = cv2.boxFilter(img, -1, (5, 5), normalize=False)
// C++ 均值滤波
cv::Mat img = cv::imread("photo.jpg");
cv::Mat blurred;
cv::blur(img, blurred, cv::Size(5, 5));
均值滤波核
K = 1/(ksize_w × ksize_h) × ┌ ┐
│ 1 1 1 1 1│
│ 1 1 1 1 1│ (5×5)
│ 1 1 1 1 1│
│ 1 1 1 1 1│
│ 1 1 1 1 1│
└ ┘
5.3 高斯滤波(Gaussian Blur)
高斯滤波是最常用的降噪方法,使用高斯分布作为权重。
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 基本高斯模糊
blur_g3 = cv2.GaussianBlur(img, (5, 5), 0) # σ 由核大小自动计算
blur_g5 = cv2.GaussianBlur(img, (5, 5), 1.5) # σ=1.5
blur_g15 = cv2.GaussianBlur(img, (15, 15), 3.0) # σ=3.0,强模糊
# 分离高斯核(性能更好,大核时推荐)
# OpenCV 内部已优化,通常不需要手动分离
高斯核可视化
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 生成 5×5 高斯核
kernel = cv2.getGaussianKernel(5, 1.0)
kernel_2d = kernel @ kernel.T # 外积得到 2D 核
print("5×5 高斯核 (σ=1.0):")
print(np.round(kernel_2d, 4))
# 3D 可视化
x = np.arange(-2, 3)
y = np.arange(-2, 3)
X, Y = np.meshgrid(x, y)
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, kernel_2d, cmap='viridis')
plt.title("Gaussian Kernel (5x5, σ=1.0)")
plt.show()
高斯核参考值
| 核大小 | σ=0.5 | σ=1.0 | σ=2.0 | σ=3.0 |
|---|---|---|---|---|
| 3×3 | 锐化 | 轻微模糊 | 模糊 | — |
| 5×5 | 轻微 | 适中 | 较强 | — |
| 7×7 | — | 适中 | 较强 | 强 |
| 11×11 | — | 轻微 | 适中 | 较强 |
经验法则: σ ≈ (ksize - 1) / 6 时效果最佳
5.4 中值滤波(Median Filter)
中值滤波对椒盐噪声特别有效,用邻域的中值替代中心像素。
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 添加椒盐噪声
def add_salt_pepper(image, salt_prob=0.02, pepper_prob=0.02):
noisy = image.copy()
h, w = noisy.shape[:2]
# 盐噪声(白点)
num_salt = int(h * w * salt_prob)
coords = [np.random.randint(0, i, num_salt) for i in [h, w]]
noisy[coords[0], coords[1]] = 255
# 椒噪声(黑点)
num_pepper = int(h * w * pepper_prob)
coords = [np.random.randint(0, i, num_pepper) for i in [h, w]]
noisy[coords[0], coords[1]] = 0
return noisy
noisy = add_salt_pepper(img, 0.03, 0.03)
# 中值滤波
median_3 = cv2.medianBlur(noisy, 3) # 3×3 核
median_5 = cv2.medianBlur(noisy, 5) # 5×5 核
# 对比高斯滤波
gauss = cv2.GaussianBlur(noisy, (5, 5), 0)
# 中值滤波在去除椒盐噪声方面远优于高斯滤波
注意: 中值滤波核大小必须是奇数且 ≥ 3。核越大,去噪效果越强,但细节损失也越大。
5.5 双边滤波(Bilateral Filter)
双边滤波是保边去噪利器,在平滑的同时保留边缘。
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 双边滤波参数
# d: 像素邻域直径(-1 时自动从 sigmaSpace 计算)
# sigmaColor: 颜色空间标准差(越大,颜色差异大的像素越可能被混合)
# sigmaSpace: 坐标空间标准差(越大,距离远的像素越可能被影响)
bf1 = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
bf2 = cv2.bilateralFilter(img, d=15, sigmaColor=150, sigmaSpace=150)
bf3 = cv2.bilateralFilter(img, d=25, sigmaColor=200, sigmaSpace=200)
# 磨皮效果(多次迭代)
skin = img.copy()
for _ in range(3):
skin = cv2.bilateralFilter(skin, 9, 50, 50)
滤波方式对比
| 方法 | 去噪效果 | 边缘保持 | 速度 | 适用场景 |
|---|---|---|---|---|
| 均值滤波 | ★★☆ | ★☆☆ | ★★★★★ | 快速预处理 |
| 高斯滤波 | ★★★ | ★★☆ | ★★★★★ | 通用降噪 |
| 中值滤波 | ★★★★ | ★★★ | ★★★★ | 椒盐噪声 |
| 双边滤波 | ★★★ | ★★★★★ | ★★☆ | 美颜/保边降噪 |
| 非局部均值 | ★★★★★ | ★★★★ | ★☆☆ | 高质量降噪 |
5.6 非局部均值去噪(NLM)
import cv2
img = cv2.imread("photo.jpg")
# 彩色图去噪
denoised_color = cv2.fastNlMeansDenoisingColored(
img, None,
h=10, # 滤波强度(亮度)
hColor=10, # 滤波强度(颜色)
templateWindowSize=7,
searchWindowSize=21
)
# 灰度图去噪
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
denoised_gray = cv2.fastNlMeansDenoising(
gray, None,
h=10,
templateWindowSize=7,
searchWindowSize=21
)
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| h | 3-20 | 噪声越大,值越大 |
| hColor | 3-20 | 彩色通道滤波强度 |
| templateWindowSize | 7 | 模板窗口(奇数) |
| searchWindowSize | 21 | 搜索窗口(奇数) |
5.7 锐化(Sharpening)
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 方法 1: 自定义锐化核
kernel_sharpen = np.array([
[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]
], dtype=np.float32)
sharpened = cv2.filter2D(img, -1, kernel_sharpen)
# 方法 2: 更强的锐化
kernel_strong = np.array([
[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1]
], dtype=np.float32)
sharpened_strong = cv2.filter2D(img, -1, kernel_strong)
# 方法 3: Unsharp Mask(反锐化掩模)
# 原理: 锐化 = 原图 + (原图 - 模糊图) × 系数
def unsharp_mask(image, sigma=1.0, strength=1.5):
blurred = cv2.GaussianBlur(image, (0, 0), sigma)
return cv2.addWeighted(image, 1.0 + strength, blurred, -strength, 0)
sharpened_um = unsharp_mask(img, sigma=2.0, strength=0.5)
常用卷积核
| 核类型 | 矩阵 | 效果 |
|---|---|---|
| 锐化 | [0,-1,0; -1,5,-1; 0,-1,0] | 轻微锐化 |
| 强锐化 | [-1,-1,-1; -1,9,-1; -1,-1,-1] | 强锐化 |
| 浮雕 | [-2,-1,0; -1,1,1; 0,1,2] | 浮雕效果 |
| 边缘检测 | [-1,-1,-1; -1,8,-1; -1,-1,-1] | 边缘提取 |
| Sobel X | [-1,0,1; -2,0,2; -1,0,1] | 水平边缘 |
| Sobel Y | [-1,-2,-1; 0,0,0; 1,2,1] | 垂直边缘 |
5.8 自定义卷积核
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
# 通用卷积函数
# ddepth: 输出深度,-1 表示与输入相同
# kernel: 卷积核
# anchor: 锚点,(-1,-1) 表示中心
# delta: 偏移量
# borderType: 边界处理方式
# 自定义均值核
kernel_avg = np.ones((5, 5), np.float32) / 25.0
result_avg = cv2.filter2D(img, -1, kernel_avg)
# 自定义高斯核(手动生成)
def gaussian_kernel(size, sigma):
"""生成归一化高斯核"""
ax = np.arange(-size // 2 + 1, size // 2 + 1)
xx, yy = np.meshgrid(ax, ax)
kernel = np.exp(-(xx**2 + yy**2) / (2 * sigma**2))
return kernel / kernel.sum()
k = gaussian_kernel(5, 1.5)
result_gauss = cv2.filter2D(img, -1, k)
# 浮雕效果
kernel_emboss = np.array([
[-2, -1, 0],
[-1, 1, 1],
[ 0, 1, 2]
], dtype=np.float32)
result_emboss = cv2.filter2D(img, -1, kernel_emboss)
自定义核验证
def validate_kernel(kernel):
"""验证卷积核的基本属性"""
print(f"形状: {kernel.shape}")
print(f"数据类型: {kernel.dtype}")
print(f"元素和: {kernel.sum():.4f}")
print(f"最大值: {kernel.max():.4f}")
print(f"最小值: {kernel.min():.4f}")
print(f"是否对称: {np.allclose(kernel, kernel.T)}")
print("核矩阵:")
print(np.round(kernel, 4))
5.9 形态学滤波预览
import cv2
import numpy as np
img = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
# 形态学梯度(边缘检测)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
# 顶帽变换(提取亮细节)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# 黑帽变换(提取暗细节)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
5.10 实战:图像降噪流水线
"""
denoise_pipeline.py — 自适应图像降噪
"""
import cv2
import numpy as np
def estimate_noise_level(gray_img):
"""使用拉普拉斯算子估计噪声水平"""
laplacian = cv2.Laplacian(gray_img, cv2.CV_64F)
noise = laplacian.var()
return noise
def adaptive_denoise(img, noise_threshold=500):
"""根据噪声水平自动选择降噪策略"""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
noise = estimate_noise_level(gray)
print(f"估计噪声水平: {noise:.0f}")
if noise < noise_threshold * 0.5:
# 低噪声 — 只需轻微处理
return cv2.GaussianBlur(img, (3, 3), 0.5), "高斯模糊 3×3"
elif noise < noise_threshold:
# 中等噪声 — 双边滤波
return cv2.bilateralFilter(img, 9, 50, 50), "双边滤波"
elif noise < noise_threshold * 3:
# 较高噪声 — NLM 去噪
return cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21), \
"NLM 去噪"
else:
# 高噪声 — 多步降噪
step1 = cv2.fastNlMeansDenoisingColored(img, None, 15, 15, 7, 21)
step2 = cv2.bilateralFilter(step1, 5, 40, 40)
return step2, "NLM + 双边联合降噪"
# 使用
img = cv2.imread("noisy_photo.jpg")
if img is not None:
result, method = adaptive_denoise(img)
print(f"使用方法: {method}")
cv2.imwrite("denoised.jpg", result)
5.11 性能优化
| 技巧 | 说明 |
|---|---|
| 核大小选择 | 小核(3×3)多次迭代 ≈ 大核一次,但更快 |
| 分离卷积 | 2D 卷积可分离为两个 1D 卷积(OpenCV 自动优化) |
cv2.blur vs cv2.GaussianBlur | 均值滤波更快,适合预处理 |
| GPU 加速 | cv2.cuda.createGaussianFilter() 比 CPU 快 10-50 倍 |
| 数据类型 | uint8 比 float32 快 2-3 倍(精度足够时) |
import time
img = cv2.imread("photo.jpg")
# 性能测试
def benchmark(func, name, n=100):
start = time.perf_counter()
for _ in range(n):
result = func(img)
elapsed = (time.perf_counter() - start) / n * 1000
print(f"{name:30s}: {elapsed:.2f} ms")
benchmark(lambda im: cv2.blur(im, (5, 5)), "blur 5x5")
benchmark(lambda im: cv2.GaussianBlur(im, (5, 5), 0), "GaussianBlur 5x5")
benchmark(lambda im: cv2.medianBlur(im, 5), "medianBlur 5")
benchmark(lambda im: cv2.bilateralFilter(im, 9, 75, 75), "bilateralFilter")
5.12 扩展阅读
| 资源 | 链接 | 说明 |
|---|---|---|
| OpenCV 滤波文档 | docs.opencv.org/4.x/d4/d13/tutorial_py_filtering | 滤波教程 |
| 卷积可视化 | setosa.io/ev/image-kernels | 交互式核可视化 |
| 下一章 | 第 06 章 — 边缘检测 | Sobel/Canny/霍夫 |
本章小结: 掌握了均值、高斯、中值、双边、NLM 等主流滤波器的原理与用法,理解了自定义卷积核的构建方式,以及在实际场景中如何选择合适的降噪策略。