基于MATLAB的Retinex图像增强:SSR/MSR/MSRCR/MSRCP
2026/9/17 17:47:22 网站建设 项目流程

简介:基于Matlab的图像增强算法对比程序包,涵盖单尺度Retinex(SSR)、多尺度Retinex(MSR)、带色彩恢复的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) ]

通常取sigma1580250,权重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 = 125beta = 46G = 192b = 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

这样做的好处是颜色恢复不再依赖alphabeta这两个敏感参数,而且因为所有通道共用同一个亮度增强结果,颜色偏移的概率比 MSRCR 低。代价是如果像素点亮度趋近 0,S / 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

颜色恢复因子Clog(sumI + 1)可以理解为所有通道总亮度的对数。如果某一像素的I(:,:,c)接近sumI,意味着该通道在该像素占绝对优势,C为正值,增强作用保留;如果该通道占比很低,C可能是负值,会压低这个通道的响应。这样就避免了 MSR 把所有颜色都推向灰色的现象。Gb本质上是输出缩放和偏移,类似线性拉伸,但我建议保留下来,因为和后续直接归一化相比,它保留了 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只给一个尺度,msrmsrcrmsrcp都用同一组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,最终熵值也会变成NaNimhist返回的是每个灰度级的像素个数,归一化后就是概率分布。信息熵的单位是 bit,最大不会超过 8,最小值接近 0。

均方根对比度更简单:

function c = rms_contrast(I) % 输出图像已经是 double 类型 c = std(I(:)); end

std已经计算了所有像素的一阶标准差,对于均值为 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 的熵和对比度取决于alphabeta是否匹配,调不好时可能比原图还低;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后再 log
MSRCR 颜色偏黄或偏青alphabeta不匹配固定beta=46,调整alpha到 100~150
MSRCP 噪点特别严重暗部亮度接近 0,比例放大Lumeps,或对输出做median滤波

6.3 一个能明显提速的替换

如果对比图像很大,fspecialimfilter的组合会非常慢。MATLAB 自带的imgaussfilt直接使用可分离高斯滤波,在同样sigma下速度通常比imfilter快一倍以上。把ssr函数里的高斯滤波替换成:

L = imgaussfilt(I(:, :, c), sigma, 'Padding', 'replicate');

就可以把gauss_kernel那段整个删掉。'Padding'选项对应'replicate'的边界处理方式。不过注意,imgaussfiltfspecial对高斯核的截断策略不完全相同,同一个sigma下的视觉效果有细微差异。在对比程序里,建议四种算法统一用同一种滤波实现,避免把实现的差别误当成算法的差别。

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

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

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

立即咨询