☰
图像信号处理算法代码实战:ADMM、组稀疏与总变分去噪
2026/10/3 2:49:57 网站建设 项目流程

简介:面向信号处理和图像处理研究者的优化算法代码包,内容围绕交替方向法(ADM)、交替最小化法(AMA)、组稀疏信号去噪以及基于Majorization-Minimization(MM)框架的图像信号处理展开,旨在解决多变量耦合优化、图像去噪、复原与去模糊等经典难点。包内共35个文件,主体为31个Matlab脚本(.m),另有README说明、测试数据(.mat)与Git工程配置等辅助文件,整包仅43KB,轻量易读。目前已有188人学习该资源。资料给出ADMM矩阵补全、非凸Lp优化、二阶全变差(HOTV)、RPCA图像修复、彩色图像ADMM去噪、1D信号去噪、L1-OGS组稀疏去噪以及Split Bregman去模糊等可运行Demo,每个算法均配有演示脚本,便于对照论文理解推导并快速复现。同时目录按算法主题划分,适合作为图像优化领域入门及进阶的参考资料。

1. 一份能直接跑起来的优化算法代码包:ADMM、AMA 与组稀疏信号去噪的真实打开方式

做图像恢复的人大概都遇到过这种尴尬:论文里 ADMM 的迭代公式看得懂,真到写 MATLAB 的时候却不知道辅助变量怎么初始化、rho 调多大、停机条件看哪个残差。这份 Image-Signal-Processing-master 恰好把这类问题集中解决了——矩阵补全、RPCA、总变分去噪、高阶 TV、组稀疏、Lp 非凸范数去噪,全都以可运行的 demo 形式打包在一起,而且每个 demo 主脚本大多只有几十行,适合当基线代码来改。

它适合两类人。一类是正在做图像恢复方向、需要对比算法跑实验的研究生,拿 lrmcADMM、RPCA_ADMM、HOTV 这些现成实现去和自己的方法做对比,省掉从零写优化器的功夫;另一类是工程落地的开发者,想快速判断某个正则项在自己数据上到底有没有效果,直接改 demo 里的参数就行。拆完这套代码之后我的真实感受是:它最大的价值不是某一个单独算法,而是让你一次性看清 ADM、AMA、MM 这几套框架在同一类问题上的行为差异。

2. ADMM 套路拆解:矩阵补全的 lrmcADMM 与图像去噪的 ADMM.m 怎么读、怎么改

2.1 先看懂 ADMM 的迭代骨架:三个更新步与一个停机条件

ADMM 解决的是能写成下面这种形式的最小化问题:

目标函数被拆成 f(x) 加 g(z),中间用线性约束 Ax + Bz = c 联系起来。图像去噪、去模糊、修复、补全几乎都能套进这个壳子,差别只在 f、g 怎么选。算法本身只有四步:x 更新、z 更新、乘子 u 更新、停机检查。前期绝大多数时间都花在求近端算子上。

lrmcADMM.m 是低秩矩阵补全的实现。补全问题的原目标是秩最小化,工程上一般松弛成核范数,也就是奇异值之和最小化。ADMM 把约束拆开,引入辅助变量 Z 和乘子 U。我习惯把这类循环拆成三个子问题来读,核心结构是这样:

% lrmcADMM.m 核心迭代(结构示意,以实际文件为准) for k = 1 : maxIter % X 更新:核范数近端算子 = 奇异值软阈值 X = prox_nuclear(Z - U, tau / rho); % Z 更新:保持观测位置值不变,其余位置跟随 X Z = X + U; Z(Omega) = M(Omega); % 已知位置回填 % 乘子更新:沿原始残差方向做一步梯度上升 U = U + X - Z; % 停机检查:primal residual 小于 tol 就退出 if norm(X - Z, 'fro') < tol * max(1, norm(X, 'fro')) break; end end

三段更新的逻辑很直接。第一行 prox_nuclear 是核范数的近端算子:对矩阵做 SVD,把奇异值往零方向收缩,收缩量是 tau/rho,这一步让 X 保持低秩;第二行 Z 更新里,Z(Omega)=M(Omega) 是强制回填观测位置的值,保证补全结果在已知像素上不漂移,这正是矩阵补全和去噪的本质区别——去噪没有这种硬约束;第三行乘子更新是让 X 和 Z 的差异信息进入下一步。rho 越大,X 和 Z 越贴近,收敛越稳,但每一步的修正幅度也越小,迭代次数会变多。

