☰
压缩感知图像重构实战:MATLAB分块OMP算法与参数调优
2026/10/14 21:18:17 网站建设 项目流程

简介:压缩包提供二维图像压缩感知重构算法的完整MATLAB实现,面向信号处理、图像处理方向的研究者、学生及工程师。内容覆盖测量矩阵生成、图像采样、重构算法与性能评估,包含OMP、Wavelet_OMP、CS_OMP等典型程序,并配有fig可视化文件与示例图像,便于对照调试与复现实验。资源共26个文件,其中18个m脚本为核心代码,7个fig图表用于结果展示,另有1个bmp测试图像,压缩包大小约1.59MB,结构紧凑实用。已有4295人学习下载,常用于算法对比、参数调优及课程设计等场景。通过研读代码,可掌握迭代软阈值、正交匹配追踪等重构思路,并能进一步扩展至不同测量矩阵效果比较、噪声环境下的鲁棒性实验,以及医学成像、遥感分析等应用领域,是一份兼顾理论理解与动手实践的优质参考资料。

1. 二维图像的压缩感知重构:一篇能直接跑通的 MATLAB 实战笔记

一张 256×256 的灰度图,直接按像素采集需要 65536 个测量值;压缩感知理论告诉你,用其中 5%~10% 的随机线性测量,配合稀疏重构就能把图像恢复出来。二维图像的压缩感知重构算法程序代码,做的就是把这句理论落地成可运行的 MATLAB 工程。这篇笔记从测量矩阵构造、稀疏基选择写起,一直到 OMP 迭代、分块策略和参数调法,每个关键参数都给出取值依据。适合刚接触压缩感知、想绕开论文公式直接出图的开发者,也适合手头代码能跑但重构总翻车、想知道问题出在哪的人。理论只讲必要部分,重点是可复现代码和排错思路。

2. 压缩感知理论骨架:稀疏表示、测量矩阵与分块的工程理由

压缩感知重构的起点不是算法,而是三个前提:信号可稀疏表示、测量矩阵与稀疏基不相关、测量数大于稀疏度若干倍。三个条件都满足,欠定方程 y=Φx 才有唯一稀疏解。二维图像之所以是压缩感知最经典的应用场景,是因为自然图像在 DCT 或小波域天然稀疏,这给下面所有代码提供了成立基础。

2.1 图像在哪个变换域稀疏:DCT 基与稀疏度 K 的估计

CS 恢复图像的前提是信号可压缩。对二维图像来说,像素域的灰度值天然不平缓,直接把像素当 x 去求稀疏解没有意义,必须先在某个正交基 Ψ 下做变换:x = Ψθ,其中 θ 只有 K 个显著非零项,这个 K 远小于图像维度。这时观测方程变成 y = ΦΨθ = Aθ,y 是低维测量,A 是 M×N 的传感矩阵。因为 M < N,方程欠定,但若 θ 足够稀疏,求解 min‖θ‖₀ s.t. ‖y-Aθ‖≤ε 就能恢复。实际算法用 OMP 这类贪心策略逼近最优解,省去组合爆炸的 l0 搜索。

图像处理里最常用的稀疏基是 DCT 和小波。DCT 对平滑区域的能量集中度很好,16×16 块内几乎全部能量集中在前几十个低频系数;小波对边缘和纹理更友好,但 MATLAB 里构造可逆小波矩阵要绕一步。工程第一版我一般选 DCT:dctmtx 一行生成正交矩阵,计算快,分块重构时稳定性足够。FFT 基也有应用,但周期性延拓在图像块边界会产生振铃,不如 DCT 平稳。

K 怎么估?把一块 16×16 图像做 DCT 后按幅值降序排序,计算累计能量:前 K 个系数能量占全块能量的比例 ≥95% 就认为 K 有效。多数自然图像在 16×16 块内取 K=15~30 就能保住 95% 以上能量。如果实测重构结果细节大量丢失,优先怀疑 K 取小,而不是换基。

2.2 测量矩阵为什么选高斯随机:RIP 与实现成本

