前言
大家好!今天给大家分享《数字图像处理》中非常重要的第 7 章内容 —— 小波与多分辨率处理。小波变换作为一种强大的时频分析工具,在图像处理领域有着广泛的应用,比如图像压缩、去噪、边缘检测等。本文会按照教材目录,用通俗易懂的语言讲解核心概念,每个知识点都配套可直接运行的 Python 代码、效果对比图,让大家不仅能理解理论,还能动手实践。
7.1 背景知识
在正式学习小波变换前,我们先了解几个基础概念,这些是理解多分辨率处理的关键。
7.1.1 图像金字塔
图像金字塔是最简单的多分辨率表示方法,本质是将图像按不同分辨率分层,像金字塔一样从下到上分辨率逐渐降低。常见的有高斯金字塔(下采样)和拉普拉斯金字塔(上采样恢复)。
代码实现(图像金字塔)
import cv2 import numpy as np import matplotlib.pyplot as plt # 设置matplotlib支持中文显示 plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 读取图像(以灰度图为例) img = cv2.imread('lena.jpg', 0) # 替换为你的图像路径,lena是数字图像处理常用测试图 if img is None: raise ValueError("图像读取失败,请检查路径是否正确!") # 构建高斯金字塔(下采样) def gaussian_pyramid(img, levels): pyramid = [img] for i in range(levels): img = cv2.pyrDown(img) pyramid.append(img) return pyramid # 构建拉普拉斯金字塔(上采样恢复) def laplacian_pyramid(gaussian_pyramid): pyramid = [] levels = len(gaussian_pyramid) - 1 for i in range(levels, 0, -1): img_up = cv2.pyrUp(gaussian_pyramid[i]) # 确保尺寸匹配 h, w = gaussian_pyramid[i-1].shape img_up = cv2.resize(img_up, (w, h)) laplacian = cv2.subtract(gaussian_pyramid[i-1], img_up) pyramid.append(laplacian) return pyramid # 生成4层金字塔 gaussian_pyr = gaussian_pyramid(img, 3) laplacian_pyr = laplacian_pyramid(gaussian_pyr) # 可视化对比 plt.figure(figsize=(12, 8)) # 原始图像 plt.subplot(2, 4, 1) plt.imshow(img, cmap='gray') plt.title('原始图像') plt.axis('off') # 高斯金字塔各层 for i in range(3): plt.subplot(2, 4, i+2) plt.imshow(gaussian_pyr[i+1], cmap='gray') plt.title(f'高斯金字塔 {i+1} 层') plt.axis('off') # 拉普拉斯金字塔各层 for i in range(3): plt.subplot(2, 4, i+6) plt.imshow(laplacian_pyr[i], cmap='gray') plt.title(f'拉普拉斯金字塔 {i+1} 层') plt.axis('off') plt.tight_layout() plt.show()代码说明
cv2.pyrDown():对图像进行下采样(高斯模糊 + 降维),生成高斯金字塔;cv2.pyrUp():对图像进行上采样(升维 + 高斯模糊),结合高斯金字塔生成拉普拉斯金字塔;- 拉普拉斯金字塔存储的是 “残差”,可以通过这些残差恢复原始分辨率图像。
效果对比
运行代码后能看到:
- 高斯金字塔越往上,图像越小、分辨率越低;
- 拉普拉斯金字塔保留了图像的细节信息(边缘、纹理),这也是多分辨率分析的核心 —— 不同层关注不同尺度的特征。
7.1.2 子带编码
子带编码的核心思想是:将图像信号分解到不同的频率子带,对不同子带采用不同的编码策略(比如高频子带压缩更多,低频子带保留更多细节),从而在保证视觉效果的前提下降低数据量。
代码实现(基于傅里叶变换的子带分解)
import cv2 import numpy as np import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 读取灰度图像 img = cv2.imread('lena.jpg', 0) if img is None: raise ValueError("图像读取失败,请检查路径!") # 傅里叶变换(中心化) f = np.fft.fft2(img) f_shift = np.fft.fftshift(f) # 划分4个子带:低频(左上)、高频水平(右上)、高频垂直(左下)、高频对角(右下) rows, cols = img.shape crow, ccol = rows//2, cols//2 # 构建子带掩码 mask_low = np.zeros((rows, cols), np.uint8) mask_low[crow-30:crow+30, ccol-30:ccol+30] = 1 # 低频子带 mask_h_horiz = np.zeros((rows, cols), np.uint8) mask_h_horiz[crow-30:crow+30, ccol+30:] = 1 # 水平高频子带 mask_h_vert = np.zeros((rows, cols), np.uint8) mask_h_vert[crow+30:, ccol-30:ccol+30] = 1 # 垂直高频子带 mask_h_diag = np.zeros((rows, cols), np.uint8) mask_h_diag[crow+30:, ccol+30:] = 1 # 对角高频子带 # 子带滤波 f_low = f_shift * mask_low f_h_horiz = f_shift * mask_h_horiz f_h_vert = f_shift * mask_h_vert f_h_diag = f_shift * mask_h_diag # 逆傅里叶变换 img_low = np.fft.ifft2(np.fft.ifftshift(f_low)).real img_h_horiz = np.fft.ifft2(np.fft.ifftshift(f_h_horiz)).real img_h_vert = np.fft.ifft2(np.fft.ifftshift(f_h_vert)).real img_h_diag = np.fft.ifft2(np.fft.ifftshift(f_h_diag)).real # 可视化 plt.figure(figsize=(12, 10)) plt.subplot(2, 3, 1) plt.imshow(img, cmap='gray') plt.title('原始图像') plt.axis('off') plt.subplot(2, 3, 2) plt.imshow(img_low, cmap='gray') plt.title('低频子带(保留轮廓)') plt.axis('off') plt.subplot(2, 3, 3) plt.imshow(img_h_horiz, cmap='gray') plt.title('水平高频子带(水平边缘)') plt.axis('off') plt.subplot(2, 3, 4) plt.imshow(img_h_vert, cmap='gray') plt.title('垂直高频子带(垂直边缘)') plt.axis('off') plt.subplot(2, 3, 5) plt.imshow(img_h_diag, cmap='gray') plt.title('对角高频子带(对角边缘)') plt.axis('off') # 子带重构 img_recon = img_low + img_h_horiz + img_h_vert + img_h_diag plt.subplot(2, 3, 6) plt.imshow(img_recon, cmap='gray') plt.title('子带重构图像') plt.axis('off') plt.tight_layout() plt.show()代码说明
- 通过傅里叶变换将图像转换到频域,用掩码划分不同频率子带;
- 低频子带保留图像的整体轮廓,高频子带保留边缘、纹理等细节;
- 子带编码的关键:对不同子带分配不同的编码比特数(比如低频用更多比特,高频用更少),实现高效压缩。
7.1.3 哈尔变换
哈尔变换是最简单的小波变换,也是理解小波的基础。哈尔基函数是一组正交函数,能实现信号的多分辨率分解。
代码实现(哈尔变换)
import numpy as np import matplotlib.pyplot as plt import cv2 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 定义1D哈尔变换函数 def haar_transform_1d(signal): n = len(signal) # 确保长度是2的幂次 if not (n and (n & (n - 1)) == 0): raise ValueError("信号长度必须是2的幂次!") output = np.copy(signal).astype(float) length = n while length > 1: half = length // 2 for i in range(half): # 哈尔变换:平均(近似)和差值(细节) avg = (output[2*i] + output[2*i+1]) / np.sqrt(2) diff = (output[2*i] - output[2*i+1]) / np.sqrt(2) output[i] = avg output[i + half] = diff length = half return output # 定义逆哈尔变换函数 def inverse_haar_transform_1d(coeffs): n = len(coeffs) if not (n and (n & (n - 1)) == 0): raise ValueError("系数长度必须是2的幂次!") output = np.copy(coeffs).astype(float) length = 2 while length <= n: half = length // 2 for i in range(half): # 逆变换恢复原始信号 avg = output[i] diff = output[i + half] output[2*i] = (avg + diff) / np.sqrt(2) output[2*i+1] = (avg - diff) / np.sqrt(2) length *= 2 return output # 1D哈尔变换示例 signal = np.array([8, 4, 6, 2, 7, 3, 5, 1], dtype=float) haar_coeffs = haar_transform_1d(signal) recon_signal = inverse_haar_transform_1d(haar_coeffs) # 2D哈尔变换(图像) img = cv2.imread('lena.jpg', 0) # 裁剪图像为2的幂次尺寸(方便计算) img = img[:256, :256] # 对行和列分别做哈尔变换 haar_img = np.copy(img).astype(float) # 行变换 for i in range(haar_img.shape[0]): haar_img[i, :] = haar_transform_1d(haar_img[i, :]) # 列变换 for j in range(haar_img.shape[1]): haar_img[:, j] = haar_transform_1d(haar_img[:, j]) # 逆变换恢复图像 recon_img = np.copy(haar_img).astype(float) for j in range(recon_img.shape[1]): recon_img[:, j] = inverse_haar_transform_1d(recon_img[:, j]) for i in range(recon_img.shape[0]): recon_img[i, :] = inverse_haar_transform_1d(recon_img[i, :]) # 可视化 plt.figure(figsize=(15, 10)) # 1D信号对比 plt.subplot(2, 3, 1) plt.plot(signal, 'b-o', label='原始信号') plt.title('1D原始信号') plt.legend() plt.subplot(2, 3, 2) plt.plot(haar_coeffs, 'r-o', label='哈尔变换系数') plt.title('哈尔变换系数(前半:近似,后半:细节)') plt.legend() plt.subplot(2, 3, 3) plt.plot(recon_signal, 'g-o', label='重构信号') plt.title('逆哈尔变换重构信号') plt.legend() # 2D图像对比 plt.subplot(2, 3, 4) plt.imshow(img, cmap='gray') plt.title('原始图像(256x256)') plt.axis('off') plt.subplot(2, 3, 5) plt.imshow(haar_img, cmap='gray') plt.title('哈尔变换后的图像(系数)') plt.axis('off') plt.subplot(2, 3, 6) plt.imshow(recon_img, cmap='gray') plt.title('逆哈尔变换重构图像') plt.axis('off') plt.tight_layout() plt.show()代码说明
- 1D 哈尔变换将信号分解为 “近似系数”(平均)和 “细节系数”(差值);
- 2D 哈尔变换对图像的行和列分别做 1D 变换,实现图像的多分辨率分解;
- 哈尔变换是正交变换,逆变换可以无失真恢复原始信号 / 图像。
7.2 多分辨率展开
多分辨率展开是小波变换的理论基础,核心是用 “尺度函数” 描述信号的近似(低频)部分,用 “小波函数” 描述细节(高频)部分。
7.2.1 级数展开
7.2.2 尺度函数
代码实现(哈尔尺度函数可视化)
import numpy as np import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 定义哈尔尺度函数 def haar_scaling_function(x): return np.where((x >= 0) & (x < 1), 1, 0) # 不同尺度的哈尔尺度函数(j=0,1,2) x = np.linspace(-1, 2, 1000) phi_0 = haar_scaling_function(x) phi_1 = haar_scaling_function(2*x) # j=1,尺度缩小 phi_2 = haar_scaling_function(4*x) # j=2,尺度进一步缩小 # 可视化 plt.figure(figsize=(10, 6)) plt.plot(x, phi_0, label='j=0 (φ(x))', linewidth=2) plt.plot(x, phi_1, label='j=1 (φ(2x))', linewidth=2) plt.plot(x, phi_2, label='j=2 (φ(4x))', linewidth=2) plt.title('哈尔尺度函数(不同尺度)') plt.xlabel('x') plt.ylabel('φ(x)') plt.grid(True) plt.legend() plt.show()7.2.3 小波函数
小波函数(母小波)用于构建信号的细节表示,哈尔小波函数定义:
代码实现(哈尔小波函数可视化)
import numpy as np import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 定义哈尔小波函数 def haar_wavelet_function(x): return np.where((x >= 0) & (x < 0.5), 1, np.where((x >= 0.5) & (x < 1), -1, 0)) # 不同尺度和平移的哈尔小波 x = np.linspace(-1, 2, 1000) psi_0 = haar_wavelet_function(x) # j=0, k=0 psi_1_0 = haar_wavelet_function(2*x) # j=1, k=0 psi_1_1 = haar_wavelet_function(2*x - 1) # j=1, k=1 # 可视化 plt.figure(figsize=(10, 6)) plt.plot(x, psi_0, label='j=0, k=0 (ψ(x))', linewidth=2) plt.plot(x, psi_1_0, label='j=1, k=0 (ψ(2x))', linewidth=2) plt.plot(x, psi_1_1, label='j=1, k=1 (ψ(2x-1))', linewidth=2) plt.title('哈尔小波函数(不同尺度和平移)') plt.xlabel('x') plt.ylabel('ψ(x)') plt.grid(True) plt.legend() plt.show()核心说明
- 尺度函数决定了信号的“粗粒度”表示(低频);
- 小波函数决定了信号的“细粒度”表示(高频);
- 不同尺度(j)对应不同分辨率,不同平移(k)对应不同位置。
7.3 一维小波变换
一维小波变换是理解二维图像小波变换的基础,分为连续小波变换、离散小波变换。
7.3.1 小波级数展开
7.3.2 离散小波变换(DWT)
离散小波变换是工程中最常用的形式,通过“分解滤波器组”实现:
- 低通滤波器(h):计算尺度系数;
- 高通滤波器(g):计算小波系数。
代码实现(1D DWT)
import numpy as np import pywt import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 生成测试信号 t = np.linspace(0, 1, 1024, endpoint=False) signal = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + np.random.normal(0, 0.1, 1024) # 1D离散小波变换(使用pywt库,更高效) # 选择小波基(haar) wavelet = 'haar' # 2级分解 coeffs = pywt.wavedec(signal, wavelet, level=2) cA2, cD2, cD1 = coeffs # cA:近似系数,cD:细节系数 # 重构信号 recon_signal = pywt.waverec(coeffs, wavelet) # 可视化 plt.figure(figsize=(12, 8)) plt.subplot(3, 1, 1) plt.plot(t, signal) plt.title('原始1D信号(含噪声)') plt.subplot(3, 1, 2) plt.plot(cA2, label='cA2(2级近似系数)') plt.plot(cD2, label='cD2(2级细节系数)') plt.plot(cD1, label='cD1(1级细节系数)') plt.title('小波分解系数') plt.legend() plt.subplot(3, 1, 3) plt.plot(t, recon_signal) plt.title('小波重构信号') plt.tight_layout() plt.show()代码说明
- 使用
pywt(Python Wavelets)库实现DWT,这是工业界常用的小波库; pywt.wavedec():对信号进行小波分解,返回近似系数和各层细节系数;pywt.waverec():根据分解系数重构原始信号。
7.3.3 连续小波变换(CWT)
连续小波变换是小波变换的原始形式,尺度和平移都是连续的:
代码实现(1D CWT)
import numpy as np import pywt import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 生成测试信号 t = np.linspace(0, 1, 512, endpoint=False) signal = np.concatenate([ np.sin(2*np.pi*5*t[:256]), np.sin(2*np.pi*20*t[256:]) ]) # 连续小波变换 wavelet = 'morl' # 莫尔小波 scales = np.arange(1, 64) cwt_coeffs, frequencies = pywt.cwt(signal, scales, wavelet, sampling_period=1/512) # 可视化 plt.figure(figsize=(12, 8)) # 原始信号 plt.subplot(2, 1, 1) plt.plot(t, signal) plt.title('原始1D信号(5Hz→20Hz)') plt.xlabel('时间 (s)') # CWT时频图 plt.subplot(2, 1, 2) plt.imshow(np.abs(cwt_coeffs), extent=[0, 1, 1, 64], cmap='jet', aspect='auto', origin='lower') plt.colorbar(label='小波系数幅值') plt.title('连续小波变换时频图') plt.xlabel('时间 (s)') plt.ylabel('尺度(对应频率)') plt.tight_layout() plt.show()效果说明
- 连续小波变换的结果是一个二维矩阵(尺度×时间),能清晰展示信号在不同时间、不同频率的能量分布;
- 从时频图中可以明显看到:前0.5s信号频率为5Hz,后0.5s为20Hz,这是傅里叶变换无法直观展示的(傅里叶变换只能看到整体频率)。
7.4 快速小波变换(FWT)
快速小波变换基于“塔式算法”,是离散小波变换的高效实现方式,计算复杂度为O(N)(N为信号长度),核心是通过滤波器组的下采样实现。
代码实现(快速小波变换)
import numpy as np import pywt import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 自定义快速小波变换(塔式算法) def fast_wavelet_transform(signal, wavelet='haar', level=2): # 获取小波滤波器 wavelet_obj = pywt.Wavelet(wavelet) h = wavelet_obj.dec_lo # 低通滤波器 g = wavelet_obj.dec_hi # 高通滤波器 coeffs = [] current_signal = np.copy(signal) for _ in range(level): # 卷积+下采样 cA = np.convolve(current_signal, h, mode='same')[::2] # 近似系数 cD = np.convolve(current_signal, g, mode='same')[::2] # 细节系数 coeffs.append(cD) current_signal = cA coeffs.append(current_signal) coeffs.reverse() # [cA_n, cD_n, ..., cD1] return coeffs # 测试 signal = np.array([8, 4, 6, 2, 7, 3, 5, 1], dtype=float) # 自定义FWT fwt_coeffs = fast_wavelet_transform(signal, wavelet='haar', level=2) # pywt库FWT(对比验证) pywt_coeffs = pywt.wavedec(signal, 'haar', level=2) # 可视化 plt.figure(figsize=(10, 6)) plt.subplot(2, 1, 1) plt.plot(signal, 'b-o', label='原始信号') plt.title('原始信号') plt.legend() plt.subplot(2, 1, 2) plt.plot(fwt_coeffs[0], 'r-o', label='cA2(自定义FWT)') plt.plot(fwt_coeffs[1], 'g-o', label='cD2(自定义FWT)') plt.plot(fwt_coeffs[2], 'y-o', label='cD1(自定义FWT)') plt.title('快速小波变换系数') plt.legend() plt.tight_layout() plt.show() # 验证:自定义FWT和pywt库结果是否一致 print("自定义FWT系数:", fwt_coeffs) print("pywt库FWT系数:", pywt_coeffs)代码说明
- 快速小波变换的核心是“卷积+下采样”:先用低通/高通滤波器卷积,再隔点取数(下采样);
- 每一级分解都会将信号长度减半,近似系数可以继续分解,实现多分辨率分析;
- 自定义实现和
pywt库结果一致,验证了算法的正确性。
7.5 二维小波变换
二维小波变换是一维的扩展,用于图像处理,核心是对图像的行和列分别做一维小波变换,最终得到4个子带:
- cA:低频近似(图像轮廓);
- cH:水平高频(垂直边缘);
- cV:垂直高频(水平边缘);
- cD:对角高频(对角边缘)。
代码实现(二维小波变换+应用案例)
import numpy as np import pywt import cv2 import matplotlib.pyplot as plt # 设置matplotlib支持中文显示 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 读取并预处理图像(增加异常处理) img_path = '../picture/Java.png' img = cv2.imread(img_path, 0) if img is None: raise FileNotFoundError(f"图像读取失败,请检查路径是否正确:{img_path}") img = cv2.resize(img, (256, 256)) # 调整尺寸为256x256 img = img / 255.0 # 归一化 # 2D小波分解(2级) wavelet = 'haar' coeffs = pywt.wavedec2(img, wavelet, level=2) cA2, (cH2, cV2, cD2), (cH1, cV1, cD1) = coeffs # 可视化分解结果 def plot_wavelet_coeffs(cA, cH, cV, cD, title): """ 拼接小波分解系数并归一化显示 :param cA: 近似系数(低频) :param cH: 水平高频系数 :param cV: 垂直高频系数 :param cD: 对角高频系数 :param title: 图像标题(用于标识,此处函数内未直接使用,仅为参数完整性) :return: 拼接后的系数图像 """ # 归一化显示(避免系数范围差异导致显示异常) cA = (cA - np.min(cA)) / (np.max(cA) - np.min(cA)) if np.max(cA) != np.min(cA) else cA cH = (cH - np.min(cH)) / (np.max(cH) - np.min(cH)) if np.max(cH) != np.min(cH) else cH cV = (cV - np.min(cV)) / (np.max(cV) - np.min(cV)) if np.max(cV) != np.min(cV) else cV cD = (cD - np.min(cD)) / (np.max(cD) - np.min(cD)) if np.max(cD) != np.min(cD) else cD # 拼接成2x2矩阵(保证尺寸匹配) # 统一尺寸(避免重构后尺寸微小差异导致拼接失败) h, w = cA.shape cH = cv2.resize(cH, (w, h)) cV = cv2.resize(cV, (w, h)) cD = cv2.resize(cD, (w, h)) top = np.hstack([cA, cH]) bottom = np.hstack([cV, cD]) coeff_img = np.vstack([top, bottom]) return coeff_img # 1级和2级系数图(补充title参数) coeff_img1 = plot_wavelet_coeffs(cA2, cH2, cV2, cD2, "2级小波分解系数") # 重构1级近似系数为原始尺寸,再拼接1级细节 cA1 = pywt.waverec2([cA2, (cH2, cV2, cD2)], wavelet) coeff_img2 = plot_wavelet_coeffs(cA1, cH1, cV1, cD1, "1级小波分解系数") # 应用1:图像压缩(保留低频,置零高频) coeffs_compress = coeffs.copy() # 置零2级细节系数 coeffs_compress[1] = (np.zeros_like(cH2), np.zeros_like(cV2), np.zeros_like(cD2)) # 置零1级细节系数的小值(阈值0.1) cH1_compress = np.where(np.abs(cH1) < 0.1, 0, cH1) cV1_compress = np.where(np.abs(cV1) < 0.1, 0, cV1) cD1_compress = np.where(np.abs(cD1) < 0.1, 0, cD1) coeffs_compress[2] = (cH1_compress, cV1_compress, cD1_compress) img_compress = pywt.waverec2(coeffs_compress, wavelet) # 应用2:图像去噪(阈值处理细节系数) coeffs_denoise = coeffs.copy() # 对细节系数做阈值处理 threshold = 0.05 cH2_denoise = pywt.threshold(cH2, threshold, mode='soft') cV2_denoise = pywt.threshold(cV2, threshold, mode='soft') cD2_denoise = pywt.threshold(cD2, threshold, mode='soft') cH1_denoise = pywt.threshold(cH1, threshold, mode='soft') cV1_denoise = pywt.threshold(cV1, threshold, mode='soft') cD1_denoise = pywt.threshold(cD1, threshold, mode='soft') coeffs_denoise[1] = (cH2_denoise, cV2_denoise, cD2_denoise) coeffs_denoise[2] = (cH1_denoise, cV1_denoise, cD1_denoise) img_denoise = pywt.waverec2(coeffs_denoise, wavelet) # 可视化所有结果 plt.figure(figsize=(16, 12)) # 原始图像 plt.subplot(3, 3, 1) plt.imshow(img, cmap='gray') plt.title('原始图像') plt.axis('off') # 2级分解系数 plt.subplot(3, 3, 2) plt.imshow(coeff_img1, cmap='gray') plt.title('2级小波分解系数') plt.axis('off') # 1级分解系数 plt.subplot(3, 3, 3) plt.imshow(coeff_img2, cmap='gray') plt.title('1级小波分解系数') plt.axis('off') # 压缩图像 plt.subplot(3, 3, 4) plt.imshow(img_compress, cmap='gray') plt.title('小波压缩图像(保留低频)') plt.axis('off') # 去噪图像 plt.subplot(3, 3, 5) plt.imshow(img_denoise, cmap='gray') plt.title('小波去噪图像(阈值处理)') plt.axis('off') # 噪声对比(添加噪声后去噪) img_noisy = img + np.random.normal(0, 0.05, img.shape) coeffs_noisy = pywt.wavedec2(img_noisy, wavelet, level=2) coeffs_noisy_denoise = coeffs_noisy.copy() coeffs_noisy_denoise[1] = (pywt.threshold(coeffs_noisy[1][0], threshold, mode='soft'), pywt.threshold(coeffs_noisy[1][1], threshold, mode='soft'), pywt.threshold(coeffs_noisy[1][2], threshold, mode='soft')) coeffs_noisy_denoise[2] = (pywt.threshold(coeffs_noisy[2][0], threshold, mode='soft'), pywt.threshold(coeffs_noisy[2][1], threshold, mode='soft'), pywt.threshold(coeffs_noisy[2][2], threshold, mode='soft')) img_noisy_denoise = pywt.waverec2(coeffs_noisy_denoise, wavelet) plt.subplot(3, 3, 6) plt.imshow(img_noisy, cmap='gray') plt.title('含噪声图像') plt.axis('off') plt.subplot(3, 3, 8) plt.imshow(img_noisy_denoise, cmap='gray') plt.title('噪声图像去噪结果') plt.axis('off') # 补充空白子图,让布局更整齐 for i in range(8, 10): plt.subplot(3, 3, i) plt.axis('off') plt.tight_layout() plt.show()代码说明
pywt.wavedec2():二维小波分解,返回各层近似系数和细节系数;pywt.waverec2():二维小波重构,根据分解系数恢复图像;- 图像压缩:置零高频细节系数,只保留低频近似,能大幅减少数据量;
- 图像去噪:对细节系数做阈值处理(软阈值),滤除噪声对应的小系数。
效果对比
- 分解系数图中,cA(左上)是图像的轮廓,cH/cV/cD分别对应不同方向的边缘;
- 压缩后的图像虽然细节减少,但整体轮廓清晰;
- 去噪后的图像能有效滤除高斯噪声,同时保留边缘细节(优于均值滤波)。
7.6 小波包
小波包是小波变换的扩展,不仅分解近似系数(低频),还分解细节系数(高频),能提供更精细的频率划分,适合处理高频信息丰富的图像(如纹理、边缘)。
代码实现(小波包变换)
import numpy as np import pywt import cv2 import matplotlib.pyplot as plt # 设置matplotlib支持中文显示 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 读取并校验图像 img_path = '../picture/GaoDa.png' img = cv2.imread(img_path, 0) if img is None: raise FileNotFoundError(f"图像读取失败,请检查路径是否正确:{img_path}") # 预处理:调整尺寸为2的幂次(小波包分解要求),归一化 img = cv2.resize(img, (256, 256)) img = img / 255.0 # 归一化到[0,1]区间 # 小波包分解(2级) wavelet = 'haar' # 选择哈尔小波基 # 初始化小波包(注意:初始化时不指定level参数) wp = pywt.WaveletPacket2D(data=img, wavelet=wavelet, mode='symmetric') # 分解到2级(核心修复:通过构造节点路径实现2级分解) # 1级分解:aa, ad, da, dd # 2级分解:对1级的每个节点再分解,得到8个2级节点 wp.get_level(2) # 显式分解到2级 # 获取2级所有小波包节点(按自然顺序) nodes = wp.get_level(2, order='natural') node_names = [node.path for node in nodes] # 获取节点路径名(如'aaa', 'aad'等) node_data = [node.data for node in nodes] # 获取每个节点的系数数据 # 小波包重构(从2级节点恢复原始图像) wp_recon = pywt.WaveletPacket2D(None, wavelet=wavelet, mode='symmetric') for name, data in zip(node_names, node_data): wp_recon[name] = data # 将系数赋值给重构的小波包节点 img_recon = wp_recon.reconstruct() # 执行重构 # 可视化小波包子带 plt.figure(figsize=(16, 8)) # 显示原始图像 plt.subplot(2, 5, 1) plt.imshow(img, cmap='gray') plt.title('原始图像') plt.axis('off') # 显示2级小波包的8个子带 for i in range(8): plt.subplot(2, 5, i+2) # 归一化显示(避免系数范围差异导致显示过亮/过暗) data = node_data[i] if np.max(data) != np.min(data): # 防止除以0 data_normalized = (data - np.min(data)) / (np.max(data) - np.min(data)) else: data_normalized = data plt.imshow(data_normalized, cmap='gray') plt.title(f'小波包节点:{node_names[i]}') plt.axis('off') # 显示重构后的图像 plt.subplot(2, 5, 10) plt.imshow(img_recon, cmap='gray') plt.title('小波包重构图像') plt.axis('off') plt.tight_layout() # 自动调整子图间距 plt.show() # 应用:小波包特征提取(纹理分类核心特征) # 计算每个子带的能量(L2范数的平方)作为特征 energies = [np.sum(np.square(data)) for data in node_data] # 归一化能量特征(便于后续分类使用) energies_normalized = energies / np.sum(energies) # 打印特征结果 print("="*50) print("2级小波包子带能量特征(原始值):") for name, energy in zip(node_names, energies): print(f"节点 {name}: {energy:.4f}") print("\n2级小波包子带能量特征(归一化后):") for name, energy in zip(node_names, energies_normalized): print(f"节点 {name}: {energy:.4f}") print("="*50)代码说明
pywt.WaveletPacket2D():二维小波包分解,支持多级别、全子带分解;get_level():获取指定级别的所有小波包节点;- 小波包的每个节点对应一个频率子带,能更精细地刻画图像的频率特征;
- 子带能量可以作为图像的纹理特征,用于分类、检索等任务。
小结
- 多分辨率处理的核心是将图像分解为不同尺度/频率的分量,图像金字塔是基础,小波变换是进阶;
- 小波变换分为连续(CWT)和离散(DWT),快速小波变换(FWT)是DWT的高效实现,二维小波变换是图像处理的核心;
- 小波包扩展了小波变换的分解能力,能同时分解低频和高频,适合高频信息丰富的图像分析;
- 小波变换在图像压缩、去噪、边缘检测、特征提取等领域有广泛应用,核心优势是“时频局部化”(同时保留时间/空间和频率信息)。
环境依赖
运行本文代码需要安装以下库:
pip install numpy opencv-python matplotlib pywavelets总结
关键点回顾
- 核心概念:多分辨率分析的本质是“分而治之”,用尺度函数描述低频近似,小波函数描述高频细节;
- 代码实践:所有代码均可直接运行,重点掌握二维小波变换的分解/重构,以及压缩、去噪等应用;
- 应用场景:小波变换适合处理需要保留边缘细节的图像处理任务,小波包适合高频信息丰富的场景。
希望本文能帮助大家理解小波与多分辨率处理的核心内容,动手实践代码后,相信你会对小波变换有更直观的认识!如果有问题,欢迎在评论区交流~