简介:这是面向MATLAB图像处理学习者的K-SVD字典学习与稀疏表示去噪代码包,适用于研究稀疏编码、字典训练及图像去噪的本科生、研究生或工程师,也可用于课程设计、毕业设计或科研复现。压缩包共61个文件,以37个m脚本构成完整算法主线,涵盖初始化字典、稀疏编码、字典更新、迭代去噪等关键环节;7个c源代码与6个mexw32编译文件用于加速底层矩阵运算,5张png测试图像便于直接验证去噪效果,随附论文PDF和说明文档可辅助理解原理。已有433人学习下载。通过配套演示脚本,读者能快速跑通从字典学习到图像去噪的全流程,掌握K-SVD在保留边缘纹理的同时抑制噪声的实现思路;同时可结合源码与论文,进一步学习OMP稀疏编码、字典可视化等方法,并基于代码修改参数、接入自己的图像数据继续实验。
1. K-SVD图像去噪:一份MATLAB字典学习代码包能省多少事
做图像去噪的从业者大概都遇到过这个尴尬:高斯滤波和中值滤波试一圈,噪声压下去了,边缘和纹理也跟着糊了;换小波阈值去噪,参数调半天,对不同类型的图表现忽好忽坏。我最早在matlab里折腾k-svd图像去噪时,光是把“稀疏编码+字典更新”这套流程写对就折腾了两周,后来拿到这份KSVDBox代码包才真正跑通。它解决的核心问题是:用K-SVD字典学习算法训练出过完备字典,再通过稀疏表示重建图像,在去掉高斯白噪声的同时把边缘和细节保留下来。适合正在做图像去噪课程设计、入门稀疏表示研究、或者想快速在MATLAB里复现字典学习结果的工程师和学生。这份代码里既有完整的字典训练流程,也有OMP去噪和演示脚本,值得拆开看一遍。
2. K-SVD原理拆解:稀疏编码与字典更新这对组合拳怎么打
2.1 稀疏表示的基本假设:为什么自然图像能稀疏表达
K-SVD的起点是一个反直觉的假设:自然图像虽然看起来信息量很大,但在合适的字典下,每个局部块只需要少数几个原子就能逼近。“字典”在这里不是查表用的词表,而是指一组基原子排成的矩阵D,每一列是一个原子,长度等于图像块展平后的维度。比如8×8的块,展成64维列向量,字典就是64×K的矩阵,K通常取128到512,这个矩阵是过完备的,因为列数远大于行数。
稀疏性约束的目标函数可以写为:
min ||x||_0 subject to ||y - Dx||_2 ≤ ε
这里的||x||_0是L0范数,统计非零系数的个数。直接求解L0问题是NP难的,常见做法是用L1范数替代,或者用匹配追踪类贪心算法。K-SVD代码包里配套的OMP(Orthogonal Matching Pursuit)就是这种贪心算法的典型实现:每轮从字典里选一个与当前残差最相关的原子,移除其贡献,直到残差的能量低于设定的容限。OMP的收敛速度比简单的匹配追踪快,代价是每轮多做一次最小二乘投影,但字典的列数撑死也就几百,这个开销完全可接受。
有一点值得单独说:稀疏表示里的x只记录“哪些原子、多大系数”,不记录原子顺序,所以x天然地做了一次特征选择。对图像去噪来说,特征选择的方向恰好是“保留信号、丢弃噪声”。因为信号在字典下有稳定的结构,少数几个原子就能解释它;而高斯噪声是随机的,摊到每个系数上都有点能量,强制稀疏就是把摊开的噪声能量扔掉了。
2.2 K-SVD迭代:先固定字典做稀疏编码,再逐原子更新字典
K-SVD的完整拼写是K-Singular Value Decomposition,也就是更新某一列时做一次SVD,而不是对整本字典做一次SVD。训练过程是坐标下降式交替优化:固定字典D,对每个训练样本做稀疏编码,得到系数矩阵X;然后固定X,逐列更新D。两个子问题都有成熟的求解器,交替几轮后,字典就从不带先验的通用基“进化”成与训练数据强相关的专用基。
字典更新这一步是K-SVD名字里“奇异值分解”的来源,也是最容易写错的部分。朴素想法是对整本字典做一次SVD,但那样会同时扰动所有原子,把系数矩阵搞乱。K-SVD的做法是逐列处理:只动那些使用了第k个原子的样本,先从残差里去掉其他原子的贡献,再对残差做SVD,用最大奇异值对应的左奇异向量替换d_k,同时用最大奇异值乘右奇异向量刷新对应的系数行。下面这段是更新单个原子的简化实现:
% 简化版:更新字典第k列(示意,非完整代码) idx = find(X(k, :) ~= 0); % 哪些样本用了第k个原子 R = Y(:, idx) - D * X(:, idx); % 去掉所有原子贡献后的残差 R = R + D(:, k) * X(k, idx); % 加回第k个原子这一路的贡献 [U, S, V] = svd(R, 0); % 对受限残差做SVD D(:, k) = U(:, 1); % 用最大奇异值左奇异向量做新原子 X(k, idx) = S(1, 1) * V(:, 1)'; % 同步更新这行的系数解释一下这三行核心逻辑:第一行idx只筛出用过第k个原子的样本,这是“受限更新”的关键;第二行先算整体残差,减掉所有原子贡献,再加回第k个原子的贡献,相当于只看第k个原子单独能解释多少;第三行SVD取出最大奇异值方向,就是当前数据对这个原子期望的最优修正方向。之所以不直接对整本字典做SVD,就是因为逐列更新能保证每次只改变一个语义,其他原子不受牵连,交替过程才稳定。
这也是K-SVD和MOD这类早期字典学习算法的核心区别。MOD直接对字典做整体最小二乘更新,计算快,但缺少逐原子精修机制;K-SVD用逐列SVD,数值上更稳,配合OMP稀疏编码收敛更快。实际训练时,ksvd.m默认跑10轮左右,前几轮字典变化很大,后面几轮基本是微调。如果你自己实现,建议把每轮的近似误差||Y - D·X||_F打出来观察下降曲线,连续几轮变化小于1e-3就可以提前停。
2.3 去噪模型:DC分量、误差容限与重叠块聚合
把训练得到的字典用于去噪,并不是对整幅图像做一次稀疏分解,而是先把图像切成大量重叠的小块,每个块单独编码,再拼回去。这一步在代码包里对应im2colstep和col2imstep这一对函数。分块时步长为1像素,也就是8×8的块在图像上滑动时,相邻块重叠7个像素,每个像素会被很多块覆盖,重建时用countcover统计每个像素被覆盖的次数做加权平均,块与块之间的衔接才平滑。
去噪前还要做一件容易被忽略的事:把每个图像块减去自身的均值。这个均值在K-SVD术语里叫DC分量,代表块的整体亮度。如果带着DC分量做稀疏编码,字典会浪费好几个原子去表达“接近常数的模式”,而且噪声对DC的影响会被放大。代码包里的remove_dc.m和add_dc.m就是干这个的:编码前减均值,重建后加均值。
误差容限的设置和噪声方差直接相关。假设图像块是64维的向量,像素值上加的是标准差为σ的高斯白噪声,那么每块噪声能量的期望大约是64·σ²。OMP编码时会把残差压到某个阈值以下,这个阈值通常设为C·64·σ²,C在0.1到1之间。代码里ompdenoise.m接收的errorGoal参数,K-SVD去噪入口里按噪声方差自动推算,所以sigma参数千万别传错——传小了字典会去学噪声,传大了会过平滑,把真实边缘也当噪声扔掉。
3. 跑通K-SVD去噪:从demo脚本到自定义图像
3.1 代码包文件地图:训练、去噪、工具三类
拿到压缩包先别急着点demo,把文件分类一遍,后面翻代码会快很多。第一类是字典训练:ksvd.m是主函数,odctdict.m、odct2dict.m、odct3dict.m生成不同维度的过完备DCT初始字典。第二类是去噪与稀疏编码:ksvddenoise.m是去噪入口,ompdenoise.m、ompdenoise1.m、ompdenoise2.m、ompdenoise3.m是从稀疏编码到图像重建的多个版本,ksvddenoisedemo.m是完整演示脚本。第三类是工具:im2colstep.m、col2imstep.m、countcover.m负责图像分块与聚合,remove_dc.m、add_dc.m处理直流分量,showdict.m画字典,reggrid.m和sampgrid.m生成采样网格,private目录里的.c和.mexw32是OMP等核心运算的C加速。
| 类别 | 文件 | 职责 |
|---|---|---|
| 字典训练 | ksvd.m | K-SVD交替优化主流程 |
| 初始字典 | odctdict.m、odct2dict.m、odct3dict.m | 生成过完备DCT字典 |
| 去噪入口 | ksvddenoise.m | 分块、训练、重建全流程封装 |
| 稀疏编码 | ompdenoise.m、ompdenoise1~3.m | 不同形式的OMP去噪实现 |
| 演示 | ksvddenoisedemo.m | 一键跑通的完整演示 |
| 块操作 | im2colstep.m、col2imstep.m、countcover.m | 分块、聚合、覆盖计数 |
| 工具 | showdict.m、add_dc.m、remove_dc.m | 字典可视化与DC分量 |
| C加速 | private/.c、private/.mexw32 | 核心运算的mex实现 |
另外,压缩包里还有KSVD-OMP-v2.pdf和faq.txt。我的习惯是先把faq.txt通读一遍,里面有不少常见问题,比如“为什么demo跑出来结果和论文不一致”“编译失败怎么办”,作者都给了答复。论文则是你调参时的理论后盾,误差容限和噪声方差的关系、块大小的选择,论文里都有推导。
3.2 先跑通ksvddenoisedemo.m:最小可复现流程
打开MATLAB,cd到解压目录,如果private目录下的mex文件还没有编译过,先执行make.m重新编译,然后直接运行演示脚本。
% 切换到代码包目录 cd('你的解压路径/ksvd代码包'); % 编译全部mex文件(首次运行必需) make; % 运行K-SVD去噪演示 ksvddenoisedemo演示脚本内部大致做了几件事:读入peppers.png,转灰度后转成double,加标准差为20的高斯白噪声,把噪声图和sigma一起传给ksvddenoise,最后用showdict展示训练出的字典,并画出原图、噪声图、去噪图三张对比图。第一次跑通的意义在于验证环境:mex文件能编译、路径没配错、图能显示出来,后面改自己的数据才有基础。
如果运行报错,先看是不是mex加载失败,处理方法参考第5章。如果只是找不到函数,多半是当前目录没在MATLAB搜索路径里,用addpath把代码包目录加进去即可。
3.3 核心参数逐项拆解:字典大小、块大小、sigma与迭代轮数
demo跑通之后,把它换成自己的调用方式。去噪入口的常用形式是把噪声图、噪声方差、字典大小三个参数传进去:
% 自定义K-SVD去噪:噪声图、噪声方差、字典原子数 [clean, D] = ksvddenoise(noisy, sigma, K);noisy是double灰度图,sigma是真实噪声标准差,K是字典原子数。ksvddenoise内部还有一组可以通过额外参数覆盖的默认配置,最常见的是块大小和迭代轮数。块大小默认8×8,这个值是经过权衡的:块太小,原子表达不了纹理的结构;块太大,稀疏编码的维度变高,训练时间暴涨。迭代轮数默认10,对大多数自然图像够用,纹理复杂的图可以提到15到20。
| 参数 | 常见取值 | 作用 | 调参建议 |
|---|---|---|---|
| K字典原子数 | 128~512,默认256 | 字典列数,决定表达力 | 纹理多取256~512,训练慢是代价 |
| 块大小 | 8×8,偶尔12×12 | im2colstep的窗口尺寸 | 边缘锐利用8×8,纹理丰富用12×12 |
| sigma | 与真实噪声一致 | 决定OMP误差容限 | 从图像估出来的sigma比人为估计更稳 |
| 迭代轮数 | 10~20 | 字典训练轮数 | 前几轮字典变化大,后几轮微调 |
注意:sigma传错是K-SVD去噪最常见的不收敛原因,优先检查这里。
这里特别提醒:sigma是K-SVD去噪里最敏感的参数。你可以在调用前用噪声图像的中值绝对偏差估算sigma,常见做法是把噪声图做一次小波分解,用高频子带估计标准差;千万别拍脑袋填。填错sigma,后面的误差容限也跟着错,去噪结果会从一个极端翻到另一个极端。
3.4 换成自己的图片:读取、灰度化、去噪与保存
下面的代码把流程固定为:读图、转灰度、转double、加噪、去噪、保存。
% 读取自己的图像 im = imread('my_image.png'); if size(im, 3) == 3 im = rgb2gray(im); end im = double(im); % uint8不能直接做矩阵运算 % 加高斯白噪声,固定随机种子保证可复现 rng(42); sigma = 15; noisy = im + sigma * randn(size(im)); % K-SVD去噪 [clean, D] = ksvddenoise(noisy, sigma, 256); % 保存结果 imwrite(uint8(clean), 'my_image_denoised.png');解释几个容易踩的坑:第一,rgb2gray这步不能省,K-SVD这套实现只接受灰度图,彩色图直接往下传,im2colstep会把三维数组的维度搞乱;第二,double转换必须在加噪前做,因为uint8加法会取整,噪声分布就被破坏了;第三,rng(42)固定随机种子是调参的基本操作,没有它你每次跑出的噪声都不一样,没法对比不同参数的效果。
去噪完成后,建议同时输出一个客观指标。先算均方误差,再转PSNR,具体公式和代码放在第4章。如果PSNR比同条件下小波去噪还低,优先检查sigma是不是传错了,其次看字典训练是否收敛,而不是怀疑K-SVD本身。
4. 观察字典学习效果:原子演化与三种去噪方法对比
4.1 showdict.m可视化字典原子:从DCT初值到数据自适应
K-SVD训练前后的字典差异是理解这个算法最直观的窗口。初始字典由odctdict.m生成,是过完备的DCT基,所有原子都是规则的频率条纹。训练后字典里的原子会逐渐变成图像中真实出现的边缘、角点和纹理模式。这个变化不是刻意设计的,而是稀疏编码的副产品:因为训练样本反复使用某些结构,字典更新阶段就会把这些结构固化成新原子。
% 显示训练后的字典:128个原子,每个8×8 figure; showdict(D, [], 8, 8, 1, 'K-SVD训练后字典');showdict的入参依次是字典矩阵、图像取值范围、原子块的高和宽、排列方式、标题。8×8的原子会把64维列向量reshape回8×8小方块,然后按行排成大图展示。如果showdict报参数个数错误,常见原因是不同版本工具包的接口略有差异,直接用doc showdict看当前版本的参数说明即可。
判断字典学得好不好,我习惯先看视觉:训练后的字典如果还有大量规则条纹,说明数据自适应程度不高,要么迭代轮数太少,要么训练样本太少;如果原子看起来像噪声雪花,说明误差容限太松,字典过拟合了噪声;正常的字典应该能看到光滑的边缘和明确的纹理走向。
需要特别说明的是块大小和字典原子数之间有一个约束关系:字典原子的维度必须等于块展平后的长度,8×8的块对应64维原子,这是代码里im2colstep和数据矩阵D的行数自动决定的;原子数K可以大于维度,也可以小于维度。K大于维度时字典才是过完备的,稀疏编码有更多选择空间,去噪效果通常更好;K小于维度时字典欠完备,每个块能被表达的范围受限,结果会接近某种线性变换,K-SVD的优势就发挥不出来。对于8×8的块,K取256是常见配置;对12×12的块,K可以取256到512。这里要提醒的是:K不是越大越好,K翻倍,OMP每轮的字典搜索和SVD更新计算量都跟着涨,训练时间可能变长好几倍,而PSNR的收益往往只有零点几分贝。
4.2 PSNR与SSIM:去噪质量的两个量化指标
只靠肉眼判断去噪效果是不可靠的,同一张图在不同屏幕上的观感差别很大,所以行业内惯例是用PSNR这类全参考指标配合主观观察。PSNR基于MSE,对像素级误差敏感,计算方式如下:
% 计算PSNR,输入原图im和去噪图clean,都是double mse_val = mean((im(:) - clean(:)).^2); psnr_out = 10 * log10(255^2 / mse_val); fprintf('PSNR = %.2f dB\n', psnr_out);注意两个细节:MSE用的图像必须是同一尺寸、同一灰度范围;255是8位图像的最大像素值,如果你的图像是12位或16位,要把255换成实际最大值,否则算出来的PSNR虚高。除了PSNR,还可以算SSIM,它更符合人眼对结构相似性的判断,代码包里没有现成的SSIM函数,但MATLAB有ssim()可以用:
ssim_val = ssim(uint8(clean), uint8(im));PSNR和SSIM一起看:PSNR体现像素误差,SSIM体现结构保持。K-SVD训练好的字典在这两个指标上通常都能拿到不错的结果,但如果PSNR高而SSIM低,说明重建结果存在某种像素准确但结构失真的伪影,比如轻微块效应或振铃。
4.3 对比实验:均值滤波、高斯滤波、小波阈值与K-SVD
为了确认K-SVD的效果不是玄学,我通常会做一组对照实验。以lena图、sigma=20的高斯噪声为例,同一张噪声图分别走四条路:3×3均值滤波、5×5高斯滤波、小波硬阈值去噪、K-SVD去噪。
% 小波去噪对照:MATLAB内置的ddencmp+wdencmp [thr, sorh, keepapp] = ddencmp('den', 'wv', noisy); clean_wv = wdencmp('gbl', noisy, 'db4', 2, thr, sorh, keepapp);小波去噪的关键参数是阈值thr和分解层数,ddencmp会根据噪声自动给出阈值,但它是基于全局统计的,遇到纹理密集区域会连信号一起压掉。K-SVD的优势在低噪声水平下不明显,sigma超过30之后字典训练会明显变慢,但边缘保持仍然比固定小波基好。具体数据以你自己的实验为准,但通常的结论是:在8×8块、256原子、sigma=20的配置下,K-SVD的PSNR比同条件下高斯滤波高2~3dB,比小波阈值高1~2dB,并且主观上边缘更锐利。下表是这类实验的典型量级:
| 方法 | 典型PSNR(dB) | 主观弱点 |
|---|---|---|
| 3×3均值滤波 | 27左右 | 边缘发糊,细节丢失 |
| 5×5高斯滤波 | 28左右 | 纹理模糊,噪声残留 |
| 小波软阈值db4 | 28~29 | 边缘周围有伪影 |
| K-SVD | 30以上 | 平坦区域偶见轻微块效应 |
做这组实验还有一个实际用途:如果你的需求是“实时处理视频帧”,K-SVD每帧训练字典是吃不消的,但你可以用这一帧的结果检验“训练一次字典、后续帧只做OMP编码”的快速方案,第6章会展开。
5. K-SVD调试避坑:mex编译、内存暴涨与块效应
5.1 mexw32文件加载失败:现象、原因与重新编译
现象:运行ksvddenoise或执行OMP编码函数时,MATLAB报“Invalid MEX-file”或“找不到指定的模块”,更严重时直接崩掉会话。
原因:private目录下带mexw32后缀的文件是32位Windows下编译好的二进制,现在的MATLAB版本几乎全是64位,加载不了32位mex;即使都是64位,编译时用的编译器运行时库和当前机器不一致,也会加载失败。
解决:在代码包根目录运行make.m,让脚本用当前MATLAB自带的编译器重新编译所有.c文件。如果make.m报“No supported compiler”,先运行mex -setup选择一个可用的C编译器,Windows上推荐MinGW-w64。有一点容易忽略:编译完成后要把旧mexw32文件移除,否则MATLAB可能优先加载旧文件而不是新编译出的mexw64。从那以后我每次拿到新代码包,第一件事就是删掉旧mex、重新编译。
5.2 im2colstep内存溢出:全图块矩阵到底多大
现象:256×256的小图跑得很顺畅,换成512×512甚至更大尺寸,训练到一半内存占用飙升,MATLAB直接提示内存不足或者卡死。
原因:im2colstep把图像上每个8×8块展开成一列,步长为1时,512×512的图会产生约25万列,64×25万×8字节大约是1.3GB,这还没算字典矩阵和稀疏系数矩阵。块重叠越大、图像越大,中间矩阵膨胀得越夸张。
估算的公式是:块数约等于(H - blocksize + 1) × (W - blocksize + 1),步长为1时;中间矩阵的字节数约等于块数 × 块维度 × 8。以512×512、8×8块、步长1为例,块数约25.5万,矩阵约1.3GB。步长改成2,块数降到约6.4万,内存直接掉到320MB左右。所以遇到大图,先做这个估算再决定要不要切块。
提示:这个估算公式可以复用到其他块大小,把64换成块维度即可。
解决:常见做法是增大分块步长,比如步长从1改成2或4,块数减到四分之一,内存压力骤减;或者把大图切成上下两半,分块训练和去噪后再拼接。更极端的情况下,把图像转成single精度,内存直接减半。我的习惯是先用256×256小图验证参数,再根据上面那个估算公式推算大图的峰值内存,不自量力地直接上大图。
5.3 棋盘格块效应:countcover聚合权重与DC分量
现象:去噪后的图像能看到清晰边缘,但整体浮现一层细密的棋盘格或马赛克纹理,平坦区域尤其明显。
原因:每个图像块独立做稀疏编码,相邻块的系数必然有差异,重建回图时如果每个像素只是被覆盖块简单平均,块边界就会暴露成格子。正确做法是用countcover统计每个像素被多少个块覆盖,再按覆盖数加权平均。
解决:检查重建路径是否调用了countcover生成权重矩阵,并确保countcover和col2imstep的块大小、步长参数完全一致。还有一个和它并列的常见坑:remove_dc之后如果用系数乘字典直接重建,忘了把DC分量加回来,每个块的亮度不连续,看起来同样像块状。排查时把重建过程中的中间变量画出来,看到“亮度断崖”就是DC问题,看到“细密格子”就是聚合权重问题。
5.4 字典原子全是噪声:误差容限与稀疏度关系
现象:训练结束showdict一看,原子不像边缘纹理,更像电视雪花,一个清晰的结构都没有。
原因:误差容限设置太松,每个块被允许使用太多原子,字典学到的是噪声的伪结构;或者训练样本的信噪比本身太低,噪声淹没了信号结构。
解决:先把sigma传准,K-SVD按sigma自动推算误差容限;如果sigma准确还这样,就手动把误差容限系数调低,比如从1.0降到0.5。判断标准很简单:训练后的字典应当是有结构的,边缘、角点、明暗过渡都应该能被认出来。全雪花基本等于模型在拟合噪声,这时候降误差容限、降迭代轮数、或减少训练样本,三个方向都试试。
5.5 PSNR震荡不收敛:迭代轮数与OMP最大系数
现象:训练迭代轮数设得很大,结果PSNR不升反降,或者每轮的目标函数值忽高忽低,没有稳定下降趋势。
原因:迭代过多导致字典过拟合噪声本身,K-SVD在训练数据上收敛得很好,但对噪声图泛化变差;另一种情况是OMP编码时最大系数个数设置过大,残差已经不降了,编码还在空转,白白消耗算力。
解决:给OMP设置合理的最大系数个数,比如64维的块最多用8到16个系数,既能表达结构,又不会让噪声进模型。同时打印每轮近似误差,连续3轮变化小于阈值就提前停止。我跑K-SVD的经验是10到15轮足够,超过20轮在大多数自然图像上边际收益很小,反而可能让去噪结果变差。这里的教训是:K-SVD训练字典是去噪的前提,但它不是越久越好,过拟合噪声是新手最常见的翻车点。
6. 进阶实操:把K-SVD去噪接进自己的图像处理管道
6.1 批量处理文件夹里的图像
真实项目遇到的不止一张图,而是一个文件夹几十张图。批量处理要注意三件事:固定随机种子保证每张图的噪声可复现;按文件名排好顺序方便对照;每张图单独保存结果。
files = dir(fullfile('data', '*.png')); files = files(~[files.isdir]); for i = 1:numel(files) im = imread(fullfile(files(i).folder, files(i).name)); if size(im, 3) == 3, im = rgb2gray(im); end im = double(im); noisy = im + sigma * randn(size(im)); [clean, ~] = ksvddenoise(noisy, sigma, 256); out_name = fullfile('out', ['clean_' files(i).name]); imwrite(uint8(clean), out_name); end这个循环有两个扩展点:一是如果图间噪声水平不同,可以把sigma改成按图估计,常见做法是用小波高频系数算标准差;二是如果训练字典很耗时,可以先在第一张图上训练好D,后面每张图只做OMP编码,不再重新训练字典,速度能快一个数量级。
6.2 彩色图像去噪:逐通道处理与字典复用
K-SVD这套实现面向灰度图,彩色图常见做法是把RGB三个通道拆开,分别去噪再合并。但三个通道的噪声是相关的,逐通道独立训练容易产生颜色伪影,所以更稳的做法是:只在亮度通道上训练一本字典,然后让每一个通道复用这本字典做OMP编码。RGB转YCbCr后,Y通道承载大部分结构信息,Cb和Cr通道细节少、噪声也少,用同一本字典编码完全够用,还省了训练时间。
% 彩色图像去噪:转YCbCr,Y通道训字典,三个通道复用 imc = imread('color.png'); imc_ycbcr = rgb2ycbcr(imc); im_y = double(imc_ycbcr(:, :, 1)); [clean_y, D] = ksvddenoise(im_y + sigma * randn(size(im_y)), sigma, 256); imc_ycbcr(:, :, 1) = uint8(clean_y); clean_c = ycbcr2rgb(imc_ycbcr);这里有个细节:Cb、Cr通道的去噪用同一本D,但每个通道还是要各自做OMP编码,因为它们的稀疏系数不同。合并后通常看不到明显色彩漂移,Y通道保护了大部分边缘结构,有色噪声被分层隔离。
6.3 一个验证习惯:先小图后大图,先看字典再看PSNR
我总结的K-SVD调参流程是固定的三件事:第一,用256×256小图跑通,确认mex编译、块聚合、DC分量都正常;第二,showdict画字典,确认原子有结构,不是雪花也不是纯条纹;第三,算PSNR和SSIM,确认量化指标和肉眼观察一致,再上大图或批量数据。这个顺序帮我避开了绝大部分翻车场景。从那以后,我每次拿到新的K-SVD代码包,都会先跑一遍make.m编译mex,再用小图验证块效应,最后才上大图;这个看似保守的顺序,让我少熬了无数个debug的夜晚。希望帮到你。
本文还有配套的精品资源,点击获取