ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSIM图像质量评价Matlab实现:公式、代码与调参

2026/9/18 12:04:41 拓冰建站 浏览量
SSIM图像质量评价Matlab实现:公式、代码与调参 简介面向图像处理与计算机视觉方向的学习者与开发者这份文档围绕结构相似性SSIM图像质量评价指标展开适合已具备Matlab基础、希望理解算法内部实现细节的中级读者。资料为单个PDF文件约105KB内容以带详细注释的Matlab源代码为主体逐行说明函数ssim的输入输出、自动下采样流程以及均值、标准差、协方差的窗口计算方式并对照亮度、对比度、结构三项比较公式解释K、window、L等参数的含义与默认取值。读者可据此掌握mssim与ssim_map两个输出结果的用途理解如何用可视化映射定位图像失真区域并将其迁移到图像压缩、增强、视频编码传输质量评估等实验场景中。目前已有748人学习便于在较短篇幅内建立对SSIM评价体系的完整认识。1. 从一张压缩图的评分说起SSIM 在图像质量评价里到底测什么图像经过 JPEG 压缩、超分重建或者去噪之后怎么用一个数字说清看起来还行是图像质量评价绕不开的问题。MSE 和 PSNR 算得快却经常出现两张图 PSNR 相近、肉眼观感差很远的情况原因是它们逐像素做差对结构、纹理、边缘的退化不敏感。SSIM 换了个角度不比较单个像素的差值而是比较局部窗口内的亮度、对比度和结构三组统计量把人眼更关注结构信息这个先验写进公式。这个标题指向的 Matlab 源代码就是把 SSIM 公式从论文落到矩阵运算上的一版实现配上详细注释适合做 matlab 图像处理大作业、超分模型的评价脚本或者单纯想搞懂 SSIM 内部每一步在算什么的人。下面按公式来源、Matlab 逐段注释、参数怎么调、批量评估的顺序展开代码都能直接跑。2. SSIM 的数学骨架从亮度、对比度到结构项的公式拆解2.1 局部窗口里的均值、方差、协方差怎么定义SSIM 的一切都建立在一个局部窗口上。把参考图 x 和待评价图 y 在同一位置裁出大小相同的窗口窗口内像素数为 N就能算出三组统计量均值 μx、μy 描述窗口的平均亮度方差 σx²、σy² 描述窗口内像素的离散程度也就是对比度协方差 σxy 描述两个窗口的像素一起偏离各自均值的趋势也就是结构相关性。这三组量不需要遍历像素逐个比较而是先做一次统计汇总因此对局部噪声和小幅平移没那么敏感。标准定义里方差和协方差用无偏估计分母取 N-1但在用滤波实现的版本里通常直接用 N也就是不做 N/(N-1) 的修正。窗口内 N 一般有几十到上百个像素这点差异可以忽略而且省掉一次缩放。这是读 Matlab 源代码时容易困惑的地方公式里写 N-1代码里看不到除以 N-1原因就在这里。2.2 亮度、对比度、结构三个比较项的构造有了统计量就可以构造三个比较项每一项都落在 -1 到 1 之间亮度比较: l(x,y) (2·μx·μy C1) / (μx² μy² C1) 对比度比较: c(x,y) (2·σx·σy C2) / (σx² σy² C2) 结构比较: s(x,y) (σxy C3) / (σx·σy C3)亮度项在 μx μy 时取 1对比度项在两个窗口方差相同且非零时取 1结构项在 y 与 x 线性同向时接近 1反向时接近 -1。三者相乘就是单窗口的 SSIMSSIM(x,y) l(x,y) · c(x,y) · s(x,y)论文里取 C3 C2/2此时对比度项和结构项可以合并成一项得到常见的简化形式SSIM(x,y) [(2·μx·μy C1)(2·σxy C2)] / [(μx² μy² C1)(σx² σy² C2)]Matlab 里绝大多数实现都直接用这个合并形式一次矩阵乘除就能出结果不需要分别算三项再相乘。2.3 C1、C2 与动态范围 L 的取值C1 (K1·L)²C2 (K2·L)²K1 取 0.01K2 取 0.03L 是像素动态范围。8 位灰度图 L 255于是 C1 ≈ 6.50C2 ≈ 58.52。这两个常数的唯一作用是防止分母为零或接近零。图像里的平坦区域比如纯色背景、天空方差很小σx² σy² 趋近 0如果没有 C2结构项会被噪声放大成 0/0 或正负跳变亮度项同理。加上常数之后平坦区域的 SSIM 会被温和地拉向 1符合人眼看不出差别的平坦区域相似度应该高的直觉。需要注意C1、C2 也带来一个副作用图像整体对比度很低时SSIM 对真实的亮度偏差不够敏感。这就是为什么归一化到 0~1 的图如果再按 L 255 去算分数会明显偏高。符号含义论文默认值μx、μy局部均值对应亮度随窗口计算σx²、σy²局部方差对应对比度随窗口计算σxy局部协方差对应结构随窗口计算K1亮度稳定系数0.01K2对比度/结构稳定系数0.03L像素动态范围2558bitC1(K1·L)²6.5025C2(K2·L)²58.5225窗口局部窗口形状11×11 高斯σ1.52.4 从单窗口 SSIM 到整图 MSSIM单个窗口只给一个局部分数。要评价整幅图做法是让窗口逐像素滑动每个位置算一个 SSIM得到一张和原图近似同尺寸的 SSIM 映射图最后对映射图取平均就是 MSSIMMean SSIM。映射图本身也很有用颜色越暗的地方代表局部退化越严重做超分、去噪的对比实验时这张图比一个孤立的数字更能说明问题。选择高斯窗而不是全 1 方窗是因为高斯窗对窗口中心像素加权更高边缘权重趋近 0滑窗时不会因为窗口边界跨越强边缘而产生周期性跳变。Matlab 图像处理里用fspecial(gaussian, 11, 1.5)生成这个窗再用filter2一次卷积完成所有位置的加权求和比双重 for 循环快几个数量级这也是这套源代码值得逐行看的原因它把公式翻译成了向量化运算。3. Matlab 源代码逐段注释从灰度化到 SSIM 映射图3.1 输入预处理灰度转换、数据类型与归一化原始图像读进来是 uint8直接做减法和平方会溢出回绕比如 10 - 20 在 uint8 下会变成 246SSIM 直接算错。所以第一步永远是转 double。彩色图还要先转灰度避免三个通道分别比较带来的语义混乱。% 读取参考图和待评价图 ref imread(lena_ref.png); test imread(lena_jpeg70.png); % 彩色图先转灰度rgb2gray 按亮度加权比三通道取平均更符合人眼 if ndims(ref) 3 ref rgb2gray(ref); end if ndims(test) 3 test rgb2gray(test); end % uint8 直接做减法和平方会溢出回绕必须先转 double ref double(ref); test double(test); % 尺寸不一致直接报错比后面维度不匹配的报错更早暴露问题 assert(isequal(size(ref), size(test)), 两幅图像尺寸必须一致);ndims判断维度rgb2gray内部按 R:G:B ≈ 0.299:0.587:0.114 加权。assert放在最前面是个习惯SSIM 的输入必须严格同尺寸否则后面filter2出来会报维度错误报错信息离真正的原因很远。3.2 用 fspecial 和 filter2 算局部统计量% 生成 11x11 高斯窗sigma 取 1.5与论文一致 window fspecial(gaussian, 11, 1.5); window window / sum(window(:)); % 归一化保证是加权平均而不是加权求和 % filter2 的 valid 模式只保留窗口完全覆盖的区域避免边界补零污染 mu1 filter2(window, ref, valid); mu2 filter2(window, test, valid); mu1_sq mu1 .* mu1; mu2_sq mu2 .* mu2; mu1_mu2 mu1 .* mu2; % 方差用 E[x^2] - (E[x])^2 计算省去再开一次滑动窗 sigma1_sq filter2(window, ref .* ref, valid) - mu1_sq; sigma2_sq filter2(window, test .* test, valid) - mu2_sq; sigma12 filter2(window, ref .* test, valid) - mu1_mu2;fspecial返回的窗没有归一化直接和图像做相关会让结果整体偏大先除以sum(window(:))才是加权平均。filter2的valid模式让输出尺寸变成 (M-10)×(N-10)不补零边界那几个像素被丢弃可接受。方差用E[x²] - (E[x])²是数学上等价的快捷写法只需三次滤波而不是五次。3.3 逐项合并计算 SSIM 映射与最终得分% 稳定常数 K1 0.01; K2 0.03; L 255; C1 (K1 * L)^2; C2 (K2 * L)^2; % 分子分母都是逐元素运算一次算完整张映射图 ssim_map ((2 * mu1_mu2 C1) .* (2 * sigma12 C2)) ./ ... ((mu1_sq mu2_sq C1) .* (sigma1_sq sigma2_sq C2)); % 映射图取平均得到整图 MSSIM( : ) 把矩阵拉成列向量 mssim mean(ssim_map(:)); fprintf(MSSIM %.4f\n, mssim);这段是整份源代码的核心。.*和./是逐元素乘除不是矩阵乘除写错成*会直接报维度错误或算出完全错误的结果。映射图里偶尔会出现极小的负值这是因为用E[x²] - (E[x])²计算方差时存在浮点舍入误差理论上方差非负实际可能算出 -1e-15 量级代入分母后让 SSIM 略微超出 [-1,1]。一般直接忽略严格一点可以ssim_map max(min(ssim_map,1),-1)截断。3.4 完整可运行脚本与参数对照把上面几段拼成函数再写一个 demo就能跑通全流程function [mssim, ssim_map] ssim_index(img1, img2, K, window, L) % SSIM_INDEX 计算两幅图像的结构相似度 if nargin 3 || isempty(K), K [0.01 0.03]; end if nargin 4 || isempty(window), window fspecial(gaussian,11,1.5); end if nargin 5 || isempty(L), L 255; end img1 double(img1); img2 double(img2); C1 (K(1)*L)^2; C2 (K(2)*L)^2; window window / sum(window(:)); % 归一化 mu1 filter2(window, img1, valid); mu2 filter2(window, img2, valid); sigma1_sq filter2(window, img1.*img1, valid) - mu1.*mu1; sigma2_sq filter2(window, img2.*img2, valid) - mu2.*mu2; sigma12 filter2(window, img1.*img2, valid) - mu1.*mu2; ssim_map ((2*mu1.*mu2 C1).*(2*sigma12 C2)) ./ ... ((mu1.*mu1 mu2.*mu2 C1).*(sigma1_sq sigma2_sq C2)); mssim mean(ssim_map(:)); end调用和可视化ref rgb2gray(imread(lena_ref.png)); test rgb2gray(imread(lena_jpeg70.png)); [mssim, ssim_map] ssim_index(ref, test); figure; subplot(1,3,1); imshow(ref); title(参考图); subplot(1,3,2); imshow(test); title(待评价图); subplot(1,3,3); imagesc(ssim_map); axis image off; colormap(jet); colorbar; title(SSIM 映射图);函数输入参数对照参数类型默认值作用img1double 矩阵无参考图建议先转灰度img2double 矩阵无待评价图尺寸须与 img1 一致K1×2 向量[0.01 0.03]对应 K1、K2window矩阵11×11 高斯局部窗口需归一化L标量255像素动态范围归一化图填 1mssim标量输出整图平均 SSIMssim_map矩阵输出逐像素 SSIM 映射imagesc配colormap(jet)是 matlab 画图里看映射图的常规组合暖色代表高相似冷色代表退化严重比imshow直接显示灰度更容易分辨差异区域。4. 复现与调参窗口、常数和彩色图像的取舍4.1 11×11 高斯窗和 8×8 方窗到底差多少标准 SSIM 用 11×11 高斯窗、σ1.5目的是让窗口覆盖足够多像素来稳定估计方差同时不至于跨过太多结构边界。有些早期实现改用 8×8 全 1 方窗好处是计算更简单、边界损失小坏处是方窗对窗口边缘和中心一视同仁遇到强边缘时容易产生块状伪影映射图看起来一格一格的。实际对比一下win_gauss fspecial(gaussian, 11, 1.5); win_box ones(8, 8) / 64; s_gauss ssim_index(ref, test, [], win_gauss, 255); s_box ssim_index(ref, test, [], win_box, 255); fprintf(高斯窗: %.4f 方窗: %.4f\n, s_gauss, s_box);一般高斯窗的分数会略低一点因为它给中心像素更高权重对局部细节退化更敏感。做论文复现或者要在不同实现之间对齐分数时窗口必须写清楚否则同一对图报出 0.92 和 0.95 都正常这跟 matlab 优化工具箱里换求解器一样参数一变结果就变。4.2 K1、K2、L 改动的影响C1、C2 直接由 K1、K2、L 决定改动这三个值等于改了 SSIM 的灵敏度曲线。参数常见取值调大后的效果建议K10.01亮度和项变松低对比度区域分数偏高一般不动K20.03结构项变松纹理区域分数偏高噪声大时可略调L2558bit常数整体变大分数被拉向 1与位深匹配L1.0归一化常数极小分数对噪声敏感输入已归一化时用一个高频错误是把已经归一化到 0~1 的图当成 8bit 图仍用 L 255。这样 C2 会大 65025 倍平坦区域的结构项直接变成接近 1SSIM 虚高到 0.99 以上。判断方法很简单如果图里像素最大值不超过 1L 就该取 1。深度学习里做 matlab 图像处理、拿 SSIM 当损失函数时尤其要注意这点损失尺度错了梯度也会跟着错。4.3 彩色图像的三种处理方式彩色图不能直接塞进这套代码filter2对三维矩阵的行为和预期不一致。常见做法有三种一是转灰度后算简单、和多数论文口径一致二是逐通道分别算 SSIM 再取平均三是转到 YCbCr只在亮度通道 Y 上算因为人眼对色度退化不敏感。三种方式代码分别是% 方式一转灰度 g1 rgb2gray(img1); g2 rgb2gray(img2); s1 ssim_index(g1, g2); % 方式二逐通道平均 s_ch zeros(1,3); for c 1:3 s_ch(c) ssim_index(img1(:,:,c), img2(:,:,c)); end s2 mean(s_ch); % 方式三只在 Y 通道算 y1 rgb2ycbcr(img1); y2 rgb2ycbcr(img2); s3 ssim_index(y1(:,:,1), y2(:,:,1));三种口径报出的数常常差 0.01~0.03跨论文比较之前先确认对方用的是哪种。做去噪、超分的对比表时建议三行都列出来避免被质疑口径不一致。4.4 常见报错与数值异常排查报错Matrix dimensions must agree两图尺寸不同先assert或imresize对齐。分数总是 0.99 以上多半是 L 设错或者把同一张图和自己比了检查输入路径。分数为负或超过 1浮点舍入导致可对映射图做截断。映射图边缘一圈异常valid模式已裁掉若仍异常检查是否用了same。全黑或全白图上分数失真平坦区域方差接近 0属 SSIM 固有局限换 MS-SSIM 或 GMSD 更合适。5. 把 SSIM 用起来批量评估与进阶替代指标5.1 批量评估一个目录下的重建结果单张对比跑通后实际项目里往往是几百张验证图。写个循环把目录里的图两两配对输出到 CSV方便后续在 Excel 或 Python 里画图。这套模板在大规模 matlab 图像处理任务里很常见ref_dir fullfile(pwd, val, GT); test_dir fullfile(pwd, val, SR); files dir(fullfile(ref_dir, *.png)); results zeros(numel(files), 1); for i 1:numel(files) r rgb2gray(imread(fullfile(ref_dir, files(i).name))); t rgb2gray(imread(fullfile(test_dir, files(i).name))); results(i) ssim_index(r, t); end writematrix([ (1:numel(files)), results ], ssim_scores.csv); fprintf(均值 %.4f 中位数 %.4f 最小值 %.4f\n, ... mean(results), median(results), min(results));只报均值容易掩盖个别崩坏的样本同时看min和median能快速定位到哪几张图出问题。中位数比均值高很多时说明少数样本严重拉低了整体回去查那几张的映射图通常能发现问题。5.2 SSIM 的边界与 MS-SSIM、GMSD 等替代SSIM 假设图像是平稳的在单一尺度上比较对模糊、压缩伪影敏感对几何位移和旋转不够稳。同一张图轻微平移 1 像素SSIM 可能掉一大截而人眼几乎看不出差别。进阶替代指标里MS-SSIM 把图像按 2 倍下采样分成 5 个尺度各尺度加权相乘更贴近人眼看图时既看全局轮廓又看局部纹理的习惯GMSD 换用梯度幅值做比较对模糊类退化更敏感FSIM 引入相位一致性对结构性失真更稳。这些指标在 Matlab 里都能找到开源实现但计算量比 SSIM 大一截批量评估时要注意耗时。实际工作中更稳的做法是同时报 PSNR、SSIM、LPIPS 三个数一旦三者趋势不一致比如 PSNR 提升但 SSIM 下降基本能判断是模型在往纹理平滑的方向走而不是真的恢复出了更多细节。本文还有配套的精品资源点击获取