强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OpenCV 计算机视觉完全教程 / 第 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
)
参数推荐范围说明
h3-20噪声越大,值越大
hColor3-20彩色通道滤波强度
templateWindowSize7模板窗口(奇数)
searchWindowSize21搜索窗口(奇数)

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 倍
数据类型uint8float32 快 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 等主流滤波器的原理与用法,理解了自定义卷积核的构建方式,以及在实际场景中如何选择合适的降噪策略。