稀疏表示与字典学习:MATLAB实现OMP、K-SVD及图像去噪 简介面向信号处理、机器学习与图像处理方向的研究者和学生这份资源以MATLAB代码形式呈现稀疏表示从原理到落地的完整流程适合课程实验、算法复现及课题起步。包体为RAR压缩包共6个文件全部为.m脚本大小仅4KB虽然体积精简却覆盖主程序、PCA预处理、L1范数优化求解、样本读取、精度计算与数据预处理等关键环节。代码对应LASSO、BPDN等常见稀疏编码思路并能衔接字典学习与图像去噪、压缩感知等应用场景可帮助读者边改边跑加深对稀疏性约束和优化算法的理解。资源结构紧凑、无额外依赖可直接运行查看各步骤中间结果便于逐步调试。已有6567人学习下载适合希望用轻量实例快速上手稀疏表示的MATLAB开发者也适合作为理解信号恢复、降噪与分类应用的入门参考。1. 稀疏表示入门为什么一张人脸图像能压缩成 30 个非零系数稀疏表示这几年在图像去噪、人脸识别、压缩感知里频繁出现核心思想其实一句话信号在某个字典下只需要少数几个原子就能线性组合出来。很多人第一次接触会被「稀疏」「字典学习」「OMP」「K-SVD」这些词劝退但真正跑一遍完整的 MATLAB 代码后会发现它没有想象中那么玄学难点反而集中在字典怎么建、稀疏度怎么选、重构误差怎么控制这三个问题上。这篇文章从理论到代码一步一步拆适合正在做图像处理课程设计、毕设或者刚接触压缩感知的从业者看完能用同一套流程处理去噪、修复和人脸识别三类任务。先说明白稀疏表示不是万能的它牺牲的是计算量换来的是对噪声和遮挡的鲁棒性这个取舍在工程选型时非常重要。2. 稀疏表示的核心原理从线性组合到 l0 范数优化2.1 为什么是「稀疏」而不是「低秩」或「紧凑」很多初学者会把稀疏表示和 PCA、低秩分解混在一起。PCA 追求的是用一组正交基重构信号能量集中在前面几个主成分上但主成分是全局的不包含局部结构信息。稀疏表示不一样它允许字典过完备也就是原子数量远大于信号维度然后从这堆冗余基里挑最少的一组来组合信号。举个实际例子一张 64×64 的人脸图像展成 4096 维向量如果用 PCA可能需要几百个主成分才能把误差压到可接受范围如果用稀疏表示配合一个训练好的字典可能 30 到 60 个非零系数就能达到相近的重构质量。这里的「稀疏」指的是非零系数占全部系数的比例极低比如 5% 以下。数学上稀疏表示求解的是这样一个问题给定信号 y 和字典 D找一个系数向量 x让 y ≈ Dx同时要求 x 的 l0 范数非零元素个数尽量小。直接求解 l0 范数最小化是 NP 难问题所以工程上常用两种替代策略一是贪婪算法比如匹配追踪MP和正交匹配追踪OMP每次迭代挑一个最相关的原子二是把 l0 松弛成 l1 范数变成凸优化问题用 LASSO 或基追踪BP求解。MATLAB 里实现 OMP 只需要几十行代码但跑大规模数据时要注意字典矩阵的存储方式后面会讲到。2.2 字典的两种来源固定字典与学习字典字典 D 的构造方式决定了稀疏表示的效率和效果上限。固定字典包括 DCT 字典、小波字典、Gabor 字典优点是构造简单、不需要训练数据缺点是原子形态固定对特定信号不一定是最优的。学习字典则是直接从样本集里学出来的最经典的是 K-SVD 算法它交替执行稀疏编码和字典更新两步每次更新时逐个原子修正而不是一次性整体更新。K-SVD 的名字来源于 K 均值聚类它把每个字典原子看作一个聚类中心用 SVD 分解来更新。这两类字典怎么选我的建议是如果样本量少或者任务简单比如基础的去噪实验DCT 字典完全够用如果样本量大且任务复杂比如人脸识别或者图像修复老老实实跑 K-SVD效果差距肉眼可见。K-SVD 训练比较慢一个 256 原子的字典在 50000 个训练块上可能要跑十几分钟但这是值得的因为字典的适配性直接决定了后面重构误差的上限。2.3 稀疏度参数 k 的选取策略稀疏度 k 是指每个信号最多使用 k 个字典原子。这个参数的选取是实践中最大的坑之一。k 太小重构误差大细节丢失严重k 太大稀疏性优势就没了还会引入噪声。常见的做法是画一条稀疏度-重构误差曲线然后去找拐点。比如对一个测试集分别设 k 5, 10, 15, 20, 30计算平均重构信噪比PSNR通常曲线会在某个 k 值附近开始变平缓那个点就是合适的选择。我在实际项目中一般会用交叉验证的方式把样本分成训练集和验证集在训练集上学习字典在验证集上测试不同 k 值对应的误差选误差最小且 k 尽量小的点。这样就可以避免过拟合。另一个经验是k 的取值和字典大小有关字典越大需要的 k 也越大但不要超过信号维度的 10%否则稀疏性就名存实亡了。3. 完整 MATLAB 实现OMP 稀疏编码与字典学习实战3.1 环境准备与测试数据生成在写稀疏表示代码之前先确认 MATLAB 版本至少是 R2019b因为要用到imageslice这类图像处理函数的话需要 Image Processing Toolbox如果只跑纯矩阵运算基础版就够。测试数据我建议不要直接用真实图像而是先用合成信号验证算法正确性再切换到真实图像。这里用一段混合正弦信号做验证因为它的稀疏性明显便于观察 OMP 的行为。% 生成测试信号三个频率叠加加少量噪声 fs 1000; % 采样率 1kHz t (0:999) / fs; % 1 秒时长 y 0.6*sin(2*pi*50*t) 0.3*sin(2*pi*120*t) 0.1*sin(2*pi*300*t); y y 0.01*randn(size(t)); % 加 1% 的高斯噪声 N length(y); % 信号长度 1000这段代码生成一个 1000 点的混合正弦信号三个正弦分量频率分别是 50Hz、120Hz、300Hz。稀疏表示的角度看这个信号在傅里叶基下理论上只需要 3 个非零系数就能完美表示加噪声后非零系数也不应该超过 6 个。用这个信号测试 OMP如果重构误差在 0.01 量级且非零系数只有 3-4 个说明实现是正确的。注意噪声幅度设为信号幅度的百分之一量级太小体现不出鲁棒性太大会淹没稀疏结构。3.2 正交匹配追踪 OMP 的核心代码实现OMP 算法是稀疏编码里最常用的工具它的思路很简单每次迭代从字典里找一个与当前残差最相关的原子然后用最小二乘重新计算系数更新残差直到达到设定稀疏度或残差足够小。下面这段代码是完整可运行的 OMP 函数不需要额外工具箱。function [x, support] omp(D, y, k, tol) % OMP 正交匹配追踪求解 y D*x约束非零系数数量 k % 输入 % D - 字典矩阵维度 [N, M]M N 时为过完备字典 % y - 观测信号维度 [N, 1] % k - 目标稀疏度最多选 k 个原子 % tol - 残差阈值可选默认 1e-6 % 输出 % x - 稀疏系数向量维度 [M, 1] % support - 被选中的原子索引集合 if nargin 4, tol 1e-6; end [N, M] size(D); x zeros(M, 1); support []; residual y; iter 0; while iter k norm(residual) tol % 计算所有原子与残差的内积绝对值找最相关的原子 correlation abs(D * residual); [~, j] max(correlation); % 如果该原子已在支撑集中为避免重复选择将其相关性置零后重找 if ismember(j, support) correlation(j) 0; [~, j] max(correlation); end support [support, j]; % 最小二乘更新支撑集上的系数 D_support D(:, support); x_support D_support \ y; % 使用反斜杠求解最小二乘 % 更新残差 residual y - D_support * x_support; iter iter 1; end % 将支撑集系数写回完整向量 x(support) x_support; end这段 OMP 代码的要点有三个。第一相关性计算用矩阵乘法D * residual一次算出所有原子与残差的内积比循环快得多。第二最小二乘用的是 MATLAB 反斜杠运算符对于过完备字典支撑集的列数远小于行数反斜杠会走 QR 分解路径数值稳定性好。第三参数 k 和 tol 是双停止条件谁先满足谁停默认残差阈值 1e-6 对大多数信号足够但如果字典原子之间相关性很强建议放宽到 1e-4否则迭代次数会超出预期。有一点要提醒如果字典的列没有归一化相关性比较时会偏向范数大的原子所以字典应该提前归一化到单位 l2 范数。3.3 基于 K-SVD 的字典学习逐原子更新的实现技巧OMP 只能解决编码问题字典还是要学。K-SVD 的 MATLAB 实现分两步走先用 OMP 对所有训练样本做稀疏编码然后逐原子更新字典。这里给出核心的字典更新代码段完整的迭代循环写在主函数里。function [D, X] ksvd_step(Y, Dinit, K, iterations) % K-SVD 单轮迭代编码 字典更新 % 输入 % Y - 训练样本矩阵每一列是一个样本维度 [N, L] % Dinit - 初始字典维度 [N, M] % K - 稀疏度 % iterations - 编码迭代次数 % 输出 % D - 更新后的字典, X - 稀疏系数矩阵 D Dinit; X zeros(size(D, 2), size(Y, 2)); for iter 1:iterations % 使用 OMP 对每一列样本进行稀疏编码 for i 1:size(Y, 2) [x, ~] omp(D, Y(:, i), K, 1e-4); X(:, i) x; end % 逐原子更新 for j 1:size(D, 2) % 找出使用了第 j 个原子的所有样本索引 used find(X(j, :)); if isempty(used) continue; % 没有样本用到该原子跳过 end % 构造误差矩阵减去当前字典时其他原子贡献后的残差 R Y(:, used) - D * X(:, used); R R D(:, j) * X(j, used); % 把第 j 个原子的贡献加回来 % 对 R 做 SVD 分解更新原子和对应系数 [U, S, V] svd(R, econ); D(:, j) U(:, 1); % 新原子取左奇异向量 X(j, used) S(1,1) * V(:, 1); % 新系数取奇异值乘右奇异向量 end end end这段代码的关键在逐原子更新这一步。对每个原子 j找出所有用到它的样本然后计算「去掉原子 j 贡献之后的残差矩阵 R」再对 R 做 SVD。取 U 的第一列作为新原子取 S(1,1) 乘以 V 的第一列作为新系数。这样做的好处是每次更新都能最小化当前误差的 Frobenius 范数收敛速度比简单梯度法快很多。注意svd(R, econ)是经济型分解只计算前 min(行数,列数) 个奇异向量避免内存浪费。实际跑的时候如果样本量很大比如五万列训练数据每轮编码也要五万次 OMP一次迭代可能要几十秒所以 iterations 一般设 5 到 10 轮就够了不是越多越好太多反而会过拟合训练集。3.4 图像去噪实战从图像分块到字典训练再到重构把上面两个函数串起来就是一个完整的图像去噪流程。先读入图像加噪声然后分块处理每个小块展成列向量组成训练矩阵学字典再对每块做 OMP 编码重构后拼回整图。注意分块的目的是让字典学到局部结构而不是全局像素分布。% 主脚本稀疏表示图像去噪 clear; close all; rng(42); I im2double(imread(cameraman.tif)); % 读入标准测试图 I_noisy I 0.1 * randn(size(I)); % 加标准差 0.1 的高斯噪声 block_size 8; % 分块大小 8x8 stride 4; % 步长 4块与块之间有重叠 blocks []; % 提取所有块并展成向量 for i 1:stride:size(I,1)-block_size1 for j 1:stride:size(I,2)-block_size1 block I_noisy(i:iblock_size-1, j:jblock_size-1); blocks [blocks, block(:)]; % 每一列是一个 64 维向量 end end % 初始化字典用随机选取的训练样本加噪声 rng(0); sel randperm(size(blocks,2), 256); Dinit blocks(:, sel) 0.05*randn(64, 256); % 归一化字典原子 Dinit Dinit ./ vecnorm(Dinit); % K-SVD 训练字典 [D, ~] ksvd_step(blocks, Dinit, 8, 10); % 对每个块用 OMP 编码并重构 I_rec zeros(size(I)); cnt zeros(size(I)); for i 1:stride:size(I,1)-block_size1 for j 1:stride:size(I,2)-block_size1 block I_noisy(i:iblock_size-1, j:jblock_size-1); y block(:); [x, ~] omp(D, y, 8, 1e-4); block_rec D * x; block_rec reshape(block_rec, block_size, block_size); I_rec(i:iblock_size-1, j:jblock_size-1) I_rec(i:iblock_size-1, j:jblock_size-1) block_rec; cnt(i:iblock_size-1, j:jblock_size-1) cnt(i:iblock_size-1, j:jblock_size-1) 1; end end I_rec I_rec ./ cnt; % 重叠区域取平均 psnr_rec psnr(I_rec, I); psnr_noisy psnr(I_noisy, I); fprintf(噪声图 PSNR: %.2f dB, 重构 PSNR: %.2f dB\n, psnr_noisy, psnr_rec);去噪效果好坏主要看两个参数稀疏度 K 和块重叠率。K 设 8 到 12 之间效果比较好K 太小会平滑过度K 太大会把噪声也当作结构保留下来。重叠率影响的是拼接处的接缝步长设为块大小的一半也就是 4这样每个像素会被多个块覆盖取平均后接缝基本消除。还有一点初始字典如果直接用噪声块学到的原子会带噪声纹理所以代码里加了 0.05 的扰动作为初始化这一步叫「去偏初始化」可以让 K-SVD 更快收敛。实际跑完这段代码cameraman图像的 PSNR 通常在 27 到 30 之间作为对比直接用均值滤波的 PSNR 一般是 25 左右稀疏表示大概能提升 2 到 4 个分贝。4. 稀疏表示应用扩展人脸识别、图像修复与压缩感知4.1 人脸识别用稀疏重构误差做分类稀疏表示做人脸识别的基本假设是同一类人脸样本共享一个低维子空间任意一个测试样本可以用该类所有训练样本的稀疏线性组合表示。识别时把测试样本对所有类分别做稀疏编码重构误差最小的那个类就是分类结果。这个思路在鲁棒性方面比传统最近邻方法好很多特别是当测试图像有遮挡或者光照变化时。下面是核心分类代码数据集用的是模拟项目X的人脸库每类 20 张灰度图尺寸统一为 32×32。% 人脸识别稀疏表示分类 function label src_classify(D, train_labels, test_img, k) % 输入 % D - 字典矩阵每一列是一张训练图像已展成列向量 % train_labels- 训练集标签向量 % test_img - 测试图像列向量 % k - 稀疏度 % 输出预测标签 x omp(D, test_img, k, 1e-3); classes unique(train_labels); errors zeros(length(classes), 1); for c 1:length(classes) idx find(train_labels classes(c)); % 属于当前类的原子索引 x_c zeros(size(x)); x_c(idx) x(idx); % 只保留当前类的系数 errors(c) norm(test_img - D * x_c); % 重构误差 end [~, label] min(errors); label classes(label); end这段分类代码虽然只有十几行但抓住了 SR-C稀疏表示分类的核心不是直接用系数做分类而是用「各类单独重构的误差」做决策。注意 x_c 构造时把非当前类的系数全部清零这是整个方法的关键。稀疏度 k 一般取训练样本数的 10% 到 30%比如每类 20 张训练图、字典总共 400 列k 取 30 到 60 比较合适。还有一个容易忽略的点字典 D 的每一列必须归一化否则原子范数差异会直接影响重构误差比较的公平性。我当时跑的时候遇到过一个诡异现象B 类的原子范数比 A 类大导致所有测试样本都偏向 B 类归一化后问题立刻消失。4.2 图像修复处理随机像素缺失与连续遮挡图像修复和去噪的流程很相似但观测模型不同。去噪是完整图像加噪声修复是部分像素缺失。用稀疏表示做修复时先要把已知像素对应的观测矩阵提取出来然后在观测约束下做稀疏编码。MATLAB 中可以用掩码矩阵实现这样不用每次重新构图。下面是随机像素缺失修复的完整实现。% 图像修复只保留 20% 随机像素 I im2double(imread(barbara.png)); mask rand(size(I)) 0.2; % 随机保留 20% 的位置为 true I_missing I .* mask; % 缺失位置为 0 block_size 8; stride 2; % 修复任务用更小的步长 D dct_dict(block_size*block_size, 128); % 预置 DCT 字典见下方函数 % 修复过程对每个块单独处理 I_rec zeros(size(I)); cnt zeros(size(I)); for i 1:stride:size(I,1)-block_size1 for j 1:stride:size(I,2)-block_size1 block_mask mask(i:iblock_size-1, j:jblock_size-1); if sum(block_mask(:)) 16 continue; % 已知像素太少跳过该块 end block I_missing(i:iblock_size-1, j:jblock_size-1); y_obs block(block_mask); % 只取已知像素 D_obs D(block_mask(:), :); % 字典行也按掩码抽取 x omp(D_obs, y_obs, 12, 1e-3); % 在观测域做稀疏编码 block_rec reshape(D * x, block_size, block_size); I_rec(i:iblock_size-1, j:jblock_size-1) I_rec(i:iblock_size-1, j:jblock_size-1) block_rec; cnt(i:iblock_size-1, j:jblock_size-1) cnt(i:iblock_size-1, j:jblock_size-1) 1; end end I_rec I_rec ./ max(cnt, 1);修复任务的坑和去噪完全不同。第一掩码抽取字典D_obs D(block_mask(:), :)的操作必须放在循环外提前做索引预计算否则每个块都要重新生成逻辑索引速度会慢好几倍。第二块内已知像素太少时比如小于 16 个OMP 求解的最小二乘会变成欠定问题重构结果基本是随机噪声所以直接跳过比较稳妥。第三DCT 字典在这里比 K-SVD 学习字典更稳定因为修复任务中观测信息不完整学习字典容易过拟合到已知像素的分布上。如果非要加学习字典可以在完整图像的无遮挡区域预先训练再用于遮挡区域。4.3 压缩感知从欠采样测量到信号恢复压缩感知是稀疏表示最硬核的应用场景它处理的是「观测维度远小于信号维度」的问题。理论上只要信号在某字典下稀疏且测量矩阵满足约束等距性RIP就能从远少于奈奎斯特采样定理要求的测量数中恢复信号。在 MATLAB 里演示这个场景最容易因为可以精确控制稀疏度和测量数。% 压缩感知一维信号恢复 N 256; % 原始信号长度 M 80; % 测量数远小于 N K 8; % 稀疏度 % 构造稀疏信号只有 8 个非零位置 x_true zeros(N, 1); idx randperm(N, K); x_true(idx) randn(K, 1); % 测量矩阵用随机高斯矩阵 A randn(M, N) / sqrt(M); y A * x_true; % 压缩测量 % 恢复用 OMP 直接在 A 字典下编码此时字典就是 A Phi A; [x_rec, support] omp(Phi, y, K, 1e-6); % 计算恢复误差 err norm(x_true - x_rec) / norm(x_true); fprintf(相对恢复误差: %.6f\n, err);这段代码的关键对照关系压缩感知里的测量矩阵 A 等价于稀疏表示里的字典 D y 的观测等价于被压缩的信号。OMP 的字典就是 A而真实信号 x_true 已经是 K 稀疏的。当 M 满足 M 2K*log(N) 这个经验阈值时恢复误差通常在 1e-5 量级。如果 M 太小比如取 40OMP 会恢复失败误差直接到 0.3 以上。这个临界行为可以作为验证算法正确性的试金石。要注意的是随机高斯测量矩阵每列需要归一化否则相关性偏大会破坏 OMP 的选择正确性但归一化方式不是简单的除以列范数而是要在生成时用 sqrt(M) 缩放整行行归一化才不会改变测量的信噪比特性。5. 稀疏表示避坑指南五个高频错误与排查手段5.1 字典原子未归一化导致分类和重构同时失效现象人脸识别准确率在某个类上断崖式下降或者去噪重构图像出现大片明暗不均的色块。原因字典列向量范数不一致OMP 相关性计算时偏好范数大的原子最小二乘阶段又会放大这个偏差。解决字典训练和初始化后强制加一步D D ./ vecnorm(D)或者用D normc(D)需要神经网络工具箱。我建议在 OMP 函数内部主动检测列范数是否接近 1偏差超过 1e-3 就报警这个防御习惯能省很多排查时间。5.2 稀疏度 k 设得过大噪声被当作有效结构保留现象去噪后的图像不仅没变干净反而出现很多细小的颗粒状纹理PSNR 比噪声图还低。原因k 值超过信号本身的稀疏度OMP 被迫继续选择残差中的噪声成分完成噪声拟合。解决画稀疏度-重构误差曲线选拐点同时观察恢复图像的视觉质量。另外可以做个快速验证对纯噪声信号白噪声跑 OMP如果 k 大于某个阈值重构误差反而增大那个阈值就是上限参考。我一般会把 k 设定在信号维度的 1% 到 5% 之间64 维图像块对应 3 到 8 个原子。5.3 分块重叠参数设置错误重构图像出现网格效应现象去噪结果有明显的块状拼接痕迹像是马赛克。原因步长大于块大小的一半也就是块与块之间没有足够重叠边缘处的重构不连续。解决步长设置不超过块大小的 50%比如 8×8 块的步长设为 4 或更小。如果步长等于块大小完全不重叠每个块独立编码块边界必然产生接缝。重叠区域的像素用累加平均代替直接覆盖代码里cnt计数数组就是干这个用的。这个坑最容易在赶实验时踩到因为步长影响不算大从 4 改成 8 速度可以快一倍但效果差很多。5.4 OMP 函数与输入矩阵类型不匹配导致内存错误或极慢现象函数运行到一半报Dimensions of matrices being concatenated are not consistent或内存不足。原因OMP 输入 y 如果是行向量而非列向量D * residual的结果维度会错乱或者 D 存储为sparse矩阵矩阵乘法和索引操作行为与普通矩阵完全不同。解决函数入口统一加y y(:)做列向量化字典 D 在调用前先检测issparse(D)若是稀疏矩阵则用full(D)转换。这个排查思路可以处理大多数莫名其妙的维度报错。5.5 训练样本过大导致内存溢出K-SVD 跑到一半假死现象训练样本矩阵有几十万列blocks [blocks, block(:)]在循环里不断拼接内存不涨但运行越来越慢最后 MATLAB 卡死。原因循环内动态扩容矩阵会导致反复重新分配内存。解决先确定分块总数预分配矩阵blocks zeros(64, total_blocks)循环只填入列。或者用 cell 数组收集最后cell2mat拼接。我一般用预分配方案速度快而且代码容易读。如果样本量超过百万列考虑分批次训练字典或者用在线字典学习算法标准 K-SVD 的内存复杂度是 O(N*M)M 是原子数这个不会变但训练样本 L 和原子数 M 的比例建议控制在 100:1 以上。6. 进阶验证技巧用合成数据做算法正确性检验与参数调优当代码跑通以后最难的部分其实是判断结果是否正确。很多时候 OMP 重构误差看起来很小但系数支撑集完全是错的这会导致人脸识别或压缩感知任务失败。所以我的习惯是每次写稀疏表示代码都先用合成数据做一个「封顶测试」。具体做法是随机生成一个稀疏系数向量 x_true用已知字典 D 生成信号 y D*x_true然后直接用 OMP 恢复检查恢复的支撑集是否和 x_true 完全一致。如果不一致说明算法实现有 bug不要急于跑真实数据。% 封顶测试稀疏表示算法的自检脚本 N 100; M 200; K 5; % 信号维 100字典 200 原子稀疏度 5 D randn(N, M); D D ./ vecnorm(D); % 归一化字典 x_true zeros(M, 1); pos randperm(M, K); x_true(pos) randn(K, 1); y D * x_true; % 用 OMP 恢复 [x_rec, support] omp(D, y, K, 1e-8); % 检查支撑集是否完全一致 support_true sort(pos); support_rec sort(support); if isequal(support_true, support_rec) norm(x_true - x_rec) 1e-6 disp(OMP 实现验证通过); else disp(OMP 实现有误请检查相关性计算或最小二乘更新); fprintf(真实支撑集: %s\n, mat2str(support_true)); fprintf(恢复支撑集: %s\n, mat2str(support_rec)); end这个自检脚本的价值在于它把问题暴露在最小范围。如果支撑集不一致可能是相关性计算写错、字典没有归一化、或者残差更新逻辑出错。如果是恢复系数误差大但支撑集对那问题出在最小二乘更新阶段比如没有重新计算所有支撑集系数而是沿用之前迭代的旧值。我每次修改 OMP 函数后都会跑一遍这个测试确认支撑集和系数都正确再继续往下做实验。另一个进阶验证技巧是稀疏度扫描曲线。对压缩感知任务固定测量数 M改变稀疏度 K记录恢复成功率应该得到一条从 100% 到 0% 的过渡曲线。这个曲线可以帮助确认理论阈值是否成立。实际跑的时候每个点要重复 50 次随机实验取成功率否则单次结果方差太大看不出趋势。% 稀疏度扫描观察恢复成功率随稀疏度的变化 N 128; M 50; trials 50; K_range 2:2:20; success_rate zeros(length(K_range), 1); for t 1:trials A randn(M, N) / sqrt(M); for idx 1:length(K_range) K K_range(idx); x_true zeros(N, 1); pos randperm(N, K); x_true(pos) randn(K, 1); y A * x_true; x_rec omp(A, y, K, 1e-6); if norm(x_true - x_rec) / norm(x_true) 1e-3 success_rate(idx) success_rate(idx) 1 / trials; end end end plot(K_range, success_rate * 100, o-); xlabel(稀疏度 K); ylabel(恢复成功率 (%)); grid on;这段扫描代码可以让你直观看到压缩感知的相变现象K 在某个临界值之前恢复成功率接近 100%超过临界值迅速掉到 0。这个临界值大约在 M / (2*log(N/M)) 附近也就是 M50、N128 时K 临界大约在 8 到 10。如果扫描曲线没有明显的相变区说明测量矩阵构造可能有缺陷。我用这个曲线调整过测量矩阵的行归一化方式效果立竿见影。从那以后我每次写稀疏表示相关代码都强制走一遍封顶测试和稀疏度扫描验证无误再进入实际任务。希望这些调试习惯和经验能帮你减少走弯路的时间。本文还有配套的精品资源点击获取