2.2 跑通 lrmcADMM_Demo.m:矩阵补全的恢复误差与参数设置

这个 demo 不需要改任何代码,在 MATLAB 命令行直接执行就能看到完整流程:

% 进入代码目录后直接运行 run('lrmcADMM_Demo.m')

脚本做的事按固定顺序走:先用 MATLAB 的 randn 生成一个低秩矩阵 M,再按给定采样率随机丢掉一部分元素,接着调用 lrmcADMM 做补全,最后计算恢复矩阵与原矩阵的相对误差并画图。这个流程本身就是标准的合成实验套路,我后来做矩阵补全对比实验时,直接把这一节复制出来改数据源。

demo 里需要关心的参数不多,整理成表看得更清楚:

参数含义典型取值调节方向
m, n矩阵维度100~500维度提高时迭代次数要同步加
r真实秩5~20秩越高,需要的采样率越高
obs_rate观测比例0.2~0.5低于 0.2 时恢复误差涨得很快
rho惩罚参数1~10偏大收敛慢,偏小容易震荡
tau核范数权重与噪声水平相关噪声大就调大
maxIter最大迭代500~1000收敛慢时优先调这个
tol停机容差1e-4~1e-6追求精度时收紧

判断补全质量不要只看最后的相对误差数字,要看误差随迭代的变化曲线。常见的情况是前面几十步误差下降很快,后面进入平台期,这说明算法已经收敛,继续加 maxIter 没有意义。如果误差曲线在某个值附近上下跳动,那几乎都是 rho 没选对。

2.3 从补全到 RPCA 再到图像去噪:同一个框架换目标函数

看懂 lrmcADMM 之后,RPCA_ADMM.m 就非常好理解了。鲁棒主成分分析把观测矩阵 M 拆成一个低秩矩阵 L 加一个稀疏矩阵 S,目标函数变成核范数加 L1 范数,约束条件从 M 的局部位置强制变成了 L+S=M 的全局约束。ADMM 循环里多了一个 S 更新,本质上就是多了一个软阈值算子。

% RPCA_ADMM.m 结构示意 for k = 1 : maxIter L = prox_nuclear(M - S - U, lambda_L / rho); % 低秩部分 S = prox_l1(M - L - U, lambda_S / rho); % 稀疏部分 U = U + (L + S - M); % 乘子更新 end

这个代码拿去做视频前景提取很合适:视频帧拉成列向量放进矩阵 M,背景是低秩的 L,运动的前景物体是稀疏的 S,RPCA 一遍跑完就分离出两部分的近似解。L1 稀疏代表的是孤立的极少数像素被噪声污染,而组稀疏代表非零系数成块出现;图像里的结构纹理天然是成块的,所以组稀疏在去噪上往往比 L1 效果好。这份资源里几乎每个图像 demo 都建立在这个认知上。

ADMM.m 和 Denoise_Img.m 是另一组对照。ADMM.m 是通用求解器,把 f、g 两个函数句柄传进去就能解一大类问题;Denoise_Img.m 是总变分去噪的封装,内部把图像梯度惩罚和保真项拆成两个变量,然后交给 ADMM 迭代。想换保真项时,只改 ADMM.m 里的目标函数和对应的近端算子即可,循环骨架完全不用动。

3. 组稀疏与高阶 TV:GSTVD、HOTV、L0 的去噪选型逻辑

3.1 组稀疏与 L1 稀疏的本质区别:为什么分组正则更贴近图像

标准的 L1 正则假设信号里非零系数是独立出现的,每个系数单独决定去留。但图像不是这样的:一个纹理区域的像素总是成片出现,一条边缘的梯度总是连续延伸。组稀疏把系数预先分组,惩罚的是整个组的范数,这一组要么整体保留、要么整体收缩,更贴合图像的局部结构。

demoOGS_impulse.m 是重叠组稀疏处理脉冲噪声的例子。OGS 和普通组稀疏不同之处在于,它允许分组窗口重叠,一个系数可能同时属于多个组,这样避免在块边界产生断裂感。这段代码用来处理椒盐噪声效果很直观:脉冲噪声是随机孤立的,但图像结构是成组的,正则项会倾向于把孤立噪声点判为组外成分而滤掉。

% demoOGS_impulse.m 的核心调用(结构示意) img = im2double(imread('cameraman.tif')); y = imnoise(img, 'salt & pepper', 0.3); % 30% 椒盐噪声 xRec = gstv2d_imp(y, lambda, groupSize, overlap); % 重叠组稀疏去噪

