第三章 频域去噪:傅里叶变换与频域滤波

各位同学,欢迎来到第三章。这一章我们聊聊频域去噪。说实话,很多做ISP的工程师对频域有点怵,觉得数学太深。我刚开始也这么想,直到在项目中吃了一次亏——有个传感器在特定光照下产生周期性条纹,空域滤波怎么调都去不掉。后来用频域方法,十分钟搞定。从那以后,我就把频域当成一个必备工具了。

3.1 为什么需要频域去噪?

你想想看,空域滤波就像用刷子刷墙,局部操作,效率高但有些噪声就是刷不掉。比如周期性噪声、摩尔纹这些,它们在空域里分布得乱七八糟,但在频域里却集中在几个点上。

频域去噪的核心思想很简单:把图像从空间域变换到频率域,在频率域里处理噪声,再变换回来。说白了,就是换个角度看问题。

核心观点:空域中难以分离的噪声,在频域中往往有清晰的分布特征。这就是频域去噪的底气所在。

3.2 傅里叶变换:从空间到频率

傅里叶变换是什么?我习惯这么理解:任何图像都可以看作无数个不同频率、不同方向的正弦波的叠加。傅里叶变换就是帮我们拆解这些成分的数学工具。

对于数字图像,我们用的是二维离散傅里叶变换(2D DFT):

F(u,v) = Σ Σ f(x,y) * exp(-j2π(ux/M + vy/N))

其中:
- f(x,y) 是空域像素值
- F(u,v) 是频域系数
- M,N 是图像尺寸
- u,v 是频率坐标

嗯,公式看着有点吓人。但实际用的时候,我们直接用快速傅里叶变换(FFT),OpenCV里一行代码就搞定了。

我的习惯:做频域处理前,先把图像中心化——把低频分量移到频谱中心。这样观察和操作都方便。用 np.fft.fftshift() 就行。

3.3 频谱分析:读懂图像的"基因"

拿到频谱图后怎么看?我教你一个简单的方法:

  • 中心区域:低频成分,对应图像的平滑区域、背景
  • 远离中心:高频成分,对应边缘、纹理、细节
  • 亮点或亮线:周期性噪声或特定方向的结构

举个例子。有一次我处理一张工业相机拍的电路板图像,频谱图上出现了几个对称的亮点。一看就知道——这是50Hz工频干扰。用带阻滤波器一卡,干净了。

注意:频谱图是对称的,因为实图像的傅里叶变换是共轭对称的。所以设计滤波器时,一定要成对处理。

3.4 频域滤波器设计

频域滤波说白了就是:设计一个掩膜,在频域里乘上去。乘1保留,乘0滤除,乘0.5衰减。

3.4.1 低通滤波器

低通滤波器保留低频,抑制高频。适合去除高斯噪声、椒盐噪声,但会模糊边缘。

def ideal_lowpass_filter(shape, cutoff):
    rows, cols = shape
    crow, ccol = rows//2, cols//2
    mask = np.zeros((rows, cols), np.uint8)
    for i in range(rows):
        for j in range(cols):
            dist = np.sqrt((i-crow)**2 + (j-ccol)**2)
            if dist <= cutoff:
                mask[i,j] = 1
    return mask

我个人不太喜欢理想低通滤波器,振铃效应太明显。我建议用高斯低通滤波器,过渡更平滑:

def gaussian_lowpass(shape, sigma):
    rows, cols = shape
    crow, ccol = rows//2, cols//2
    x = np.linspace(0, cols-1, cols)
    y = np.linspace(0, rows-1, rows)
    X, Y = np.meshgrid(x, y)
    dist = np.sqrt((X-ccol)**2 + (Y-crow)**2)
    mask = np.exp(-dist**2 / (2*sigma**2))
    return mask

3.4.2 高通滤波器

高通滤波器保留高频,抑制低频。用于锐化、边缘检测。但要注意——它也会放大噪声。

高通滤波器 = 1 - 低通滤波器。就这么简单。

避坑指南:我曾经在项目中直接用高通滤波器做边缘增强,结果图像噪声被放大了三倍。后来我改用拉普拉斯算子在频域实现,配合阈值控制,效果就好多了。

3.4.3 带阻滤波器

带阻滤波器是频域去噪的杀手锏。它专门针对特定频率范围的噪声进行抑制。

我处理过一张医学X光图像,上面有规律的网格噪声。频谱图上对应位置有四个对称的亮点。我设计了一个高斯带阻滤波器:

def gaussian_bandstop(shape, center, radius, width):
    rows, cols = shape
    crow, ccol = rows//2, cols//2
    x = np.linspace(0, cols-1, cols)
    y = np.linspace(0, rows-1, rows)
    X, Y = np.meshgrid(x, y)
    # 计算到目标频率点的距离
    dist = np.sqrt((X-center[0]-ccol)**2 + (Y-center[1]-crow)**2)
    mask = 1 - np.exp(-((dist-radius)**2) / (2*width**2))
    return mask

效果立竿见影。网格噪声消失了,图像细节几乎没受影响。

3.5 小波去噪入门

傅里叶变换有个局限:它只能告诉你图像里有哪些频率成分,但不知道这些频率出现在哪里。小波变换解决了这个问题——它同时提供频率和位置信息

小波去噪的基本流程:

  1. 对图像进行小波分解,得到近似系数和细节系数
  2. 对细节系数进行阈值处理(软阈值或硬阈值)
  3. 重构图像
import pywt

def wavelet_denoise(img, wavelet='db4', level=3, threshold=0.1):
    # 小波分解
    coeffs = pywt.wavedec2(img, wavelet, level=level)
    # 对每一层细节系数进行阈值处理
    coeffs_thresh = list(coeffs)
    for i in range(1, len(coeffs)):
        coeffs_thresh[i] = tuple(
            pywt.threshold(detail, threshold, mode='soft')
            for detail in coeffs_thresh[i]
        )
    # 重构
    return pywt.waverec2(coeffs_thresh, wavelet)

我的经验:小波去噪对纹理保留特别好。如果你处理的图像有丰富的细节(比如织物、毛发),小波去噪比频域滤波更合适。但计算量也更大,移动端慎用。

3.6 实战对比:频域 vs 空域

我整理了一个对比表,方便你选择:

噪声类型 推荐方法 原因
高斯噪声 空域高斯滤波 / 频域低通 两者效果接近,空域更快
椒盐噪声 空域中值滤波 频域方法对脉冲噪声效果差
周期性噪声 频域带阻滤波 空域几乎无法处理
摩尔纹 频域带阻/陷波滤波 频谱上有明显亮点
纹理噪声 小波去噪 保留细节的同时去噪

3.7 本章小结

频域去噪不是万能的,但它是你工具箱里不可或缺的一把刀。我建议你:

  • 先学会看频谱图,这是基本功
  • 周期性噪声、摩尔纹——优先考虑频域方法
  • 细节丰富的图像——试试小波去噪
  • 别迷信理想滤波器,高斯过渡更实用

下一章我们聊聊时域去噪,也就是多帧融合。那是另一个有意思的话题。到时候见。