第三章 频域去噪:傅里叶变换与频域滤波
各位同学,欢迎来到第三章。这一章我们聊聊频域去噪。说实话,很多做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 小波去噪入门
傅里叶变换有个局限:它只能告诉你图像里有哪些频率成分,但不知道这些频率出现在哪里。小波变换解决了这个问题——它同时提供频率和位置信息。
小波去噪的基本流程:
- 对图像进行小波分解,得到近似系数和细节系数
- 对细节系数进行阈值处理(软阈值或硬阈值)
- 重构图像
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 本章小结
频域去噪不是万能的,但它是你工具箱里不可或缺的一把刀。我建议你:
- 先学会看频谱图,这是基本功
- 周期性噪声、摩尔纹——优先考虑频域方法
- 细节丰富的图像——试试小波去噪
- 别迷信理想滤波器,高斯过渡更实用
下一章我们聊聊时域去噪,也就是多帧融合。那是另一个有意思的话题。到时候见。