
简介基于Matlab的图像增强算法对比程序包涵盖单尺度RetinexSSR、多尺度RetinexMSR、带色彩恢复的MSRCR及MSRCP四种经典增强策略面向图像处理初学者、算法研究人员以及课程设计开发者方便在统一平台上对比不同方法对低照度、雾霾、背光等退化图像的增强效果。程序采用GUI交互设计将测试图片导入后即可自动输出增强结果建议在Matlab 2021a及以上版本运行便于快速验证和改进算法。压缩包共含15个文件以Matlab源码.m、GUI界面文件.fig、jpg/png测试图集和mp4操作演示为主整体大小为4.95MB小巧易用适合快速搭建实验环境。测试图像覆盖室内、风景、人像、建筑、自然景观等常见场景演示视频可帮助用户快速熟悉运行流程与界面操作。目前已有1331人学习体验通过阅读代码、调整参数并观察输出结果能够直观理解各算法在亮度提升、色彩保持和伪影控制等方面的优缺点为后续针对特定场景做二次开发或算法融合提供可扩展的参考实现。1. 为什么要把 SSR、MSR、MSRCR、MSRCP 放一起比做图像处理大作业或者实际项目时最烦的不是看不懂 Retinex 原理而是同一张图在不同博客上跑的 SSR 结果完全不一样。有的代码把输出直接线性拉伸有的代码在 log 域里加 1有的甚至把高斯窗口写死成固定大小。参数不统一代码接口不一致得出的“对比结论”其实是在对比实现方式而不是在对比算法本身。所以我整理了一套基于 MATLAB 的图像增强算法对比程序把 SSR、MSR、MSRCR、MSRCP 这四种常见的 Retinex 变体放进同样的调用框架里统一输入、统一参数、统一显示再用信息熵、均方根对比度等指标量化结果。这样做出来的对比才有说服力也方便往自己项目里移植。2. 从 Retinex 到 MSRCP四种图像增强算法的原理和差异2.1 先理解 Retinex 假设照度分量和反射分量Retinex 理论把人眼看到的图像S拆成照度分量L和反射分量R的乘积S L * R照度L决定图像的动态范围反射R决定物体本身的颜色和细节。增强的目标是压低L的影响、保留或突出R。因为乘法在 log 域里会变成加法所以几乎所有 Retinex 实现第一步都是对图像做log变换然后把问题变成“如何用低频信息估计照度”。L的估计方式直接决定了后续算法的复杂度、光晕程度和颜色表现。在 MATLAB 里估计L最常用的方式是高斯滤波。高斯核的sigma决定了“照度估计”的平滑范围小sigma只保留很局部的照度变化大sigma会让照度估计更接近全局亮度分布。这也是后面SSR单尺度和MSR多尺度最本质的区别。理解了这一点再看四种算法的公式就不会觉得它们只是参数不同了。算法照度估计方式颜色处理典型场景SSR单个高斯核逐通道独立处理快速预览、局部细节增强MSR多个高斯核加权平均逐通道独立处理颜色容易发灰对亮度分布不均匀的图像增强MSRCR多个高斯核加权平均加入颜色恢复因子低照度彩色照片、雾天图像MSRCP多个高斯核加权平均基于亮度通道做颜色比例映射对颜色保真度要求更高的场景这四种算法在 MATLAB 里都能用少量代码实现关键在于把核尺寸、尺度数、颜色恢复参数拆成显式参数而不是写死在代码里。2.2 SSR、MSR、MSRCR 的数学表示SSR 的输出形式对每个颜色通道独立计算R_ssr log(S) - log(G_sigma * S)其中G_sigma * S表示用标准差为sigma的高斯核对原图卷积。G_sigma * S相当于局部平均亮度也就是照度估计。小尺度高斯核会让 SSR 增强更多高频细节但平坦区域容易出现光晕大尺度核则更接近整体亮度补偿但对比度提升有限。单独用sigma 80是一个比较稳定的起点但实际要按图像分辨率缩放。MSR 在 SSR 的基础上做了多尺度加权平均R_msr sum_i w_i * [ log(S) - log(G_sigma_i * S) ]通常取sigma为15、80、250权重w_i 1/3。小尺度提取边缘中尺度平衡大尺度负责整体色彩自然度。MSR 的问题在于每个通道独立处理输出会明显向灰色靠近图像的饱和度下降。MSRCR 在 MSR 后面串了一个颜色恢复因子C_i beta * log( alpha * S_i / sum(S_j) ) R_msrcr_i G * ( R_msr_i * C_i b )常见的参考参数是alpha 125、beta 46、G 192、b 30。其中C_i本质上是当前通道在像素上的占比。如果某个通道占比很小C_i会是负值会抑制该通道占比大时C_i为正值把颜色拉回来。需要说明的是这几个参数不是物理推导出来的而是经验值换一张动态范围差别很大的图像时可能可以直接用但最优解往往需要自己微调。2.3 MSRCP 是怎么改的MSRCP 不是单纯的参数调整而是改变了处理链路。它先对灰度图或亮度图做一次 MSR得到亮度增强结果L_out再让原始 RGB 通道除以亮度的比例作为颜色权重把颜色乘回去Lum rgb2gray(S) L_out MSR(Lum) R_out S / Lum * L_out这样做的好处是颜色恢复不再依赖alpha和beta这两个敏感参数而且因为所有通道共用同一个亮度增强结果颜色偏移的概率比 MSRCR 低。代价是如果像素点亮度趋近 0S / Lum会被放大暗部噪点会被明显增强。所以代码里一般会对分母加eps或者对输出做裁剪。从工程实现的角度看MSRCP 更像是在“增强亮度”和“保持颜色比例”之间做了一个折中。对比程序中把它单独拿出来是因为它和 MSRCR 在实现复杂度、参数数量和视觉风格上差异很大适合放在一起看效果边界。2.4 四种算法适用范围总结如果图像只是偏暗没有明显色偏SSR 和 MSR 就够用。如果图像同时存在局部光照不均和颜色失真MSRCR 的恢复能力更强但参数调节成本高。如果对颜色保真度有要求比如皮肤色调、产品图颜色MSRCP 往往更稳妥。这四种算法在 MATLAB 里的运行速度差异不大真正影响速度的是高斯核大小和图像尺寸所以对比程序里可以统一用同一组尺度。3. 用 MATLAB 写一个可复用的 Retinex 增强函数3.1 统一入口函数为了对比我建议把四种算法封装成一个入口函数retinex_enhance用alg字符串选择具体算法。这样后续写对比脚本时只需要调用一个函数不会因为接口不同而反复改参数。function out retinex_enhance(I, alg, params) % I 可以是 uint8 或 double兼容灰度图和 RGB 图 % alg 支持 ssr、msr、msrcr、msrcp % params 是一个结构体保存当前算法需要的全部参数 if isa(I, uint8) I double(I) / 255; end if size(I, 3) 1 % 灰度图统一转成 3 通道方便后面分通道处理 I repmat(I, [1 1 3]); end switch lower(alg) case ssr out ssr(I, params.sigma); case msr out msr(I, params.scales); case msrcr out msrcr(I, params.scales, params.alpha, params.beta, params.G, params.b); case msrcp out msrcp(I, params.scales); otherwise error(未知算法: %s, alg); end end这段代码做了两件关键事情一是自动把uint8图像归一化到[0, 1]避免后续imfilter出现数值溢出二是灰度图主动复制成 3 通道避免某个算法分支因为通道数不一致报错。入口函数本身不依赖具体算法细节后续想加一个AMS或者SRW变体只要继续加case分支就行。3.2 高斯环绕核的生成所有 Retinex 算法都需要生成高斯核。MATLAB 里最直接的是fspecial(gaussian, hsize, sigma)但核大小不能随便写固定值。核太小会截断高斯函数的边缘导致照度估计不完整。我一般用下面这种方式自动计算function g gauss_kernel(sigma) % 核大小取 3 倍 sigma 的中心区域并强制为奇数 half ceil(3 * sigma); hsize 2 * half 1; g fspecial(gaussian, hsize, sigma); end这里3 * sigma覆盖了高斯核的主要能量区域超过这个范围的卷积权重已经非常小可以忽略。强制奇数是为了匹配imfilter的same选项避免输出尺寸偏移。sigma值越大生成的高斯核越大卷积计算量也越高。在对比程序里同一组scales会被四个算法反复使用可以提前算好核避免重复生成。3.3 SSR、MSR、MSRCR 的 MATLAB 实现SSR 的实现最简单核心就是一次高斯滤波和一次 log 域相减function R ssr(I, sigma) % 输入 I 是 double 型 RGB 图像范围 [0, 1] % 输出 R 是 log 域差分结果范围可能为负 [rows, cols, chs] size(I); R zeros(rows, cols, chs); g gauss_kernel(sigma); for c 1:chs logI log(eps I(:, :, c)); L imfilter(I(:, :, c), g, replicate, conv); logL log(eps L); R(:, :, c) logI - logL; end end注意eps的用法。log(0)在 MATLAB 里会得到-Inf图像中常见的纯黑像素会让结果变成 NaN 或者无穷大。加eps是一种最常见的防御性写法代价是暗部细节的增强幅度会被略微压低但对整体视觉效果影响很小。imfilter的replicate选项让边界外像素沿用边缘像素避免卷积在边界处产生黑边。conv表示使用卷积模式匹配高斯核的对称形式。MSR 的基础上只需要加一层加权和function R msr(I, scales) [rows, cols, chs] size(I); R zeros(rows, cols, chs); for c 1:chs logI log(eps I(:, :, c)); for k 1:length(scales) g gauss_kernel(scales(k)); L imfilter(I(:, :, c), g, replicate, conv); R(:, :, c) R(:, :, c) (logI - log(eps L)) / length(scales); end end end这里的权重用1 / length(scales)如果想单独调整某一个尺度可以改成显式权重向量weights。需要注意scales里的值不要差距过大比如[15, 80, 250]是一个比较常见的组合。如果第二个尺度远大于图像尺寸高斯核会覆盖整个图像导致该尺度退化为全局亮度调整失去了“多尺度”的意义。MSRCR 需要在 MSR 的基础上计算颜色恢复因子function R msrcr(I, scales, alpha, beta, G, b) R_msr msr(I, scales); sumI sum(I, 3); C zeros(size(I)); for c 1:size(I, 3) C(:, :, c) beta * (log(alpha * I(:, :, c) 1) - log(sumI 1)); end R G * (R_msr .* C b); end颜色恢复因子C中log(sumI 1)可以理解为所有通道总亮度的对数。如果某一像素的I(:,:,c)接近sumI意味着该通道在该像素占绝对优势C为正值增强作用保留如果该通道占比很低C可能是负值会压低这个通道的响应。这样就避免了 MSR 把所有颜色都推向灰色的现象。G和b本质上是输出缩放和偏移类似线性拉伸但我建议保留下来因为和后续直接归一化相比它保留了 log 域的相对强度关系。3.4 MSRCP 的快速实现MSRCP 的代码比 MSRCR 短很多function R msrcp(I, scales) % 先计算灰度图再对灰度图做 MSR Lum rgb2gray(I); L_msr msr(Lum, scales); % 把 log 域结果线性拉伸到 [0, 1]避免亮度反向 L_min min(L_msr(:)); L_max max(L_msr(:)); L_norm (L_msr - L_min) / (L_max - L_min eps); % 颜色通道按比例映射回增强结果 R zeros(size(I)); for c 1:3 ratio I(:, :, c) ./ (Lum eps); R(:, :, c) ratio .* L_norm; end % 防止增强后出现超过 [0, 1] 的像素 R max(0, min(1, R)); end这里的关键是对L_msr做了线性归一化。msr的输出是 log 域差分和它可能是负数直接和原图比例相乘会得到错误的结果。归一化后亮度增强图的分布被映射到[0, 1]再乘以颜色比例输出就是增强后的彩色图像。这个实现没有显式的颜色恢复参数但实际效果和 MSRCR 很接近尤其是在图像整体偏暗的场景里MSRCP 的色调更稳。为了满足后续对比程序的需要可以把这些函数放在同一个.m文件里或者做成一个retinex/文件夹加入 MATLAB 路径后直接调用。对比程序里不需要关心内部实现只要传入正确的params结构体即可。4. 对比程序的设计怎么让四个算法公平地跑起来4.1 统一参数定义和调用接口同一个算法在不同代码里表现不同很大程度是参数定义不一致造成的。对比程序里我建议先把所有参数集中定义再循环调用统一入口函数。这样跑完一组实验后回头改参数只需改一个地方。img imread(lowlight.png); params.ssr_sigma 80; params.msr_scales [15 80 250]; params.msrcr_scales [15 80 250]; params.msrcr_alpha 125; params.msrcr_beta 46; params.msrcr_G 192; params.msrcr_b 30; algos {ssr, msr, msrcr, msrcp}; results cell(1, 4); for i 1:4 p params; switch algos{i} case ssr p.sigma p.ssr_sigma; case msr p.scales p.msr_scales; case msrcr p.scales p.msrcr_scales; p.alpha p.msrcr_alpha; p.beta p.msrcr_beta; p.G p.msrcr_G; p.b p.msrcr_b; case msrcp p.scales p.msrcr_scales; end results{i} retinex_enhance(img, algos{i}, p); end注意这里p params;是浅拷贝修改p不会影响原来的params。在 MATLAB 里这是很常见的写法避免循环里参数相互污染。另外一个公平性原则是ssr只给一个尺度msr、msrcr、msrcp都用同一组msrcr_scales这样对比的差异主要来自算法本身而不是因为一个用了多尺度一个用了单尺度。4.2 处理灰度图和 RGB 图的差异如果输入是灰度图sum(I, 3)会出错因为灰度图没有第三个颜色通道。在入口函数里我把灰度图强制复制成了三层但这样会让灰度图的颜色恢复因子完全失效。更合理的做法是检测图像通道数灰度图只对比 SSR 和 MSR彩色图再对比全部四种。if size(img, 3) 1 algo_indices [1 2]; else algo_indices 1:4; end这么做不是偷懒而是让对比程序更符合图像增强的常规认知。灰度图像没有颜色信息MSRCR 和 MSRCP 的颜色恢复逻辑没有意义硬跑出来也只是得到相同的灰度结果反而干扰结论。4.3 用 subplot 拼出对比图显示结果时我建议把所有输出先做一次统一的最小最大拉伸避免某个算法的 log 域输出范围过大导致imshow自动归一化后对比度被压扁。可以写一个简单的显示函数function show_compare(img, results, algos) % 统一把输出线性拉伸到 [0, 255] 再显示 figure; subplot(2, 3, 1); imshow(img); title(原图); for i 1:length(results) r results{i}; r (r - min(r(:))) / (max(r(:)) - min(r(:)) eps); subplot(2, 3, i 1); imshow(r); title(upper(algos{i})); end end前半段生成结果后半段统一显示这就是整个对比程序的主干。注意这里imshow(r)已经是在[0, 1]范围内的 double 图像MATLAB 会正确渲染。如果直接给imshow传负值会造成裁剪看起来像一团黑这是新手最容易踩的坑。4.4 参数调整方向和默认取值参考算法关键参数默认值调参方向SSRsigma80sigma 增大整体亮度更均匀但细节对比度下降MSRscales[15 80 250]小尺度负责细节大尺度负责颜色自然度MSRCRalpha125alpha 变大颜色恢复因子对低通道的抑制更强MSRCRbeta46beta 控制颜色恢复强度过大容易出现色斑MSRCRG, b192, 30影响输出亮度范围一般不需要严格遵循论文取值MSRCPscales同 MSR和 MSR 共享尺度参数但输出对尺度变化更敏感这些默认值不是公式推出来的而是从论文和开源代码的经验积累中沉淀出来的适用大多数 500×500 到 2000×2000 的图像。如果你的图像分辨率极高比如 8000×6000 的航拍图建议把尺度按比例放大否则高斯核覆盖范围相对图像变小增强效果会弱很多。5. 用指标量化对比效果5.1 为什么需要信息熵和对比度指标人眼看结果会有很强的主观性。同一个输出反复切换显示器的亮度模式就能得出完全不同的结论。所以对比程序里必须加入客观指标。信息熵衡量的是图像灰度分布的丰富程度熵越高说明增强后灰度级越分散信息量越大。均方根对比度衡量的是像素偏离均值的程度对比度过低看起来灰蒙蒙过高则容易出现过曝和噪声放大。需要特别提醒这些函数都要求输入在[0, 255]范围或者已经被归一化到[0, 1]。如果拿 log 域差分结果直接计算熵会得到没意义的负值。所以指标计算前必须对输出做线性拉伸。5.2 信息熵和对比度的 MATLAB 代码function ent img_entropy(I) % I 是 double 型图像范围 [0, 255] I round(I); I(I 0) 0; I(I 255) 255; p imhist(uint8(I), 256); p p / sum(p); p(p 0) []; ent -sum(p .* log2(p)); end注意p(p 0) [];这一步不能省因为log2(0)会产生NaN最终熵值也会变成NaN。imhist返回的是每个灰度级的像素个数归一化后就是概率分布。信息熵的单位是 bit最大不会超过 8最小值接近 0。均方根对比度更简单function c rms_contrast(I) % 输出图像已经是 double 类型 c std(I(:)); endstd已经计算了所有像素的一阶标准差对于均值为 0 的图像这个值就是均方根对比度。实际图像均值不为 0但 MATLAB 里std默认做的是“去均值后的标准差”正好是均方根对比度定义里的平方根部分可以直接用。峰值信噪比需要有参考图像function p psnr_score(enhanced, original) % enhanced 和 original 都是 [0, 1] 的 double 图 mse_value mean((enhanced(:) - original(:)).^2); p 10 * log10(1 / (mse_value eps)); end这里1 / mse对应最大像素值为 1 的情况。PSNR 适合用来衡量增强结果与原图的偏差但图像增强不是图像恢复PSNR 高并不意味着视觉效果好。所以我的习惯是PSNR 作为辅助指标主要看熵和对比度同时结合颜色分布。5.3 指标结果怎么解读记录指标时建议按下面的表格格式整理指标原图SSRMSRMSRCRMSRCP信息熵待测待测待测待测待测RMS 对比度待测待测待测待测待测PSNR-待测待测待测待测实际结果里SSR 的熵通常提升最明显因为单尺度可以产生更多局部差异MSR 的熵略低于 SSR但整体灰度分布更平滑MSRCR 的熵和对比度取决于alpha和beta是否匹配调不好时可能比原图还低MSRCP 的熵一般居中但颜色还原最好。PSNR 的参考价值有限因为增强算法本质上是带方向性的图像变换而不是逼近原图。如果某个算法的熵明显低于其他算法先别急着下结论。检查一下输出是否被裁剪或者归一化失败。一个典型问题是在msrcr输出之后min(r(:))是负值线性拉伸到[0, 1]后暗部细节被强行压缩信息熵自然就偏低了。这时候可以在拉伸前先做一次轻微的直方图截断比如把 1% 的极端像素裁掉再计算指标。6. 把对比程序做成小工具的几条实用细节6.1 用函数保存结果图对比程序跑完后保存结果图时不要用saveas(fig, result.png)因为 MATLAB 保存出来的 PNG 会带白色背景和边框直接放进论文或报告里很难看。更好的做法是用imwrite把处理后的图像单独存下来imwrite(im2uint8(results{1}), ssr_result.png);im2uint8会把[0, 1]范围内的 double 图像自动映射到[0, 255]并转换为uint8。如果结果里有超出范围的值im2uint8会先裁剪再转换比手动mat2gray更可控。6.2 常见问题排查现象原因处理方式输出全黑或全白log 域结果未拉伸直接显示用min/max归一化后再imshow图像边缘出现明显黑边高斯滤波边界填充不当imfilter使用replicate黑色像素处出现 NaNlog(0)导致图像加eps后再 logMSRCR 颜色偏黄或偏青alpha和beta不匹配固定beta46调整alpha到 100~150MSRCP 噪点特别严重暗部亮度接近 0比例放大对Lum加eps或对输出做median滤波6.3 一个能明显提速的替换如果对比图像很大fspecial加imfilter的组合会非常慢。MATLAB 自带的imgaussfilt直接使用可分离高斯滤波在同样sigma下速度通常比imfilter快一倍以上。把ssr函数里的高斯滤波替换成L imgaussfilt(I(:, :, c), sigma, Padding, replicate);就可以把gauss_kernel那段整个删掉。Padding选项对应replicate的边界处理方式。不过注意imgaussfilt和fspecial对高斯核的截断策略不完全相同同一个sigma下的视觉效果有细微差异。在对比程序里建议四种算法统一用同一种滤波实现避免把实现的差别误当成算法的差别。本文还有配套的精品资源点击获取