这个调用里的 groupSize 和 overlap 就是组稀疏特有的参数:groupSize 决定分组窗口多大,窗口太小体现不出成组优势,窗口太大又会让细节被平均掉;overlap 控制相邻窗口的重叠程度,一般取窗口大小的一半。我在自然图像上试下来,3×3 窗口配一半重叠是比较稳的起点,纹理图可以加大到 5×5。

3.2 GSTVD_Img.m 与 TVD_Img.m:一阶 TV 和二阶 TV 的行为差异

GSTVD 全称是组稀疏总变分去噪,它把组稀疏的思想用在梯度域,让梯度沿着边缘方向成组保留。配套的 gstvdm.m 是主迭代函数,gstv2d_imp.m 是脉冲噪声版本,getConvMtx.m 用来生成卷积矩阵——这是把图像差分运算转成矩阵乘法的工具,核心作用是把二维梯度计算变成可迭代的线性算子。

一阶 TV 去噪的问题在于阶梯效应:平坦区域被过度分段,灰度值呈现一块一块的台阶,像油画一样。HOTV 用二阶差分取代一阶差分,惩罚的是灰度变化的加速度,而不是速度,所以能保留平滑区域的连续过渡。HOTV.m 和 HOTV_demo.m 就是干这个的。

方法惩罚对象典型效果适用场景
TVD_Img一阶梯度幅值边缘清晰、平坦区有阶梯卡通图、文字图
GSTVD_Img分组后梯度幅值纹理保留更好自然图像、遥感图
HOTV二阶差分平滑过渡、无阶梯医学图像、连续渐变图
L0非零梯度像素计数边缘最锐利卡通化、边缘提取

如果只是想把噪声去掉、追求自然观感,HOTV 通常比一阶 TV 舒服,代价是边缘会稍微圆滑一点。如果做文字识别预处理,L0 反而更合适,因为它会把过渡区直接压成硬边。

3.3 L0 与 L1 的取舍:DemoL0_ADMM.m 里的硬阈值更新

L0 范数统计的是非零系数的个数,是真正的计数范数,不是凸函数。L1 是 L0 的凸松弛,而 L0_ADMM.m 直接优化原始计数问题,迭代里的近端算子不再是软阈值,变成了硬阈值:小于阈值的系数直接归零,大于阈值的系数保持不变。

% L0_ADMM.m 的梯度更新示意 grad = D' * (D * x - y); % 图像梯度的误差回传 x = x - step * grad; % 梯度下降 x(abs(x) < threshold) = 0; % 硬阈值:小于阈值的直接清零

这段代码的危险点在最后一行。硬阈值不像软阈值那样把系数连续收缩,而是直接砍断,所以对阈值极其敏感,论文里管这叫对初值敏感。阈值设大了,整张图被清零,输出全黑;设小了,噪声一点没去掉。实际使用时应该先用 L1 跑出一个收敛解,再把这个解作为 L0 的初值,并且阈值从大到小递减,这样能显著降低全黑全白翻车的概率。

ALMTV_Demo.m 在 L0 基础上再加一层增广拉格朗日迭代,交替更新图像和辅助变量,收敛行为比直接硬阈值稳定,适合作为 L0 去噪的默认选择。

4. 1D 信号去噪与 AMA:ADMM_1D_Demo 到 lpALM 的非凸扩展

4.1 ADMM_1D_Demo.m 的调用链:信号载入、噪声注入、去噪输出

1D 去噪是理解整套代码的最佳入口,因为它比图像少一个维度,中间变量都能直接画图观察。ADMM_1D_Demo.m 用 testSig3.mat 里存好的干净信号做实验,先人为加噪声,再调用 ADMM_1D.m 恢复,最后把三条曲线画在一张图上对比。

% ADMM_1D_Demo.m 调用骨架(关键步骤) load('testSig3.mat'); % 载入干净信号 y = x + sigma * randn(size(x)); % 叠加高斯白噪声 [xRec, hist] = ADMM_1D(y, lambda, rho, maxIter);

这里 sigma 是噪声标准差,直接决定信噪比;lambda 是正则权重,越大去噪越强、细节损失越多;rho 是惩罚参数。我调参的经验顺序是:先固定 lambda,把 rho 从 1 往上加,观察残差曲线是否平滑下降;稳定后再回头调 lambda,看边缘保留效果。两个参数一起动会很难定位问题。

