简介:本资源围绕压缩感知理论中的正交匹配追踪(OMP)重构算法,提供一份博士论文配套的Matlab仿真实现,面向信号处理、图像重建与无线通信方向的研究生及科研人员,帮助读者在低采样率条件下理解并复现稀疏信号重构流程。压缩包内共1个文件,为m脚本类型,整体约2KB,可直接在Matlab环境中运行,用于演示测量矩阵构造、原子选择、残差更新与系数求解等核心迭代环节。目前已有420人学习下载,适合作为算法入门与课程实验的参考。通过运行该脚本,读者可直观观察OMP在不同采样率与噪声条件下的重构效果,掌握从信号生成到误差对比的完整验证思路,为后续改进算法或迁移到医学成像、图像处理等实际场景提供可复用的代码基础与调试参考。
1. 拆开 CS_OMP.rar:一份能跑通的压缩感知重构入门包
如果你正在做压缩感知(Compressive Sensing, CS)相关的课题,或者导师甩给你一个「用 OMP 算法重构信号」的任务却不知道从哪下手,这个 CS_OMP.rar 大概率能帮你省掉最难受的冷启动阶段。包里核心就一个 Demo_CS_OMP.m,配套一篇围绕正交匹配追踪(Orthogonal Matching Pursuit, OMP)展开的博士论文,主题是压缩感知重构算法在 matlab 中的实现。它解决的不是「从零推导数学」的问题,而是「给我一份能直接 run、能改参数、能看重构效果」的工程起点。适合信号处理、无线通信、医学成像方向的研究生和刚转过来的工程师,也适合已经懂 OMP 原理但没亲手写过完整仿真链路的人。下面我按「这是什么 → 怎么跑起来 → 参数怎么调 → 坑在哪」的顺序,把这份资源拆开讲清楚。
2. 压缩感知与 OMP 的工程逻辑:为什么这份代码值得逐行读
2.1 压缩感知到底在省什么
传统采样遵循奈奎斯特准则,采样率至少是信号最高频率的两倍。压缩感知换了个思路:如果信号在某个变换域上是稀疏的,那就可以用远低于奈奎斯特的采样率获取信息,再通过重构算法把原始信号恢复出来。这里有两个前提条件必须同时满足——信号本身稀疏,或者在某组基下稀疏;测量矩阵要满足 RIP(Restricted Isometry Property)或者与稀疏基不相关。
OMP 就是重构环节里最经典的一类贪婪算法。它的核心动作很朴素:每次迭代从测量矩阵里挑一个和当前残差最相关的列(原子),把它加入支撑集,然后用最小二乘更新系数、更新残差,直到残差足够小或者达到预设的稀疏度。相比 BP(基追踪)那类凸优化方法,OMP 计算量小、迭代次数可控,在稀疏度已知或可估计的场景下非常实用。
这份资源的价值在于,它没有停在公式层面,而是用 Demo_CS_OMP.m 把「生成稀疏信号 → 构造测量矩阵 → 压缩采样 → OMP 迭代重构 → 误差评估」整条链路串了起来。你拿到手就能看到每一步的矩阵维度怎么变、残差怎么收敛、重构误差怎么算。
2.2 Demo_CS_OMP.m 的骨架拆解
虽然我手上没有逐行原文,但按这类博士论文配套 demo 的常见写法,加上 OMP 标准流程,代码骨架基本可以还原成下面这个结构。你打开文件后可以对照着看,缺哪块补哪块。
%% Demo_CS_OMP.m 典型骨架 clear; clc; close all; % 1. 参数设定 N = 256; % 信号长度 K = 20; % 稀疏度(非零元素个数) M = 64; % 测量数(压缩后维度) maxIter = 2*K; % OMP 最大迭代次数,常见取 2K 留余量 % 2. 生成稀疏信号 x = zeros(N,1); pos = randperm(N, K); % 随机选 K 个位置 x(pos) = randn(K,1); % 赋随机幅值 % 3. 构造测量矩阵(常见:高斯随机矩阵) Phi = randn(M, N) / sqrt(M); % 4. 压缩采样 y = Phi * x; % 5. OMP 重构 x_hat = OMP(Phi, y, K); % 6. 误差评估 err = norm(x - x_hat) / norm(x); fprintf('相对重构误差: %.4f\n', err);这段骨架里,参数区决定了整个实验的难度。N 是信号维度,K 是稀疏度,M 是测量数。M 越小压缩率越高,但重构难度也越大。常见经验是 M 至少取到 K 的 3 到 4 倍,重构才比较稳。测量矩阵用高斯随机矩阵是最省事的做法,因为它和绝大多数稀疏基都不相关,满足 RIP 的概率高。
OMP 函数本身通常单独写成一个子函数或者放在同目录下,核心迭代逻辑如下:
function x_hat = OMP(Phi, y, K) [M, N] = size(Phi); r = y; % 初始残差 support = []; % 支撑集(已选原子索引) x_hat = zeros(N,1); for iter = 1:K % 计算残差与各原子的内积,找最相关列 proj = Phi' * r; [~, idx] = max(abs(proj)); % 更新支撑集 support = [support, idx]; % 最小二乘求解当前支撑集上的系数 Phi_s = Phi(:, support); coef = pinv(Phi_s) * y; % 更新残差 r = y - Phi_s * coef; end x_hat(support) = coef; end这里有几个细节值得注意。pinv用的是伪逆,等价于最小二乘解,计算量比直接求逆稳。残差更新后,下一轮选原子时已经选过的列理论上不会被再选,因为残差在已选列上的投影已经被消掉了。但实际数值计算中偶尔会因为精度问题重复选中同一列,所以有些实现会加一个「已选索引排除」的判断。
2.3 重构效果怎么判断
跑完 demo 后,最直观的评估指标是相对重构误差norm(x - x_hat) / norm(x)。如果误差在 1e-10 量级,说明重构几乎完美;如果在 0.1 以上,说明参数设置有问题或者 OMP 在这个条件下失效了。另一个常用手段是把原始信号和重构信号画在一起对比,稀疏位置是否对得上、幅值是否接近,一眼就能看出来。
常见做法是固定 N 和 K,逐步减小 M,观察误差什么时候开始飙升。这个临界点大致对应 OMP 在这个问题上的重构极限。你也可以固定 M,增大 K,看稀疏度对重构的影响。这些扫描实验是论文里最常放的图,用这份 demo 改几行就能跑出来。
3. 把 Demo 跑起来并改成自己的实验:参数、矩阵与评估
3.1 环境准备与首次运行
matlab 版本方面,这份代码用的是基础矩阵运算和pinv、randn、randperm这些老函数,从 R2015 到 2026b 基本都能跑。不需要额外工具箱,信号处理工具箱和统计工具箱都用不上。如果你用的是 matlab 2026b 或者更早的 2022b、2023b,直接新建脚本粘贴即可。
首次运行建议按下面步骤走:
- 把 CS_OMP.rar 解压到一个纯英文路径下,比如
D:\work\cs_omp\。中文路径在部分 matlab 版本里会导致load或cd出问题。 - 在 matlab 里
cd到该目录,确认Demo_CS_OMP.m和 OMP 子函数在同一目录或已在 path 中。 - 直接运行
Demo_CS_OMP,观察命令行输出的误差值和弹出的对比图。 - 如果报「未定义函数 OMP」,说明子函数没单独存文件,检查 demo 里是否用了局部函数或者需要手动拆分。
提示:如果 demo 里把 OMP 写成了脚本内的局部函数,matlab 要求局部函数放在脚本末尾,且脚本文件名不能和函数名冲突。R2016b 之后支持脚本内局部函数,更早版本需要单独存成 OMP.m。
3.2 关键参数怎么改
参数调整是这份 demo 最有价值的部分。下面这张表把几个核心参数的含义、常见取值范围和调整后的影响列清楚。
| 参数 | 含义 | 常见取值 | 调大后的影响 |
|---|---|---|---|
| N | 信号长度 | 128 / 256 / 512 | 计算量增加,重构难度略升 |
| K | 稀疏度 | 10 / 20 / 40 | 需要更多测量数,迭代次数增加 |
| M | 测量数 | 64 / 128 / 256 | 压缩率降低,重构更稳 |
| maxIter | 最大迭代 | K 或 2K | 过大导致过拟合,过小重构不全 |
| 测量矩阵类型 | Phi | 高斯 / 伯努利 / 部分傅里叶 | 影响 RIP 满足程度和计算速度 |
我一般会先固定 N=256、K=20,把 M 从 40 扫到 128,步长 8,看误差曲线。如果 M 在 60 左右误差突然从 1e-12 跳到 0.3,说明临界点就在那附近。这个扫描用循环包一层就行:
N = 256; K = 20; M_list = 40:8:128; err_list = zeros(size(M_list)); for i = 1:length(M_list) M = M_list(i); x = zeros(N,1); pos = randperm(N, K); x(pos) = randn(K,1); Phi = randn(M,N)/sqrt(M); y = Phi * x; x_hat = OMP(Phi, y, K); err_list(i) = norm(x - x_hat)/norm(x); end plot(M_list, err_list, 'o-'); xlabel('测量数 M'); ylabel('相对重构误差'); grid on;这段代码把单次实验变成了扫描实验,输出的曲线可以直接放进论文。注意每次循环都重新生成信号和测量矩阵,避免同一组数据反复使用带来的偶然性。如果想更严谨,每个 M 跑 50 次取平均,误差曲线会更平滑。
3.3 测量矩阵换型与稀疏基选择
demo 默认大概率用高斯随机矩阵。实际研究中你可能会遇到「用部分傅里叶矩阵能不能重构」这类问题。换测量矩阵只需要改一行:
% 高斯随机矩阵 Phi = randn(M,N)/sqrt(M); % 伯努利矩阵 Phi = sign(randn(M,N))/sqrt(M); % 部分傅里叶矩阵(从 N×N DFT 矩阵随机选 M 行) F = dftmtx(N)/sqrt(N); idx = randperm(N, M); Phi = F(idx, :);伯努利矩阵元素只有 ±1,硬件实现上比高斯矩阵友好,但重构性能略差。部分傅里叶矩阵在通信和成像里更常见,因为它有快速算法,计算效率高。换完之后重新跑误差评估,对比不同矩阵在同一组参数下的表现,这是论文里常见的对比实验。
稀疏基本身 demo 可能默认用单位矩阵(时域稀疏)。如果你要处理频域稀疏信号,需要把信号生成部分改成先产生频域稀疏系数再做逆变换:
% 频域稀疏信号 x_freq = zeros(N,1); pos = randperm(N, K); x_freq(pos) = randn(K,1); x = ifft(x_freq) * sqrt(N); % 转到时域这时候测量矩阵仍然作用在时域信号上,但重构出来的 x_hat 需要再变换回频域才能和原始稀疏系数对比。这一步很容易搞混,后面避坑章节会细说。
4. 避坑与排查:OMP 仿真里最容易翻车的五个地方
4.1 重构误差一直很大,降不下来
现象:跑完 demo,相对误差在 0.5 以上,画图看重构信号和原始信号完全对不上。
原因:最常见的是 M 太小,低于 OMP 在这个稀疏度下的重构下限。其次是测量矩阵和稀疏基相关性太高,比如用单位矩阵做测量矩阵又假设信号时域稀疏,那压缩采样就退化成截断,信息丢失严重。
解决:先把 M 调到 K 的 4 倍以上试一次。如果误差立刻降到 1e-10 量级,说明就是测量数不够。如果还不行,检查测量矩阵是不是和稀疏基「撞车」了。高斯随机矩阵基本不会出这个问题,优先用它排除干扰。
4.2 迭代次数设成 K 但重构不全
现象:误差不大不小,稀疏位置对了一部分,但总有几个非零位置没恢复出来。
原因:OMP 每轮只选一个原子,理论上 K 轮选完 K 个原子。但实际中如果某轮选错了一个原子,后面就需要额外轮次来补偿。把 maxIter 严格设成 K 会导致没有纠错余量。
解决:maxIter 设成 2K 甚至 3K,同时加一个残差阈值提前退出。残差小于 1e-6 时直接 break,避免多余迭代引入噪声。代码里加一行判断即可:
if norm(r) < 1e-6 break; end4.3 重复选中同一列导致系数异常
现象:重构信号在某些位置上幅值特别大,明显偏离原始值。
原因:数值精度问题导致已选过的原子在后续轮次中再次被选中,最小二乘解出现病态。
解决:在选原子时排除已选索引。把proj(support) = 0;加在找最大值之前,强制已选列不再参与竞争。这是最省事的修法,代价几乎为零。
4.4 换稀疏基后忘记变换回去
现象:频域稀疏信号重构后,时域波形看起来还行,但和原始频域系数对比完全不对。
原因:OMP 重构出来的是时域信号 x_hat,而你想对比的是频域稀疏系数。两者之间差了一个 FFT。
解决:对比前先做变换。x_hat_freq = fft(x_hat)/sqrt(N);然后再和原始x_freq比较。或者直接在时域比较 x 和 x_hat,避免混淆。关键是搞清楚你的稀疏表示到底在哪个域。
4.5 每次运行结果都不一样
现象:同一份代码跑两次,误差值不同,有时候差别还挺大。
原因:randn和randperm默认用随机种子,每次生成的高斯矩阵和稀疏位置都不同。这是正常现象,不是代码有 bug。
解决:如果要做可复现实验,在脚本开头固定随机种子rng(42);。这样每次运行生成的信号和矩阵完全一致,误差值也稳定。论文里报结果前记得固定种子,否则审稿人复现不出来。
注意:固定种子只影响可复现性,不影响算法本身的随机性。做统计性实验时反而要故意用不同种子跑多次取平均。
5. 从 demo 到论文级实验:扫描曲线、噪声鲁棒性与收敛判断
把 demo 跑通只是第一步。真正要拿这份资源产出论文数据,还需要做三件事:参数扫描、噪声注入、收敛过程记录。
参数扫描前面已经给了 M 的循环框架,你可以把 K 也加进去做成二维扫描,用imagesc画出误差热力图。横轴 M,纵轴 K,颜色表示误差量级。这张图能直观展示 OMP 在什么参数区间内有效,是论文里很受欢迎的一张图。
噪声鲁棒性测试是另一个常见需求。在压缩采样后加高斯白噪声:
SNR = 20; % 信噪比 dB y = Phi * x; y_noisy = awgn(y, SNR, 'measured'); x_hat = OMP(Phi, y_noisy, K);然后扫描 SNR 从 5dB 到 40dB,看误差怎么变化。OMP 在中等信噪比下通常表现不错,但 SNR 低于 10dB 时误差会明显上升。这个曲线能说明算法在噪声环境下的边界。
收敛过程记录也很简单,把每轮迭代的残差范数存下来画图:
res_hist = zeros(maxIter,1); for iter = 1:maxIter % ... OMP 选原子、更新系数 ... res_hist(iter) = norm(r); end semilogy(res_hist, 'o-'); xlabel('迭代次数'); ylabel('残差范数');正常的 OMP 残差曲线应该单调下降,前几轮降得很快,后面趋于平缓。如果残差出现反弹,说明选原子逻辑有问题或者数值精度不够。这条曲线也是判断 maxIter 设多少合适的依据——残差不再明显下降的那个点就是合理的停止轮次。
我自己的习惯是,每次改完参数先跑一次单次实验看误差和残差曲线,确认没有异常后再批量扫描。直接上循环扫描如果中间某组参数炸了,排查起来很麻烦。从那以后我每次调 OMP 参数都强制先跑单次、看残差、再批量,这个顺序帮我省了不少返工时间。希望这份拆解能帮你把 CS_OMP.rar 里的 demo 真正用起来,跑出自己的实验数据。
本文还有配套的精品资源,点击获取