测量矩阵 Φ 的作用是把 N 维信号压缩成 M 维测量。理论上要求 Φ 对任意 K 稀疏信号满足约束等距性质:对所有 K 稀疏 θ,存在常数 δ 使 (1-δ)‖θ‖₂² ≤ ‖ΦΨθ‖₂² ≤ (1+δ)‖θ‖₂²,保证不同稀疏信号压缩后仍可区分。验证一个矩阵是否满足 RIP 是组合问题,工程上没人真去验证,直接选高斯随机矩阵。

高斯随机矩阵的每个元素独立同分布于标准正态,再做 sqrt(M) 归一化。它的优势是:任意固定正交基 Ψ 与它组成传感矩阵 A 后,以高概率满足 RIP;实现只要一行 randn;和图像内容不耦合,不会因为图像本身有周期性而产生系统性失真。部分傅里叶矩阵虽然利于内存,但需要处理索引重排和共轭转置,第一版代码不建议碰。我的做法就是固定 rng 后 randn(M, N)/sqrt(M),结果可复现,出问题再换。

2.3 分块测量:全图全局测量为什么会内存爆炸

如果对整张 256×256 图像直接构造传感矩阵:图像向量长度 N=65536,取 10% 测量率 M≈6554,A 的维度是 6554×65536,元素约 4.3 亿个,double 存储占 3.4GB,这还只是单精度中间量。分块后呢?16×16 的块向量长度 256,M=8 时 A 只有 8×256 个元素,几十 KB,全图所有块复用同一个 A,内存开销可忽略,矩阵乘法还能复用。

分块代价是块与块之间失去耦合,重构后接缝处可能出现方块效应,这个坑在第五部分给对策。对首次实现来说,分块是把理论跑通的关键决策:矩阵小、速度快、内存稳,值得先接受块效应,再逐步优化。

3. 完整 MATLAB 实现:分块测量与 OMP 重构的闭环代码

3.1 主程序:测量矩阵、稀疏基与分块测量

% cs_image_omp_demo.m % 二维图像压缩感知重构:分块测量 + OMP + DCT clear; clc; rng(2024); % 固定随机种子,保证结果可复现 img = imread('cameraman.tif'); % 换成你自己的 8 位灰度图 img = im2double(img); [rows, cols] = size(img); block = 16; % 分块大小,rows 和 cols 需能被 block 整除 M = 8; % 每块测量数,决定压缩率 M/block^2 K = 24; % 稀疏度估计,OMP 迭代上限 D = dctmtx(block^2); % block^2 维正交 DCT 矩阵 Psi = D'; % x = Psi * theta Phi = randn(M, block^2) / sqrt(M); % 高斯测量矩阵 A = Phi * Psi; % 传感矩阵 A = Phi * Psi numRow = rows / block; numCol = cols / block; Y = zeros(M, numRow * numCol); % 保存每块的测量向量 idx = 1; for r = 1:numRow for c = 1:numCol xb = img((r-1)*block+1 : r*block, (c-1)*block+1 : c*block); Y(:, idx) = Phi * xb(:); % y = Phi * x,测量端实现 idx = idx + 1; end end

稀疏基用 dctmtx 的转置做反变换矩阵 Psi,传感矩阵 A 不需要单独存储全尺寸,按块复用。测量循环只做矩阵乘,没有隐式维度问题。参数 row/col 通过 numRow/numCol 保证整除,不符合时先 imresize 或裁剪。测量向量 Y 的维度是 M × 块数,后续重构按同一顺序取列。

3.2 OMP 函数:原子选择、最小二乘与残差更新

function theta = omp_solve(A, y, K) % OMP 求 y = A * theta + e,假设 theta 稀疏度为 K [~, N] = size(A); At = A'; theta = zeros(N, 1); r = y; % 初始残差 selected = zeros(K, 1); % 记录被选原子下标 tol_r = 1e-6 * norm(y); % 残差相对阈值 for s = 1:K corr = At * r; % 残差与所有原子的内积 if s > 1 corr(selected(1:s-1)) = 0; % 禁止重复选同一原子 end [~, pos] = max(abs(corr)); % 选内积绝对值最大的原子 selected(s) = pos; As = A(:, selected(1:s)); % 已选原子构成的子矩阵 theta_ls = As \ y; % 最小二乘求当前系数 r = y - As * theta_ls; % 更新残差 if norm(r) < tol_r % 残差足够小则提前停止 break; end end theta(selected(1:s)) = theta_ls; end

