本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
§1.2.12

傅里叶变换、DCT、小波变换在图像处理中的作用?JPEG 压缩流程?

核心概念

  • 傅里叶变换 (Fourier Transform, FT):一种将信号(如图像)从其空间域(像素位置)转换到频率域(频率分量)的数学变换。它将图像分解为不同频率和方向的正弦/余弦波的叠加。其核心思想是分析图像中包含的频率成分,低频对应图像的平滑区域,高频对应边缘、纹理和噪声。

  • 离散余弦变换 (Discrete Cosine Transform, DCT):一种与傅里叶变换相关的实数域变换。对于高度相关的自然图像信号,DCT 具有出色的“能量集中”特性,即图像的大部分能量(视觉信息)会集中在少数几个低频系数上。这个特性是 JPEG 压缩算法的基石。

  • 小波变换 (Wavelet Transform, WT):一种提供时频(或空频)局部化分析的工具。与使用全局正弦波的傅里叶变换不同,小波变换使用在空间和频率上都局部化的“小波”基函数。这使得它能同时分析图像的频率成分及其出现的空间位置,非常适合捕捉和表示图像中的瞬时、非平稳特征(如边缘)。

  • JPEG (Joint Photographic Experts Group):一种广泛使用的有损图像压缩标准。它通过色彩空间转换、色度二次采样、DCT、量化和熵编码等一系列步骤,在保留可接受视觉质量的前提下,大幅减小图像文件大小。

原理与推导

1. 傅里叶变换 (Fourier Transform)

对于一个大小为 M×NM \times N 的图像 f(x,y)f(x, y),其二维离散傅里叶变换 (2D-DFT) 定义为:

F(u,v)=x=0M1y=0N1f(x,y)ei2π(uxM+vyN)F(u, v) = \sum_{x=0}^{M-1} \sum_{y=0}^{N-1} f(x, y) e^{-i 2\pi \left(\frac{ux}{M} + \frac{vy}{N}\right)}

其中,u=0,1,...,M1u=0, 1, ..., M-1v=0,1,...,N1v=0, 1, ..., N-1 是频率变量。F(u,v)F(u,v) 是一个复数,代表了频率为 (u,v)(u, v) 的二维正弦波分量的幅度和相位。

其逆变换 (2D-IDFT) 为:

f(x,y)=1MNu=0M1v=0N1F(u,v)ei2π(uxM+vyN)f(x, y) = \frac{1}{MN} \sum_{u=0}^{M-1} \sum_{v=0}^{N-1} F(u, v) e^{i 2\pi \left(\frac{ux}{M} + \frac{vy}{N}\right)}
  • 直观解释
    • F(0,0)F(0,0) 称为直流分量 (DC component),代表图像的平均灰度值。
    • 频谱图中心(经过fftshift后)是低频区域,对应图像中大面积的平滑背景。
    • 频谱图的边缘是高频区域,对应图像的边缘、细节和噪声。
  • 复杂度:直接计算 DFT 的复杂度为 O(M2N2)O(M^2 N^2)。使用快速傅里叶变换 (FFT) 算法,可以将复杂度降低到 O(MNlog(MN))O(MN \log(MN))

2. 离散余弦变换 (Discrete Cosine Transform)

对于一个 N×NN \times N 的图像块 f(x,y)f(x, y),其二维 DCT-II 型(JPEG 中使用的类型)定义为:

G(u,v)=α(u)α(v)x=0N1y=0N1f(x,y)cos[π(2x+1)u2N]cos[π(2y+1)v2N]G(u, v) = \alpha(u) \alpha(v) \sum_{x=0}^{N-1} \sum_{y=0}^{N-1} f(x, y) \cos\left[\frac{\pi(2x+1)u}{2N}\right] \cos\left[\frac{\pi(2y+1)v}{2N}\right]

其中 u,v=0,1,...,N1u, v = 0, 1, ..., N-1,且