ADMM_1D_HOTV.m 是 1D 高阶 TV 版本,把惩罚从一阶差分换成二阶差分。1D 信号里的阶梯效应表现为一段段水平直线,用 HOTV 之后变成平滑斜线,对心电图、光谱这类渐变信号效果明显。ADMM_1D_HOTV_lp.m 则把 L1 惩罚换成了 Lp 范数,是非凸的入口。

4.2 AMA_1D.m 和 ADMM_1D.m 的分工:交替最小化与乘子法的差别

AMA 全称是交替最小化算法,和 ADMM 解决的问题类型很像,但迭代结构更轻。我一般理解是:ADMM 用二次惩罚项加乘子更新来强制约束成立,AMA 则不做缩放乘子,直接在原始变量和对偶变量之间交替走近端步,所以单次迭代计算量更小,对于强凸目标函数往往收敛更快。

AMA_1D.m 和 ADMM_1D.m 在 demo 里可以对照跑。效果上,强凸问题里 AMA 前几步下降更快,但接近最优解时容易慢下来;ADMM 全程稳定,对非光滑项兼容性更好。做图像恢复时推荐默认 ADMM,因为 TV 这类非光滑正则项太常见了;如果目标函数里保真项是强凸的,比如加了很强的二次先验,AMA 值得一试。

4.3 lpALM 与 rho 自适应更新:非凸 Lp 范数的调参节奏

lpALM.m 把 L1 换成 Lp 范数,p 取值在 0 到 1 之间,比如 p=0.5 时惩罚比 L1 更接近 L0,能获得更稀疏的解。代价是目标函数变成非凸,对初值和参数都非常敏感,而且没有全局收敛保证。

lpALM_rhoUpdate.m 的自适应更新策略是这套代码里值得重点学的部分:它按照原始残差和对偶残差的比值动态调整 rho,残差差距大时调 rho 让两边保持平衡。这个思路属于优化理论里的经典启发式,它的好处是把 rho 从玄学变成了可自动调节的量。我经常把这套逻辑搬回 ADMM 用,因为这正是 Majorization-Minimization 这种代理函数思想的体现——每次迭代构造一个原目标的上界函数,最小化这个代理函数来逼近原问题的解,rho 就是控制代理函数紧贴程度的旋钮。

% lpALM_rhoUpdate.m 的更新逻辑(结构示意) if primalRes > 10 * dualRes rho = rho * 2; % 原始残差太大:加大惩罚 elseif dualRes > 10 * primalRes rho = rho / 2; % 对偶残差太大:放松惩罚 end

实际跑 lpALM 的时候,p 值不要一上来就设 0.1,建议从 0.9 开始逐步往下试,每步观察恢复信号里非零系数的数量和误差变化。p 越低,中间过程越容易看出来明显的不稳定跳动,这时候先别动 p,把 rho 更新策略打开,稳定性会好很多。非凸方法的价值在信噪比低的时候才体现得出来,噪声大、稀疏性强的场景下,Lp 明显优于 L1;干净信号上两者差不多,不值得为此承担非凸的调参成本。

5. 避坑记录:跑这套代码最常见的五个翻车点

5.1 现象:一运行就报 Undefined function 'lrmcADMM'

直接在 MATLAB 里双击运行某个 demo,结果命令行提示找不到 lrmcADMM 或 RPCA_ADMM 函数。原因是 MATLAB 当前工作路径不在代码所在目录,或者子目录没有加入路径。这套代码的文件散在多个子目录里,主脚本和函数不一定在同一层。

解决方法是启动后先执行一行路径添加命令:

addpath(genpath('Image-Signal-Processing-master'));

这会把整个目录树里所有子文件夹都加进搜索路径。如果改了目录结构,也要重新跑一次。这算是 MATLAB 项目最常见的起步问题了,每次新开 MATLAB 都要记得执行,或者直接在脚本最前面加这行。

5.2 现象:去噪结果出现棋盘格或块状伪影

用 GSDTV 或 OGS 去噪后,图像上出现有规律的方块格子,尤其在平滑区域最明显。根本原因是组稀疏正则和重叠窗口参数不匹配:groupSize 设得太大,或者 overlap 设成了 0,导致分组边界可见。

解决方法是把 groupSize 和 overlap 的比例控制在合理区间,overlap 至少要取窗口大小的一半。如果已经出现块状伪影,优先把 groupSize 降一档试,比如从 5×5 降到 3×3,再把 overlap 调回一半以上。另外,块状伪影也常见于 TV 正则的阶梯效应被误判为棋盘格,此时应该换成 HOTV 而不是继续调窗口参数。

5.3 现象:迭代几百步误差还在震荡,看不出收敛趋势