每次迭代先算残差与所有原子的内积,选出相关性最大的原子加入支撑集;再用支撑集上的最小二乘获得当前系数,最后计算新残差。最小二乘那一步是关键:它让已选原子的系数整体重估,而不是各自独立求内积,因此收敛更快。残差阈值取 norm(y) 的 1e-6 倍,避免用绝对阈值在大动态范围图像上误停。

K 一旦达到,循环停止。OMP 对稀疏度很敏感:K 大于真实稀疏度会把噪声也拟合进来,K 太小则细节丢。初始 K 设为 M/3 到 M/2 之间,M=8 时 K=24 是偏保守的上限。

3.3 重构端:分块回溯与质量输出

rec = zeros(rows, cols); idx = 1; for r = 1:numRow for c = 1:numCol theta = omp_solve(A, Y(:, idx), K); xb_rec = Psi * theta; % 反变换回像素域 rec((r-1)*block+1 : r*block, (c-1)*block+1 : c*block) = reshape(xb_rec, block, block); idx = idx + 1; end end rec_disp = min(max(rec, 0), 1); % 裁剪到 [0,1] figure; subplot(1,2,1); imshow(img); title('原图'); subplot(1,2,2); imshow(rec_disp); title(sprintf('OMP 重构, PSNR = %.2f dB', psnr(rec_disp, img)));

重构时每块独立调用 omp_solve,返回的是 DCT 系数,用 Psi 反变换得到像素块,再放回原位置。显示前必须裁剪到 [0,1],否则 psnr 会因为越界报错,imshow 也会自动截断产生错觉。PSNR 在 20~35 dB 之间是正常区间,低于 20 dB 先查测量矩阵是否归一化,高于 35 dB 基本说明测量率太浪费,应该降 M 看实际压缩率。

3.4 参数表:一组能直接起步的默认值

| 参数 | 示例值 | 作用与调整方向 | | block | 16 | 决定向量维度和局部平稳性;8 更快,32 更依赖图像纹理 | | M | 8 | 每块测量数,压缩率 = M/block²,至少是 K 的 2~3 倍 | | K | 24 | 稀疏度上限,过小丢失细节,过大引入噪点 | | rng 种子 | 2024 | 固定测量矩阵,便于对比实验 | | tol_r | 1e-6 | 残差相对阈值,越小迭代越多 |

提示:第一版调试不要同时调 M 和 K,固定一个参数扫另一个,否则很难判断变化来源。

4. 算法选型:OMP 之外,SAMP、CoSaMP 与 TV 各自的适用场景

4.1 SAMP:稀疏度未知时用步长逼近

OMP 最大的弱点是要预先知道 K。实测中 K 对重构质量很敏感,K=20 和 K=30 可能一个模糊一个带噪。SAMP 的思路是以步长 L 分阶段扩展支撑集:每阶段先按残差相关性选入 L 个候选原子,用最小二乘估计后看残差是否下降,如果残差不降反增,就进入下一阶段并增大 L。这样稀疏度未知也能收敛,代价是迭代次数增加、对步长初值敏感。