α(k)={1/Nif k=02/Nif k0\alpha(k) = \begin{cases} \sqrt{1/N} & \text{if } k=0 \\ \sqrt{2/N} & \text{if } k \neq 0 \end{cases}
  • 直观解释
    • 能量集中:DCT 将图像块的能量主要集中在左上角的低频系数上。G(0,0)G(0,0) 是直流 (DC) 系数,代表了该 8×88 \times 8 块的平均像素值。其他 63 个是交流 (AC) 系数。
    • 与 DFT 的关系:DCT 可以看作是对一个对称扩展的信号进行 DFT。这种隐式的对称性避免了 DFT 在处理有限长信号时产生的边界不连续性问题,从而获得了更好的能量集中效果。
  • 复杂度:与 FFT 类似,存在快速 DCT 算法,对 N×NN \times N 块的计算复杂度约为 O(N2logN)O(N^2 \log N)

3. 小波变换 (Wavelet Transform)

小波变换通过一个母小波函数 ψ(t)\psi(t) 和一个尺度函数 ϕ(t)\phi(t) 的伸缩和平移来表示信号。在二维图像中,通常使用一个二维尺度函数和三个方向(水平、垂直、对角)的二维小波函数。

一次二维离散小波变换 (2D-DWT) 将图像分解为四个子带:

  • LL (Approximation):由尺度函数在两个方向上滤波得到,是原始图像的低分辨率近似。
  • LH (Horizontal):水平方向高频,垂直方向低频,捕捉垂直边缘。
  • HL (Vertical):水平方向低频,垂直方向高频,捕捉水平边缘。
  • HH (Diagonal):两个方向都是高频,捕捉对角线边缘。

这个过程可以递归地在 LL 子带上进行,实现多级分解,提供多分辨率分析。

  • 直观解释:想象用不同大小的“放大镜”看图像。大放大镜(低频小波)看整体轮廓,小放大镜(高频小波)看局部细节。与 DCT 不同,小波系数同时保留了频率和空间位置信息。
  • 复杂度:DWT 的计算非常高效,对于 N×NN \times N 的图像,复杂度为 O(N2)O(N^2)

4. JPEG 压缩流程

JPEG 压缩是一个精巧的工程流程,旨在利用人眼视觉系统的特性。

  1. 色彩空间转换:将图像从 RGB 转换为 YCbCr。Y 是亮度 (Luminance) 分量,Cb 和 Cr 是色度 (Chrominance) 分量。人眼对亮度的敏感度远高于色度。
  2. 色度二次采样 (Chroma Subsampling):由于人眼对色度不敏感,可以降低 Cb 和 Cr 分量的分辨率。常见格式如 4:2:2 或 4:2:0,后者将色度信息减少到原来的 1/4,而对视觉质量影响很小。
  3. 分块 (Blocking):将每个通道(Y, Cb, Cr)的图像分割成 8×88 \times 8 的像素块。这是因为 DCT 在小块上计算效率高,且图像的统计特性在局部区域内较为稳定。
  4. DCT 变换:对每个 8×88 \times 8 块独立应用 2D-DCT,将其从空间域转换到频率域。
  5. 量化 (Quantization)这是 JPEG 中唯一的有损步骤。将 DCT 系数矩阵中的每个元素除以一个对应的量化表 (Quantization Table) 中的值,然后取整。量化表对高频系数使用较大的量化步长,对低频系数使用较小的步长。这导致许多高频系数变为 0,实现了信息的大量剔除。 FQ(u,v)=round(G(u,v)Q(u,v))F_Q(u, v) = \text{round}\left(\frac{G(u, v)}{Q(u, v)}\right)
  6. Zig-Zag 扫描:将量化后的 8×88 \times 8 矩阵按照 Zig-Zag 路径重新排列成一个 1D 序列。这样做的目的是将大量的 0(主要来自高频区域)聚集在一起,便于下一步的熵编码。
  7. 熵编码 (Entropy Coding):对 1D 序列进行无损压缩。
    • 对 DC 系数:进行差分编码 (DPCM),即只编码当前块与前一个块 DC 系数的差值。
    • 对 AC 系数:使用行程长度编码 (Run-Length Encoding, RLE) 压缩连续的 0,然后对非零值和 0 的长度进行 Huffman 编码或算术编码。

解压缩是上述过程的逆过程:熵解码 -> Zig-Zag 反扫描 -> 反量化 -> IDCT -> (如果需要)色度上采样 -> YCbCr 转 RGB。

代码实现

python
1import numpy as np
2import cv2
3import matplotlib.pyplot as plt
4
5# --- 准备图像 ---
6# 为了方便演示,我们创建一个包含不同频率成分的合成图像
7# 如果你有skimage,可以用 astronaut = skimage.data.astronaut(); gray_img = skimage.color.rgb2gray(astronaut)
8try:
9 from skimage.data import camera
10 gray_img = camera().astype(np.float32)
11except ImportError:
12 print("scikit-image 未安装,将使用合成图像")
13 gray_img = np.zeros((256, 256), dtype=np.float32)
14 gray_img[64:192, 64:192] = 255 # 一个方块(包含高频边缘)
15 cx, cy, r = 128, 128, 30
16 y, x = np.ogrid[-cy:256-cy, -cx:256-cx]
17 mask = x*x + y*y <= r*r
18 gray_img[mask] = 128 # 一个圆形(包含平滑区域和曲线)
19
20# 归一化到 0-255
21if gray_img.max() > 255:
22 gray_img = (gray_img / gray_img.max()) * 255
23gray_img = gray_img.astype(np.uint8)
24
25
26# --- 1. 傅里叶变换演示 ---
27def demonstrate_fft(image):
28 # 为什么要做fft2:计算图像的二维离散傅里叶变换
29 f_transform = np.fft.fft2(image)
30
31 # 为什么要做fftshift:将零频分量(DC分量)移动到频谱的中心,便于观察
32 f_transform_shifted = np.fft.fftshift(f_transform)
33
34 # 为什么要做对数变换:频谱的幅度变化范围很大,DC分量尤其大,
35 # 对数变换可以压缩动态范围,使得低幅度的频率成分也能被看见
36 magnitude_spectrum = 20 * np.log(np.abs(f_transform_shifted) + 1)
37
38 plt.figure(figsize=(10, 5))
39 plt.subplot(1, 2, 1), plt.imshow(image, cmap='gray'), plt.title('原始图像')
40 plt.xticks([]), plt.yticks([])
41 plt.subplot(1, 2, 2), plt.imshow(magnitude_spectrum, cmap='gray'), plt.title('傅里叶幅度谱')
42 plt.xticks([]), plt.yticks([])
43 plt.suptitle("傅里叶变换 (FFT) 演示")
44 plt.show()
45
46print("--- 傅里叶变换演示 ---")
47demonstrate_fft(gray_img)
48
49
50# --- 2. DCT 和 JPEG 压缩流程模拟 ---
51def demonstrate_jpeg_pipeline(image):
52 # 截取一个 8x8 的块用于演示
53 block = image[128:136, 128:136].astype(np.float32)
54
55 # 步骤 1: DCT变换
56 # 为什么用cv2.dct:这是OpenCV提供的标准DCT实现
57 dct_block = cv2.dct(block)
58
59 # 步骤 2: 量化
60 # 这是一个标准的JPEG亮度量化表,数值越大,压缩率越高,质量损失越大
61 quantization_table = np.array([
62 [16, 11, 10, 16, 24, 40, 51, 61],
63 [12, 12, 14, 19, 26, 58, 60, 55],
64 [14, 13, 16, 24, 40, 57, 69, 56],
65 [14, 17, 22, 29, 51, 87, 80, 62],
66 [18, 22, 37, 56, 68, 109, 103, 77],
67 [24, 35, 55, 64, 81, 104, 113, 92],
68 [49, 64, 78, 87, 103, 121, 120, 101],
69 [72, 92, 95, 98, 112, 100, 103, 99]
70 ])
71
72 # 为什么要做除法和四舍五入:这是量化的核心,通过除以量化步长并取整,
73 # 丢弃了高频部分的精度,这是有损压缩的关键
74 quantized_dct = np.round(dct_block / quantization_table)
75
76 # --- 解压缩过程 ---
77
78 # 步骤 3: 反量化
79 # 为什么要做乘法:恢复DCT系数的近似值,但由于之前的取整,信息已经丢失
80 dequantized_dct = quantized_dct * quantization_table
81
82 # 步骤 4: IDCT (逆DCT)
83 # 为什么用cv2.idct:将频率域的系数转换回空间域的像素块
84 reconstructed_block = cv2.idct(dequantized_dct)
85
86 # 可视化对比
87 plt.figure(figsize=(15, 10))
88 plt.subplot(2, 3, 1), plt.imshow(block, cmap='gray', vmin=0, vmax=255), plt.title('原始 8x8 块')
89 plt.subplot(2, 3, 2), plt.imshow(dct_block, cmap='gray'), plt.title('DCT 系数')
90 plt.subplot(2, 3, 3), plt.imshow(quantized_dct, cmap='gray'), plt.title('量化后 DCT 系数')
91 plt.subplot(2, 3, 4), plt.imshow(dequantized_dct, cmap='gray'), plt.title('反量化 DCT 系数')
92 plt.subplot(2, 3, 5), plt.imshow(reconstructed_block, cmap='gray', vmin=0, vmax=255), plt.title('重建后 8x8 块')
93
94 # 计算误差
95 error = np.abs(block - reconstructed_block)
96 mse = np.mean(error**2)
97 plt.subplot(2, 3, 6), plt.imshow(error, cmap='hot'), plt.title(f'误差图 (MSE: {mse:.2f})')
98
99 plt.suptitle("JPEG 核心流程 (DCT -> 量化 -> 反量化 -> IDCT) 演示")
100 plt.tight_layout(rect=[0, 0.03, 1, 0.95])
101 plt.show()
102
103 print("\n原始 8x8 块:\n", block.astype(int))
104 print("\n量化后的 DCT 系数 (注意右下角出现大量0):\n", quantized_dct.astype(int))
105 print("\n重建后的 8x8 块 (与原始块有差异):\n", reconstructed_block.astype(int))
106
107
108print("\n--- JPEG 核心流程演示 ---")
109demonstrate_jpeg_pipeline(gray_img)

工程实践

  • 变换的选择

    • 傅里叶变换:在工程上很少直接用于压缩,因为它缺乏空间局部性,且会产生复数系数。主要用于频域分析和滤波,例如,识别并去除图像中的周期性噪声(如电网干扰形成的条纹)。在深度学习中,傅里叶变换的思想被用于设计卷积层(如 F-Conv)或理解卷积的性质。
    • DCT:是 JPEG、MPEG、H.26x 系列视频编码标准的核心。8×88 \times 8 的块大小是一个经典的权衡:足够小以适应局部图像统计特性并降低计算复杂度,也足够大以获得良好的能量集中效果。
    • 小波变换:是 JPEG 2000 标准的核心。相比 DCT,它具有更好的多分辨率特性和空间局部性,能有效避免 JPEG 的块效应 (blocking artifacts),并在低比特率下提供更高的压缩质量。但由于其计算复杂度更高和早期的专利问题,JPEG 2000 未能完全取代 JPEG。小波变换在医学图像、指纹识别和图像去噪领域应用广泛。
  • JPEG 调参经验

    • 几乎所有 JPEG 库都提供一个 "quality" 参数(通常 1-100)。这个参数内部会缩放标准的量化表。quality=95 是高质量 Web 图像的常用选择。quality=75 是一个在质量和文件大小之间不错的平衡点。低于 50 时,图像质量会明显下降,块效应显著。
    • 针对不同通道使用不同的量化表。通常,色度通道 (Cb, Cr) 会使用比亮度通道 (Y) 更激进的量化表(即更大的量化步长),因为人眼对色度变化不敏感。
  • 部署与性能

    • JPEG 编解码在现代 CPU 和 GPU 上都有高度优化的硬件指令集 (SIMD) 或专用硬件单元,速度极快。
    • 在移动端或嵌入式设备上,硬件 JPEG 编解码器是标配,直接调用硬件可以极大降低 CPU 负载和功耗。
    • 对于服务器端的大规模图像处理,libjpeg-turbo 是一个比标准 libjpeg 快得多的库,是许多 Python 图像库(如 Pillow)的后端。

常见误区与边界情况

  • 误区1:JPEG 压缩就是 DCT

    • 这是最常见的简化误区。DCT 只是 JPEG 流程中的一步。量化才是导致信息损失和实现高压缩率的关键步骤。没有量化,DCT 只是一个可逆的数学变换。同样,色彩空间转换、二次采样和熵编码也对最终的压缩率和质量有重要贡献。
  • 误区2:傅里叶变换和 DCT 效果差不多

    • 对于自然图像,DCT 的能量集中效果远优于 DFT。DFT 隐含的周期性假设会在图像边界产生不连续,导致能量泄露到高频部分。DCT 的隐式对称延拓则能很好地处理边界,使能量更集中于低频。
  • 误区3:JPEG quality=100 是无损的

    • 不是。即使 quality=100(量化表所有元素为1),压缩过程仍然可能是有损的。原因有二:
      1. 色度二次采样:如果执行了 4:2:0 或 4:2:2 采样,色度信息就已经丢失了。
      2. 浮点数精度:DCT 和 IDCT 的计算涉及浮点数,从 int -> float -> int 的转换会引入微小的舍入误差。
    • 真正的无损压缩需要使用无损 JPEG 标准(很少用)或 PNG、WebP-lossless 等格式。
  • 边界情况:块效应 (Blocking Artifacts)

    • 当 JPEG 压缩率非常高(quality 值很低)时,每个 8×88 \times 8 块的边界会变得清晰可见。这是因为每个块被独立处理,高频系数(决定了块间边缘的平滑过渡)被严重量化,导致块与块之间的不连续性凸显出来。小波变换(JPEG 2000)在全图上进行变换,自然地避免了这个问题。
  • 面试追问

    • :“如何在频域中给图像去噪?”
      • :对图像做傅里叶变换,得到频谱图。噪声通常表现为高频信号(随机噪声)或特定的亮点(周期性噪声)。可以通过一个低通滤波器(如高斯滤波器)乘以频谱图来抑制高频,或者手动/自动检测并抹除周期性噪声的亮点。然后,对修改后的频谱进行逆傅里叶变换,得到去噪后的图像。
    • :“为什么 JPEG 选择 8×88 \times 8 而不是 4×44 \times 416×1616 \times 16 的块?”
      • :这是一个工程上的权衡。4×44 \times 4 块太小,无法有效利用更大范围的像素相关性,能量集中效果不佳。16×1616 \times 16 块计算复杂度更高(复杂度与 N2logNN^2 \log N 相关),且难以适应图像局部统计特性的快速变化。8×88 \times 8 在当时的计算能力和图像统计特性之间取得了很好的平衡。现代视频编码标准(如 H.265/HEVC)已经支持可变大小的变换块(从 4×44 \times 432×3232 \times 32),以更好地适应不同内容的复杂度。
相关题目