ADMM 循环里误差曲线像锯齿一样上下跳动,maxIter 加到 2000 也没有改善。这通常是 rho 设置不合理,而且 rho 在整个迭代中保持不变,导致原始残差和对偶残差长期失衡。

解决方法是参考 lpALM_rhoUpdate.m 的思路做 rho 自适应更新,或者手工把 rho 调大一个数量级看曲线是否变平滑。要注意的是,rho 不是越大越好,rho 过大收敛稳定但速度极慢,几百步里误差下降幅度很小,看起来像收敛了其实没有。正确做法是同时观察原始残差和对偶残差两条曲线,调整 rho 让它们同步下降,而不是只看误差曲线。

5.4 现象:L0 去噪输出全黑或全白

DemoL0_ADMM.m 跑完,输出图一片全黑,或者反向的全白。原因是硬阈值选得不对,把所有系数都清零或都保留了。L0 的硬阈值对参数极其敏感,阈值稍大一点直接把信号全部砍掉,这是它和 L1 最大的差异。

解决方法是不要直接用 L0,先用 L1 跑一遍得到一个大致的去噪结果,然后把 L1 的解作为 L0 的初值,这样硬阈值只需要做小幅度裁剪。阈值设置参考噪声标准差,通常取噪声标准差的 1~2 倍作为起点,再往下微调。另外优先使用 ALMTV_Demo.m 这类带增广拉格朗日的实现,它比直接硬阈值迭代稳定得多。

5.5 现象:彩色图像去噪后颜色偏移,边缘有彩边

ADMM_DemoDenoiseColor.m 跑完,整体颜色偏淡或边缘出现红绿蓝的彩色条纹。原因是对 RGB 三个通道单独做去噪,没有约束三通道的公共结构,导致边缘处三通道的梯度方向不一致,叠加出来就是彩边。

解决方法是把 RGB 转成 YCbCr 颜色空间,只对亮度通道 Y 做去噪,Cb 和 Cr 两个色度通道可以保持不动或者用很小的正则量。人眼对亮度细节敏感,对色度细节不敏感,这样做既省计算又避免彩边。已经出现彩边的图,可以对三个通道的梯度做联合稀疏约束,也就是把三通道梯度拼成一个向量后再做组稀疏惩罚,让边缘在三通道里同时出现或同时消失。

6. 把 demo 改造成自己的基线:收敛验证与 1D 到 2D 的扩展套路

6.1 用残差曲线判断收敛:primal residual 才是真正的停机依据

很多 demo 里 tol 是停机的最后一道闸,但真正该盯的是残差曲线。原始残差定义为 ||X-Z||/||Z||,它反映两个变量的一致性;对偶残差反映乘子的变化幅度。只画误差曲线容易误判,误差平了不代表收敛,可能是参数卡住了。我改造代码时第一步永远是加残差输出,代码在调用函数的时候把 history 结构里的残差序列画出来:

figure; semilogy(hist.primalRes); hold on; semilogy(hist.dualRes); legend('primal residual', 'dual residual'); xlabel('iteration'); ylabel('residual');

两条线同步下降并进入平台区,才算真正收敛。如果 primal 和 dual 差距超过一个数量级,r迄今没准就该调 rho 了。从那以后我每次跑这套代码都强制走一遍「先画残差、再调 rho、最后换正则」的流程,已经成了习惯,基本告别了盲目改参数。

6.2 从 1D 信号扩展到 2D 图像:换差分算子就行

ADMM_1D.m 里的差分矩阵是针对一维信号构造的,想改成二维图像去噪,不需要重写整个 ADMM 框架,只需要把一阶差分算子 D 换成二维梯度算子。常见做法是用 getConvMtx.m 生成二维卷积矩阵,把图像拉成列向量后,梯度计算就是一次矩阵乘法;代价是矩阵维度变大,迭代变慢,但代码结构完全不用换。

% 1D 差分算子 → 2D 梯度算子的替换思路 [Dx, Dy] = getConvMtx(imgSize, 'gradient'); % 横向、纵向差分 D = [Dx; Dy]; % 拼成完整梯度算子

这个替换思路同时适用于 HOTV、L0、lpALM 全部方法,因为它们的差异只在近端算子和正则权重,骨架是同一套。这就是这套资源最大的杠杆点:把 1D demo 吃透,等于拿到了 2D 所有方法的主钥匙;把 ADMM 吃透,等于拿到整套图像恢复算法的公共底盘。希望帮到你。

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

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

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

立即咨询