% SAMP 核心迭代片段(简化版) % 输入 A, y, stepSize,输出 theta r = y; selected = []; stage = 1; L = stepSize; while norm(r) > 1e-6 * norm(y) && stage * stepSize <= M [~, pick] = sort(abs(A' * r), 'descend'); candidate = union(selected, pick(1:L)); % 候选集 theta_ls = A(:, candidate) \ y; r_new = y - A(:, candidate) * theta_ls; if norm(r_new) >= norm(r) stage = stage + 1; % 残差没降,扩大步长 L = stage * stepSize; else selected = candidate; r = r_new; end end

这段是思想验证用的简化版,没做剪枝。实际用 SAMP 要注意:stepSize 一般取 1~3,太大绕过最优解,太小迭代爆炸。适用场景是 K 完全不清楚、需要自动定阶的探索阶段。

4.2 CoSaMP:用回溯剪枝换稳定性

CoSaMP 每次迭代选 2K 个原子进候选集,再通过最小二乘和剪枝只保留其中贡献最大的 K 个。它比 OMP 多了一次裁剪,抗噪声能力更强,而且每轮都重新估计全部已选原子的系数,不会像 OMP 那样一旦选错原子就一路错到底。

% CoSaMP 单次迭代核心(示意) % r 为残差,selected 为上一轮支撑集 corr = A' * r; [~, idxTop] = sort(abs(corr), 'descend'); candidate = union(selected, idxTop(1:2*K)); % 选 2K 个候选 theta_ls = A(:, candidate) \ y; [~, keep] = sort(abs(theta_ls), 'descend'); selected = candidate(keep(1:K)); % 保留贡献最大的 K 个 r = y - A(:, selected) * theta_ls(keep(1:K));

CoSaMP 适合稀疏度已知但噪声偏大的场景。对二维图像来说,如果 OMP 重构后背景噪点明显而细节还在,值得换 CoSaMP 试一轮。它的代价是每轮要解两次最小二乘,速度比 OMP 慢约一倍,但对初版调试可以接受。

4.3 TV 重构:面向图像梯度稀疏的先验

OMP 家族都在 DCT 域做稀疏,但 DCT 对块边界的连续性没有约束。TV(全变分)模型直接假设图像梯度稀疏,目标函数是 min‖x‖_TV s.t. ‖y-Φx‖₂≤ε,重构结果天然抑制方块效应和颗粒噪声,对医学图像这类大片平滑区域效果好。代价是求解过程是凸优化迭代,比 OMP 慢一个数量级,而且需要现成的优化求解器,不适合只想快速验证算法的场景。

我的选型习惯是第一版无脑 OMP;如果图像重构后细节够但区域不干净,换 CoSaMP;如果主要是方块接缝问题,优先考虑重叠分块而不是直接上 TV,因为 TV 的调参成本更高。

4.4 四种算法选型表

| 算法 | 稀疏度依赖 | 每轮选原子数 | 抗噪性 | 速度 | 典型场景 | | OMP | 固定 K | 1 | 一般 | 快 | 首次跑通,稀疏度有估计 | | SAMP | 自适应 | 步长 L | 中 | 中 | K 未知,自动定阶 | | CoSaMP | 固定 K | 2K 剪回 K | 较强 | 较慢 | 噪声明显,稳定性优先 | | TV | 不需要 | 无 | 强 | 慢 | 平滑区域多,允许离线 |

5. 二维图像重构的 5 个常见坑:现象、原因与排查

5.1 坑一:全图构造测量矩阵直接内存崩溃

现象:代码在构造 A = Phi * Psi 或 Phi = randn(M, rows*cols) 时卡死,任务管理器内存打到上限,或 MATLAB 直接报“内存不足”。

原因:全图向量维数是 rows*cols,对 256×256 图像是 65536,M 取 10% 时测量矩阵有 4.3 亿个元素,double 存储 3.4GB,还没算传感矩阵乘积。

解决:按块测量,A 的尺寸降到 M×block²;若确实需要全局测量,改用部分傅里叶或结构化测量算子,不要实例化稠密矩阵。分块是更简单、对算法验证足够的选择,先跑通再谈全局。

5.2 坑二:K 取太大,重构图像出现颗粒噪声

现象:把 K 从 15 调到 40,PSNR 不升反降,图像上有明显的孤立亮点,像撒了盐。

原因:M=8 时测量向量只有 8 维,OMP 最多只能可靠恢复约 M/2 个自由度,K=40 远超过测量信息量,算法用额外原子强行拟合测量噪声。

解决:K 初始取 M/2 上下。我习惯先在小图上扫 K,画 PSNR 曲线:

% 小图快速扫 K,避免每轮全图重跑 Klist = 4:2:20; pK = zeros(size(Klist)); for i = 1:numel(Klist) recK = cs_reconstruct_64(img64, 16, 8, Klist(i)); pK(i) = psnr(recK, img64); end [~, bestIdx] = max(pK); K_best = Klist(bestIdx);

cs_reconstruct_64 是只处理 64×64 小图的封装函数,几秒能出结果。K 扫描曲线会在某个拐点后明显回落,那个拐点就是当前测量率下的信息量上限。

5.3 坑三:OMP 重复选原子,支撑集条件数恶化

现象:OMP 循环里连续两次迭代选到同一列,最小二乘出现矩阵接近奇异警告,残差停滞不降。

原因:常见于忘记把已选原子从相关投影中排除,或者传感矩阵本身两列高度相关,内积最大位置重复指向同一列。

解决:循环里对 selected 下标位置的内积值直接置零,代码在 3.2 已经实现。若置零后仍反复选到相近列,说明 A 的列相干性太高,检查 Phi 是否有 sqrt(M) 归一化,Psi 是否方正交矩阵。

5.4 坑四:分块重构后出现明显方块接缝

现象:重构图像整体轮廓正常,但每个 16×16 块的边缘有一条亮度跳变线,均匀区域也呈格子状。

原因:独立分块重构没有对块边界做一致性约束,两块的系数估计在边界处不连续,DCT 本身的周期延拓也放大了边界跳变。

解决:两招。第一招是重叠分块:步长取 block/2,每个像素被相邻块覆盖,重构后重叠区域做加权平均,接缝明显软化。第二招是评估时避开边界:PSNR 计算时把四周各裁掉 block 像素,只统计内部区域,避免边界跳变拉低指标。TV 重构本身带平滑先验,也能缓解,但调参成本高。

5.5 坑五:PSNR 算出 NaN 或数值离谱

现象:psnr 返回 NaN,或者同一算法两次跑出来一个 18dB 一个 33dB。

原因:重构值超出 [0,1] 范围,psnr 要求 double 输入在 [0,1];不同随机种子导致测量矩阵变化,重构质量波动大;图像本身不是 [0,1] 范围时 im2double 漏写。

解决:显示和计算前统一 clip:

rec_disp = min(max(rec, 0), 1); p = psnr(rec_disp, img);

评估时固定 rng 种子,做算法对比才公平。PSNR 波动的正常范围是 ±2dB,超过 5dB 先怀疑测量率或 K 参数漂了,不是算法坏了。

6. 重构质量怎么量化:固定种子、PSNR/SSIM 与快速验证流程

验证一套实现是否真跑通,我习惯按三步走。第一步固定 rng,对 64×64 小图做全参数扫描:block 固定 16,M 从 4 到 16,K 取 M/2,画 PSNR 曲线,曲线应单调上升并趋缓。如果 PSNR 完全不动,说明测量或重构链路有逻辑错误,不是调参问题。第二步选定一组好的 M/K,换三张不同类型的灰度图(平滑、纹理、混合),看 PSNR 和 SSIM 是否都在合理区间。第三步记录每次实验的 rng 种子和输入参数,避免“这次效果好,下次复现不了”。

封装一个评估函数会更顺手:

function [p, s] = eval_cs(img, block, M, K) % 把前面的主流程封装成函数 cs_reconstruct rec = cs_reconstruct(img, block, M, K); rec = min(max(rec, 0), 1); p = psnr(rec, img); s = ssim(rec, img); fprintf('PSNR = %.2f dB, SSIM = %.3f\n', p, s); end

SSIM 比 PSNR 更能反映结构保持程度,尤其是判断方块接缝和边缘模糊。SSIM 在 0.9 以上说明重构结构完整,0.7 以下就要回查分块策略和稀疏基选择。

更细的习惯:测量矩阵固定后不要每次都重新生成。把 Phi、Psi、A 作为缓存变量,测量率扫描时唯一变的是 M,A 需要重新生成,但 Psi 可以复用。这样调试时不会因为随机性干扰判断。我踩过的教训是早期没固定 rng,同一组参数跑两次 PSNR 差 3dB,当时还以为是代码有 bug,查了半天,后来发现只是测量矩阵变了。固定种子、先小图、再换图,这套流程比任何理论推导都更能帮你确认实现对不对。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询