简介:面向图像增强研究的MATLAB对比程序,整合了SSR、MSR、MSRCR及MSRCP四种经典Retinex算法,并配有可视化GUI界面,适合高校学生、算法工程师以及图像处理爱好者进行算法效果对比、教学演示或项目选型。整个包共15个文件,包括2个.m源码文件、1个.fig界面文件、1个mp4操作演示视频以及11张jpg/png测试图片,压缩包大小4.95MB,结构紧凑可直接运行。用户只需在MATLAB 2021a及以上版本运行入口脚本,通过按钮选择TestImages中的任意图片,即可自动输出增强结果,免去手动调参的繁琐。多张不同场景的测试图像覆盖室内、室外、风景、人物等类型,便于观察算法在不同光照条件下的表现;操作演示视频也显著降低了入门门槛。目前已有1331人学习下载,适合希望快速上手Retinex系列算法对比和二次开发的MATLAB使用者。
1. 四个Retinex变体,一套对比代码,差异到底在哪
在matlab里做图像增强算法对比,最容易被问倒的不是怎么跑通SSR,而是SSR、MSR、MSRCR、MSRCP四个Retinex变体到底该比什么。它们共用同一个理论起点:把图像看成光照与反射的乘积,在对数域减掉低频光照,改动只发生在尺度数量、色彩恢复方式上,输出差异却极其明显。
对IT从业者来说,这套算法适合课程设计大作业、论文对比实验,也适合在给客户做算法选型预研的时候快速判断哪条路线值得深入。尤其是MSRCR和MSRCP,名字相近,实现路径完全不同,很多人跑完发现颜色不对,问题大多出在参数域而不是算法本身。
下面这套实现顺序,能让你用同一个输入图像、同一组sigma,把四个算法串起来,顺带搞清楚每个参数到底在控制什么。
2. SSR和MSR的实现:从Retinex公式到matlab里的对数域差分
2.1 Retinex核心公式与matlab里“取对数”的原因
Retinex把图像建模成入射光与物体反射的乘积,取对数后乘性关系变成加性关系,于是“分离反射率”就等价于对log图减掉一个估计的光照。光照在空间上变化缓慢,用一个高斯低通滤波器平滑后的图像可以当作光照估计。这个基础公式是:
log(R) = log(I) - log(L)其中I是原始图像,L是对I做高斯滤波得到的低频估计,R就是增强后的反射分量。matlab里实现这一步只需要imfilter加log,但有几个细节决定了输出质量。
2.1.1 高斯核半径与光照估计精度的关系
高斯滤波器的大小直接影响L的平滑程度。常见做法是把核窗口设为round(sigma * 6) + 1,因为高斯分布的主要能量集中在3倍标准差内,窗口太小会截断高斯形状,窗口太大会让边界处的滤波结果被无效区域污染。
边界处理必须用'replicate'而不是默认的'same'。'replicate'扩展边缘像素而不是补零,能避免增强后的图像四周出现一圈明显的黑边。这个细节在单张图上不容易察觉,一旦拿去做批量对比,四周边框会直接影响后续信息熵和标准差的统计结果。
2.2 用matlab复现SSR的最小可运行函数
function out = retinex_ssr(img, sigma) img = im2double(img); % 统一转double,保证log运算不溢出 h = fspecial('gaussian', round(sigma * 6) + 1, sigma); low = imfilter(img, h, 'replicate'); log_img = log(max(img, 1e-6)); log_low = log(max(low, 1e-6)); diff = log_img - log_low; out = diff - min(diff(:)); out = out / max(out(:)); % 拉伸到[0,1]用于显示 end这里对img取max(…, 1e-6)是为了防止纯黑像素取对数产生无穷小,这是图像增强代码最常见的崩溃点。log之后的结果可能是负值,直接imshow会显示一团黑,所以需要先减最小值再除以最大值。
sigma参数控制增强尺度。单尺度SSR一般取sigma = 80,能兼顾局部细节和整体动态范围;如果sigma太小,比如10以下,高频细节被保留但亮部会出现明显的光晕伪影,暗部噪声也被一起放大。如果只是快速验证效果,SSR适合拿来做基线,不适合作为最终输出。
2.3 把SSR升级成多尺度MSR:三组sigma均衡亮暗
MSR的思路是对同一张图用多个sigma分别做SSR,再按权重叠加。这样小尺度保留细节,中尺度兼顾局部对比,大尺度压缩动态范围,比单尺度更稳定。经典参数是三尺度等权:
| 尺度 | sigma | 作用 |
|---|---|---|
| 小尺度 | 15 | 保持边缘和纹理细节 |
| 中尺度 | 80 | 控制局部对比度 |
| 大尺度 | 250 | 压缩亮度范围,避免高光溢出 |
实现时可以封装一个返回对数域原始值的核心函数,方便后面MSRCR和MSRCP复用:
function diff = retinex_msr_log(img, sigmas, weights) img = im2double(img); diff = zeros(size(img)); for k = 1:numel(sigmas) h = fspecial('gaussian', round(sigmas(k) * 6) + 1, sigmas(k)); low = imfilter(img, h, 'replicate'); log_img = log(max(img, 1e-6)); log_low = log(max(low, 1e-6)); diff = diff + weights(k) * (log_img - log_low); end end这个函数对彩色图像是逐通道处理的,保留了对数域的原始差值,不做归一化。显示前再拉一次:
function out = retinex_msr(img, sigmas, weights) diff = retinex_msr_log(img, sigmas, weights); out = diff - min(diff(:)); out = out / max(out(:)); end权重数组理论求和应为1,默认取[1 1 1]/3。如果想让暗部更亮,可以加大尺度150或250的权重,比如[0.2 0.3 0.5];如果想让细节更锐利,加大尺度15的权重。MSR的最大问题是输出偏灰,因为每个通道独立拉伸后,通道间的比例关系被破坏了,这就是下一章要解决的色彩偏色问题。
3. MSRCR的色彩恢复:C_i怎么算、怎么调不偏色
3.1 为什么MSR输出总是灰蒙蒙的
MSR对RGB三个通道分别做对数差分,最后又各自独立归一化。这一步相当于把每个通道的均值都拉到一个水平线上,通道之间的原始比例被消除,结果的饱和度显著下降,整体看起来像蒙了一层灰。
要恢复颜色,需要在MSR的输出上乘一个色彩恢复因子,把通道间的差异重新拉出来。MSRCR的名字里多出来的C,指的就是这个色彩恢复过程。它不是一个可选的后期滤镜,而是直接乘在log域差值上,位置很关键。
3.2 用matlab实现MSRCR的色彩恢复因子
色彩恢复因子的常见形式是对每个像素计算:
c = beta * log(alpha * img255 + 1) - log(sumc + 1);再把这个因子乘到MSR结果上,做整体增益和偏移:
function out = retinex_msrcr(img, sigmas, weights, alpha, beta, G, b) img = im2double(img); msr = retinex_msr_log(img, sigmas, weights); img255 = img * 255; % 颜色恢复公式默认输入域是[0,255] sumc = sum(img255, 3); c = beta * log(alpha * img255 + 1) - log(sumc + 1); out = G * c .* msr + b; out = min(max(out, 0), 255); % 截断域外值,而不是再归一化 out = out / 255; end调用参数取经典组合:
r_msrcr = retinex_msrcr(img, [15 80 250], [1 1 1]/3, 125, 46, 192, -30);其中G = 192是增益,b = -30是偏移。alpha = 125控制色彩恢复的敏感度,beta = 46控制恢复强度。这组参数来自论文里的经验值,对大多数自然图像效果都稳定。
3.2.1 参数域不一致是最常见的偏色原因
MSR本身不受输入域影响,因为log差会把常数缩放抵消掉。但MSRCR的c因子不一样,它包含log(alpha * img255 + 1)和log(sumc + 1),这两个真数对图像的数值范围极其敏感。
如果先对img做了im2double变成[0,1]区间,却仍然用alpha = 125,相当于把缩放因子缩小了255倍,色彩恢复就会失效,输出偏灰甚至偏绿。反过来,如果在[0,255]域把alpha设成0.5,色彩恢复强度会被放大到几乎改变色调的程度。所以写对比程序时,一定要统一约定:要么全程用[0,255]域,要么用img * 255回到原域再算c。
3.3 MSRCR最容易碰到的三个异常现象
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 输出偏灰,色彩恢复无效 | alpha/beta与输入域不匹配 | 检查是否im2double后没有乘255 |
| 高光区域过曝,细节丢失 | G过大或b负数过多 | 把G降到96到128之间,b改为-10到0 |
| 暗部出现彩色斑点 | 色彩恢复因子对噪声敏感 | 先做3x3中值滤波再算c |
第一个异常最常见,排查时先打印img255的最大值和c的均值。正常情况下c应该在一个窄范围内波动,比如0.8到1.2之间,如果出现-5或10这种数量级,一定是参数域写错了。第二个异常需要根据直方图观察,如果增强后的图像80%像素都聚集在250以上,就减少G或者b向正方向调整。
4. MSRCP的色度保持思路和两种matlab实现
4.1 共享比例:MSRCP与MSRCR的本质区别
MSRCR用色彩恢复因子校正每个通道,MSRCP走的是另一条路:先求亮度图,在亮度图上做MSR,得到新亮度后,计算出每个像素的亮度变化比例,再把这个比例同时乘到RGB三个通道上。
这样做的好处是通道间的比例始终不变,色调保持得很自然。实现时核心就一条线:
function out = retinex_msrcp(img, sigmas, weights) img = im2double(img); gray = rgb2gray(img); % 加权亮度,也可用三通道均值 diff = retinex_msr_log(gray, sigmas, weights); m = diff - min(diff(:)); m = m / max(m(:)); % 新亮度归一化到[0,1] ratio = m ./ max(gray, 1e-6); % 逐像素亮度变化比例 ratio = min(ratio, 3); % 限制比例,防止暗部噪声爆炸 out = img .* ratio; % RGB三通道共享同一个比例 out = min(max(out, 0), 1); endratio大于1的地方变亮,小于1的地方变暗。因为三通道乘的是同一个值,所以只有亮度改变,色调不变。ratio限制在3以内是经验值,如果场景暗部本身有细节,放大到4或5也不会差太多,但一旦超过5,暗部噪声会被明显染色。
4.2 在HSV空间实现的另一个版本
有些场景下,用rgb2gray作为亮度图会让红色和蓝色通道的比例偏移,因为加权灰度对绿色更友好。另一个常见做法是转到HSV空间,只替换V通道:
hsv = rgb2hsv(img); v = hsv(:, :, 3); diff = retinex_msr_log(v, sigmas, weights); v2 = diff - min(diff(:)); v2 = v2 / max(v2(:)); hsv(:, :, 3) = v2; out = hsv2rgb(hsv);这种写法直接把原V通道替换成MSR结果,而不是按比例乘回去,所以高饱和区域的色彩表现更稳定,但暗部噪声也会被一起放大。
4.2.1 RGB乘比例和HSV替换V的差异
| 实现方式 | 色调保持方式 | 高光表现 | 暗部噪声 |
|---|---|---|---|
| RGB共享ratio | 三通道等比例缩放 | 容易溢出 | 轻微放大 |
| HSV替换V通道 | 只改亮度,色相饱和度不变 | 过渡更平缓 | 噪声放大更明显 |
| 亮度通道取法 | 公式 | 适用场景 |
|---|---|---|
| 加权灰度 | 0.299R + 0.587G + 0.114B | 通用场景,最稳定 |
| 三通道均值 | (R + G + B) / 3 | 颜色偏离严重的图像 |
| 最大值法 | max(R, G, B) | 类似光晕效果,但过曝敏感 |
实际对比时,建议把两种实现都跑一遍,观察同一张图里天空边缘色块的过渡区域。MSRCP对雾霾图和夜间低照度图的效果最明显,因为它不强行改变颜色比例,处理后的图看起来更“像一张正常照片”,而不是“一张调色夸张的滤镜图”。
4.3 MSRCP的适用场景和溢出问题
MSRCP适合三类图:雾图、低照度图、航拍图。这三类图共同特点是亮度动态范围大,但色彩本身的偏差不大。如果用MSRCR处理雾图,天空区域容易出现色斑;用MSRCP处理,色调基本稳定。
溢出问题主要发生在ratio超过3的区域。低照度图的暗部像素接近0,分母gray很小,ratio计算出来可能上百。这时如果直接把ratio乘上去,原来暗部的噪声会变成明显的彩色噪点。解决办法不是去掉ratio上限,而是先对原始图像做一次很小的中值滤波再做ratio计算。
5. 用matlab搭建四算法对比脚本并计算客观指标
5.1 统一入口:一次调用四个函数
把前面几个函数放到同一个目录下,主脚本只要统一读取图像和参数,就能得到四组结果:
clear; close all; img = imread('test.png'); sigmas = [15 80 250]; weights = [1 1 1] / 3; r_ssr = retinex_ssr(img, 80); r_msr = retinex_msr(img, sigmas, weights); r_msrcr = retinex_msrcr(img, sigmas, weights, 125, 46, 192, -30); r_msrcp = retinex_msrcp(img, sigmas, weights); figure; subplot(1, 5, 1); imshow(img); title('原图'); subplot(1, 5, 2); imshow(r_ssr); title('SSR'); subplot(1, 5, 3); imshow(r_msr); title('MSR'); subplot(1, 5, 4); imshow(r_msrcr); title('MSRCR'); subplot(1, 5, 5); imshow(r_msrcp); title('MSRCP');5.1.1 确保输入输出格式一致
四个函数的输入都必须是double型,在函数内部统一im2double,输出统一在[0,1]区间,这样imshow和指标计算才不会出现一个需要乘255、一个不需要的混乱情况。对比程序最怕格式不统一,结果图看起来一样,指标却差了一个量级。
5.2 三个无参考指标:信息熵、标准差、平均梯度
因为没有标准参考图,图像增强的效果只能用无参考指标判断。最常用的三个:
function e = img_entropy(gray) p = imhist(gray) / numel(gray); p(p == 0) = []; e = -sum(p .* log2(p)); end gray = rgb2gray(r_msr); entropy_val = img_entropy(gray); std_val = std2(gray); [gx, gy] = gradient(double(gray)); grad_val = mean(gx(:).^2 + gy(:).^2);信息熵反映灰度分布的丰富程度,熵越高细节越多;标准差反映整体对比度,标准差太低说明图像发灰;平均梯度反映边缘强度,梯度大说明边缘清晰。这三个指标在对比表里通常趋势一致,但也有例外,下面会提到。
5.3 四种算法的参数、效果与适用方向对比表
| 算法 | 通道处理方式 | 色彩恢复 | 主要问题 | 适用场景 |
|---|---|---|---|---|
| SSR | 单尺度逐通道log差 | 无 | 细节好但动态范围压缩不足 | 暗部细节提取、快速实验 |
| MSR | 多尺度逐通道加权 | 无 | 偏灰、颜色不饱和 | 亮度增强、动态范围压缩 |
| MSRCR | MSR后乘色彩恢复因子 | 有,颜色饱和 | 参数域敏感、高光易溢出 | 室内暗图、工业相机图像 |
| MSRCP | 亮度图做MSR后共享比例 | 色度保持 | 暗部噪声放大 | 雾图、低照度图、航拍图 |
这个表也是对比程序里最终输出的核心内容,建议把每一行对应的指标数字填进去,比单独贴四张图更有说服力。
5.4 指标高不代表好:噪声的分数也会上涨
信息熵和标准差有一个共同弱点:对噪声特别友好。一张带大量椒盐噪声的图,灰度层级变多,信息熵反而升高;对比度被噪声拉大,标准差也变大。所以只看数值会误判噪声图是“好结果”。
补充一个边缘保持判断,用Sobel边缘占比来衡量:
e = edge(gray, 'sobel'); edge_ratio = sum(e(:)) / numel(e);如果两张图的信息熵接近,edge_ratio更高的那张通常细节保留更好。如果某张图edge_ratio异常高,超过0.3,就要怀疑是噪声而不是真实边缘。真实图像的边缘占比一般在0.02到0.15之间。
6. 批量自动调参:sigmas网格搜索与结果记录
6.1 等比例插值:网格不要等间隔
调试MSR和MSRCP时,最耗时间的是确定sigma组合。很多人习惯用等间隔网格,比如从10到250每隔30取一个值,这样在log尺度上,小尺度区域挤了太多点,大尺度区域反而稀疏。
常见做法是采用几何平均插入中间尺度。给定两个端点sigma,中间值取两者的几何平均,这样三尺度在log域上均匀分布:
sPool = [10 20 40 80 160 250]; score = zeros(numel(sPool), numel(sPool)); for a = 1:numel(sPool) for b = a+1:numel(sPool) sig = [sPool(a), round(sqrt(sPool(a) * sPool(b))), sPool(b)]; r = retinex_msr(img, sig, [1 1 1] / 3); g = rgb2gray(r); score(a, b) = img_entropy(g) + std2(g); end end imagesc(score);这里用sqrt(sPool(a) * sPool(b))生成中间尺度,让三个尺度在log域上等距。score用熵加标准差,是在没有参考图情况下最简单的自动筛选方式。
6.2 批量跑图时保存每组参数与结果
单张图调参意义有限,批量验证时要把每组参数和对应指标写进文件,方便后续做参数敏感性分析:
writematrix(score, 'msr_search_score.csv'); writematrix(sPool, 'msr_search_sigmas.csv');跑批处理时建议把原图文件名也一起记录下来,因为不同内容对sigma的偏好差异很大,只记参数不记图像,分析时会对不上号。
6.3 让结果可复现:记录sigmas组合和随机种子
网格搜索完成之后,把选出的最优参数回填到对比脚本里,替换掉写死的[15 80 250]。同时建议把matlab版本、图像工具箱版本一并记录在结果文件头部,因为不同版本的高斯滤波实现会引入微小数值差异,虽不影响视觉判断,却会影响论文里的指标小数点。记录方式可以直接用writetable写一个meta表,列名分别是sigma1、sigma2、sigma3、entropy、std、edge_ratio,后面再写对比章节时,直接引用这张表即可。
本文还有配套的精品资源,点击获取