查看全课大纲(第 11 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
傅里叶变换
小象实战讲义 · 基于深度学习的计算机视觉
在图像预处理中,我们之前学习了空间域的处理方法,如卷积、平滑和锐化。本节我们将进入一个全新的视角——频率域。通过傅里叶变换,我们可以将图像从空间域转换到频率域,从而揭示图像中不同频率成分的构成,并实现一些空间域难以完成的滤波操作。学完本节,你将理解傅里叶变换的基本思想,并掌握其在图像低通滤波、相位滤波和加速卷积计算中的应用。
💡 核心导读
- 从空间到频率:傅里叶变换的核心是将信号(如图像)分解为不同频率、不同幅度的正弦波(或余弦波)的叠加。
- 欧拉公式的几何意义:理解复平面上的圆周运动是理解傅里叶变换数学形式的关键。
- 二维离散傅里叶变换:图像处理中使用的是二维离散形式,任何图像都可以看作一系列特定方向和频率的“条纹”图像的叠加。
- 频率域滤波:在频率域中,通过保留或滤除特定频率成分,可以实现低通滤波(模糊)、高通滤波(边缘提取)和特定干扰的滤除。
- 卷积定理:空间域的卷积运算等价于频率域的乘法运算,结合快速傅里叶变换(FFT)可以大幅提升大卷积核的运算效率。
傅里叶变换的基本思想
傅里叶变换由法国数学家、物理学家让·巴普蒂斯·约瑟夫·傅里叶提出。他在研究热传导时提出了一个革命性的猜想:任何复杂的信号,都可以由足够多个不同频率和幅度的正弦波和余弦波组合而成。
这个想法在当时遭到了另一位数学巨匠拉格朗日的强烈反对。拉格朗日认为,像方波这样带有尖锐棱角的信号,不可能用完全平滑的正余弦波来完美拟合。这场争论的答案其实在于“足够多”这个词。从理论上讲,如果使用无穷多个不同频率的正弦波进行叠加,是可以无限逼近甚至等于原始方波信号的。但在实际应用中,我们使用有限项进行逼近,在能量误差可接受的范围内,傅里叶的观点是完全成立的。
那么,如何将这一思想应用到图像上呢?图像是二维信号。我们可以想象,一张带有规律性明暗条纹的图片,可以分解为几个更简单的、具有单一方向和频率的条纹图片的叠加。
例如,设想一张 128×128 的灰度画布,上面叠加了两组条纹:一组是宽而稀疏的竖直明暗条纹,另一组是细密且有一定倾角的斜条纹。人眼看到的是两组条纹交叠形成的复杂图案,但它的“构成成分”其实非常简单。
在频率域中,每一种单一方向和频率的条纹,会对应频率谱(即傅里叶变换后的结果)上的一个点(更准确地说,是一对关于原点对称的共轭点)。稀疏的竖条纹对应低频点,密集的斜条纹对应高频点。将这些频率点所代表的波形叠加起来,就还原出了原始图像。
因此,傅里叶变换所做的工作,就是将图像从空间域(我们看到的像素矩阵)转换到频率域(描述图像由哪些频率成分构成)。
理解变换的数学钥匙:欧拉公式
傅里叶变换的数学公式看起来有些复杂,但其核心依赖于一个优美的公式——欧拉公式:
这里, 是自然对数的底, 是虚数单位。这个公式不仅仅是一个数学游戏,它有着深刻的几何意义。
等式右边, 和 实际上是一个单位圆上某一点的横坐标和纵坐标。那么, 就是用一种非常简洁的方式(一个复数),描述了复平面上一个做匀速圆周运动的点的位置。随着角度 的变化,这个点就在圆周上运动。
如果我们将 视为时间 的函数(例如 ),那么 就描述了一个随时间在复平面上旋转的点。这个点在实轴(Re)和虚轴(Im)上的投影,分别就是余弦波 和正弦波 。
傅里叶变换的数学形式与逆变换
有了欧拉公式的基础,我们来看连续傅里叶变换的公式:
这个公式可以这样理解:它将时间域信号 与一系列不同频率 的旋转因子 做内积(积分)。其结果 是一个复数,它的模长代表了频率为 的正弦波在原始信号中的幅度,它的幅角则代表了该频率成分的相位。
相比之下,傅里叶逆变换的公式可能更直观地体现了“叠加”的思想:
这个公式明确地告诉我们:原始信号 是由无数个不同频率 、不同幅度 、不同相位 的复正弦波 叠加(积分)而成的。 就是指导如何叠加的“配方”。
图像的二维离散傅里叶变换
计算机处理的是离散的数字图像,因此我们使用的是二维离散傅里叶变换(2D-DFT)。其公式如下:
其中 是大小为 的图像在空间域的像素值, 是其对应的频率域表示。 是频率坐标。
在频率域图像(频谱图)中,频率坐标 代表图像的直流分量(即平均亮度),越远离该点,代表的频率越高。直接算出的 中低频位于四个角,为了显示方便,通常会对 (幅度谱)做对数变换并用 fftshift 把低频移到画面中心。
任何一幅图像,都可以看作是由一系列二维的“基图像”线性组合而成。这些基图像就是不同频率和方向的二维正弦波图案,看起来像是明暗相间的条纹。下面我们用代码生成几个这样的二维傅里叶基图像。
import numpy as np
import cv2
# 生成图像尺寸
height, width = 128, 128
# 创建一个空画布用于组合显示
canvas = np.zeros((height*2, width*2), dtype=np.float32)
# 生成四种不同频率/方向的基图像
bases = []
freqs = [(10, 0), (20, 0), (0, 15), (10, 10)] # (u, v) 频率
for idx, (u, v) in enumerate(freqs):
base = np.zeros((height, width), dtype=np.float32)
for x in range(width):
for y in range(height):
# 计算二维正弦波的值,值域在[-1, 1]
value = np.cos(2 * np.pi * (u * x / width + v * y / height))
base[y, x] = value
# 归一化到[0, 1]以便显示
base = (base + 1) / 2
bases.append(base)
# 拼接到画布上
row, col = divmod(idx, 2)
canvas[row*height:(row+1)*height, col*width:(col+1)*width] = base
# 由于不能使用imshow,我们打印一小块区域来验证
print("左上基图像(低频竖直条纹)左上角 8x8 区域:")
np.set_printoptions(precision=2, suppress=True)
print(bases[0][:8, :8])左上基图像(低频竖直条纹)左上角 8x8 区域:
[[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]
[1. 0.94 0.78 0.55 0.31 0.11 0.01 0.02]]频率域滤波的应用
在频率域中,我们可以通过操作频谱 来方便地实现多种滤波效果,然后再通过逆变换回到空间域。
1. 低通与高通滤波
低通滤波是保留低频成分(频谱中心部分),滤除高频成分(频谱边缘部分)。这相当于在空间域进行模糊操作,因为图像的细节和边缘对应高频信息。 高通滤波则相反,保留高频滤除低频,用于提取边缘和轮廓。
import numpy as np
import cv2
# 1. 创建一个简单的测试图像:中心有一个亮块
img = np.zeros((64, 64), dtype=np.float32)
img[24:40, 24:40] = 1.0 # 中心16x16区域为白色
# 2. 进行二维离散傅里叶变换
dft = cv2.dft(img, flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft) # 将低频移到中心
# 3. 创建低通滤波器(理想滤波器)
rows, cols = img.shape
crow, ccol = rows // 2, cols // 2
mask_low = np.zeros((rows, cols, 2), dtype=np.float32) # 2通道,对应复数的实部和虚部
radius = 15
cv2.circle(mask_low, (ccol, crow), radius, (1,1), -1) # 圆形区域为1
# 4. 应用低通滤波
dft_shift_low = dft_shift * mask_low
dft_low = np.fft.ifftshift(dft_shift_low)
img_back_low = cv2.idft(dft_low)
img_back_low = cv2.magnitude(img_back_low[:,:,0], img_back_low[:,:,1]) # 取模长
# 5. 创建高通滤波器(理想滤波器)
mask_high = np.ones((rows, cols, 2), dtype=np.float32)
cv2.circle(mask_high, (ccol, crow), radius, (0,0), -1) # 圆形区域为0
# 6. 应用高通滤波
dft_shift_high = dft_shift * mask_high
dft_high = np.fft.ifftshift(dft_shift_high)
img_back_high = cv2.idft(dft_high)
img_back_high = cv2.magnitude(img_back_high[:,:,0], img_back_high[:,:,1])
# 打印原图、低通结果、高通结果的中心区域值
print("原图中心 8x8 区域:")
print(img[28:36, 28:36])
print("\n低通滤波后中心 8x8 区域(已模糊):")
print(img_back_low[28:36, 28:36].round(3))
print("\n高通滤波后中心 8x8 区域(主要为边缘):")
print(img_back_high[28:36, 28:36].round(3))原图中心 8x8 区域:
[[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]]
低通滤波后中心 8x8 区域(已模糊):
[[3791.04 4040.23 4047.187 3838.158 3838.158 4047.187 4040.23 3791.039]
[4040.23 4295.735 4293.081 4071.863 4071.863 4293.082 4295.736 4040.23 ]
[4047.187 4293.081 4310.501 4113.486 4113.486 4310.501 4293.081 4047.187]
[3838.158 4071.863 4113.486 3945.332 3945.332 4113.486 4071.863 3838.158]
[3838.158 4071.863 4113.487 3945.333 3945.333 4113.487 4071.863 3838.158]
[4047.187 4293.081 4310.502 4113.487 4113.486 4310.502 4293.081 4047.187]
[4040.23 4295.734 4293.081 4071.863 4071.863 4293.081 4295.735 4040.23 ]
[3791.04 4040.23 4047.187 3838.158 3838.158 4047.187 4040.23 3791.04 ]]
高通滤波后中心 8x8 区域(主要为边缘):
[[304.96 55.77 48.813 257.842 257.842 48.812 55.77 304.96 ]
[ 55.77 199.736 197.081 24.136 24.137 197.082 199.735 55.77 ]
[ 48.813 197.082 214.501 17.487 17.487 214.502 197.082 48.812]
[257.842 24.137 17.487 150.667 150.668 17.487 24.137 257.842]
[257.842 24.137 17.487 150.667 150.668 17.487 24.137 257.842]
[ 48.813 197.082 214.502 17.487 17.487 214.502 197.082 48.812]
[ 55.77 199.736 197.081 24.136 24.137 197.082 199.735 55.77 ]
[304.96 55.77 48.813 257.842 257.842 48.813 55.77 304.96 ]]从输出可以看到两点。注意 cv2.idft 默认不做归一化,打印值需要除以 才是真实灰度:
- 低通:白色块内部仍在 3791
4311 之间(约等于 0.931.05),说明均匀区域基本没变;被抹平的是块与背景交界处的那一圈,那里原本是陡峭的跳变,现在变成了渐变。 - 高通:白色块内部只剩 17
305(约 0.0040.074),原本均匀的亮区被压暗到接近 0;这正体现了它提取的是变化的部分——均匀区域没有高频成分,所以响应接近零,响应集中在块的边界上。
2. 相位滤波(方向性频谱滤除)
前两类操作改变的是频谱中各频率成分的幅度,而频谱的相位同样承载信息。以一张带有规则明暗条纹的桥梁照片为例:这些条纹会让频谱上出现一条沿特定方向的亮线(该方向上的条纹都集中在这一条线上)。
如果把这条亮线所在区域整体置零、再逆变换回空间域,原本黑白分明的条纹结构就会消失——看起来像是被“漂白”了一样。这说明:频谱中被抹掉的不是某个亮度值,而是“沿这个方向、以这个周期重复”这一结构本身。反过来,如果只抹掉亮线中很小的一段,就只去掉某个特定周期/角度的条纹,其余结构保持不变。
这也是频率域滤波相比空间域模板的一个特点:按“方向 + 周期”直接下手,比设计空间域卷积核更直观。
3. 频段滤除(去周期性噪声)
有时图像会受到特定频率的周期性噪声干扰(如老式电视画面上的滚动条纹)。在频谱上,这种噪声会表现为远离中心的一对对称的亮点——它对应一个叠加在原图上的、特定频率的正弦波。通过手动将这些亮点置零(滤除该频率成分),再逆变换,就能把叠加的条纹去掉,恢复出干净的原图。
卷积定理:加速计算的利器
傅里叶变换在图像处理中还有一个极其重要的应用,它源于卷积定理: 空间域中的卷积运算,等价于频率域中的乘法运算。
用公式表示即为: 其中 表示卷积, 表示逐点相乘, 和 分别是 和 的傅里叶变换。
对于大尺寸的卷积核,在空间域进行卷积计算量很大(与图像和卷积核尺寸的乘积成正比)。而利用卷积定理,我们可以:
- 将图像和卷积核分别进行傅里叶变换(到频率域)。
- 在频率域将两者相乘(计算量很小)。
- 将结果进行傅里叶逆变换(回到空间域)。
这其中最关键的是,傅里叶变换及其逆变换本身可以通过快速傅里叶变换(FFT) 算法高效完成。对于较大尺寸的图像和卷积核,这种“变换-相乘-逆变换”的策略往往比直接空间卷积快得多。
import numpy as np
import cv2
np.random.seed(42)
# 生成一个小图像和一个卷积核
img = np.random.randn(32, 32).astype(np.float32) # 32x32 随机图像
kernel = np.ones((5, 5), dtype=np.float32) / 25.0 # 5x5 均值滤波核
# 方法1:空间域卷积(cv2.filter2D 内部是高效实现)
result_spatial = cv2.filter2D(img, -1, kernel, borderType=cv2.BORDER_CONSTANT)
# 方法2:基于FFT的卷积(原理演示)
# 获取填充后尺寸
rows, cols = img.shape
k_rows, k_cols = kernel.shape
padded_rows = rows + k_rows - 1
padded_cols = cols + k_cols - 1
# 填充图像和核,并计算FFT
img_padded = np.zeros((padded_rows, padded_cols), dtype=np.float32)
img_padded[:rows, :cols] = img
kernel_padded = np.zeros((padded_rows, padded_cols), dtype=np.float32)
kernel_padded[:k_rows, :k_cols] = kernel
# 傅里叶变换
img_fft = cv2.dft(img_padded, flags=cv2.DFT_COMPLEX_OUTPUT)
kernel_fft = cv2.dft(kernel_padded, flags=cv2.DFT_COMPLEX_OUTPUT)
# 频率域相乘 (复数乘法)
result_fft_complex = np.zeros_like(img_fft)
result_fft_complex[:,:,0] = img_fft[:,:,0]*kernel_fft[:,:,0] - img_fft[:,:,1]*kernel_fft[:,:,1] # 实部
result_fft_complex[:,:,1] = img_fft[:,:,0]*kernel_fft[:,:,1] + img_fft[:,:,1]*kernel_fft[:,:,0] # 虚部
# 逆变换并取实部;cv2.idft 默认不归一化,需手动除以元素总数
result_fft_full = cv2.idft(result_fft_complex)
result_fft_full = result_fft_full[:,:,0] / (padded_rows * padded_cols) # 取实部并归一化
# 对齐:filter2D 以核中心为锚点,相当于从完整卷积结果中偏移 (k-1)//2 处裁剪
off_r, off_c = (k_rows - 1) // 2, (k_cols - 1) // 2
result_fft = result_fft_full[off_r:off_r + rows, off_c:off_c + cols]
# 比较结果差异(应仅剩浮点误差)
diff = np.max(np.abs(result_spatial - result_fft))
print(f"空间卷积与FFT卷积结果最大差异: {diff:.6e}")
print("注意:此演示规模小,FFT优势不明显。对于大图像大核,FFT方法通常更快。")空间卷积与FFT卷积结果最大差异: 1.788139e-07
注意:此演示规模小,FFT优势不明显。对于大图像大核,FFT方法通常更快。📝 动手练一练
- 概念辨析:拉格朗日为什么反对傅里叶“任何信号可由正余弦波组成”的观点?从“有限项逼近”和“无限项叠加”两个角度,最终如何调和两人的争论?
👉 点击查看参考答案
拉格朗日反对的理由是基于有限项叠加的视角。他认为,像方波这样带有不连续跳变(棱角) 的信号,无法用有限个完全平滑的正弦或余弦波的线性组合来精确表示。在有限项叠加下,方波的跳变边缘总会出现振荡(吉布斯现象),无法完全消除棱角。
傅里叶的观点则包含了无限项的可能性。从理论上看,当叠加的正弦波数量趋向于无穷多时,其和可以无限逼近甚至等于原始方波信号。在实际工程和数学分析中,只要无限级数收敛,并且在能量或均方误差的意义上与原信号一致,就认为傅里叶的表示是成立的。
因此,两人的争论可以调和为:拉格朗日是对的,有限项无法完美表示不连续信号;傅里叶也是对的,无限项可以做到完美表示,而有限项在误差允许的范围内足以满足实际应用需求。
- 手算理解:假设有一个极其简单的4x4二值图像
img和一个2x2的均值卷积核kernel,如下:
请手动计算空间域中,以img = [[0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]] kernel = [[0.25, 0.25], [0.25, 0.25]]'valid'模式(即卷积核完全在图像内滑动)进行卷积后,输出特征图左上角第一个元素的值(即卷积核覆盖img[0:2, 0:2]的结果)。
👉 点击查看参考答案
卷积核覆盖 img[0:2, 0:2] 的区域为:
[[0, 0],
[0, 1]]计算过程为逐元素相乘后求和: (0*0.25) + (0*0.25) + (0*0.25) + (1*0.25) = 0 + 0 + 0 + 0.25 = 0.25
所以,输出特征图左上角第一个元素的值为 0.25。
本章小结
本节我们深入探讨了图像处理中从空间域到频率域的重要桥梁——傅里叶变换。我们从傅里叶的基本思想出发,理解了任何复杂信号都可以分解为不同频率正弦波的叠加。通过欧拉公式,我们看到了复平面上圆周运动与正余弦波的深刻联系,这是理解傅里叶变换数学形式的关键。
对于图像这种二维离散信号,我们使用二维离散傅里叶变换(DFT)将其转换到频率域。在频率域中,图像的能量分布以频谱图的形式呈现,中心代表低频(整体轮廓和慢变化),外围代表高频(细节、边缘和噪声)。基于此,我们可以通过操作频谱轻松实现低通滤波(模糊)、高通滤波(边缘提取)和特定频段滤除(去周期噪声)等效果。
最后,我们介绍了强大的卷积定理:空间卷积等价于频率域乘法。结合快速傅里叶变换(FFT)算法,这为处理大卷积核运算提供了一种高效的加速途径。
📋 行动清单
- 理解核心思想:能够向他人解释“为什么图像可以看成不同频率条纹的叠加”。
- 区分滤波效果:明确低通滤波和高通滤波在频率域的操作(保留中心/保留周边)及其在空间域对应的效果(模糊/锐化)。
- 代码实践:运行本节中的代码示例,尝试修改低通滤波器的半径或高通滤波器的形状,观察输出图像的变化,加深对频率域操作的理解。
—— 小象教研组