Retinex理论去雾:从光照模型到MATLAB实战
Retinex这个词,是Retina(视网膜)和Cortex(大脑皮层)的合成词。说白了,这套理论想模拟的是——人眼怎么在千变万化的光照下,还能看出物体本来的颜色。
我最早接触Retinex是在做遥感图像增强的项目。那时候雾霾图一上来,直方图均衡化效果太生硬,暗通道先验在某些场景又失效。后来试了Retinex,嗯,真香。今天我就把这块内容掰开揉碎讲给你。
核心思想:一张图像可以分解为光照分量(决定亮度范围)和反射分量(决定物体本色)。去雾的本质,就是想办法把反射分量提取出来。
Retinex理论简介
Edwin Land在1971年提出这个理论时,其实是想解释一个现象:为什么一张白纸在暗处看起来还是白的?按物理亮度算,它反射的光可能比黑纸在亮处还少,但人眼就是能「纠正」过来。
数学上,Retinex把图像建模成:
I(x,y) = L(x,y) × R(x,y)
- I(x,y) — 我们看到的图像(含雾、含光照不均)
- L(x,y) — 光照分量,变化缓慢,低频
- R(x,y) — 反射分量,包含细节和颜色,高频
去雾的思路就清晰了:估计出L,然后从I中除掉L,剩下的就是R。实际操作中,我们会在对数域做减法,因为除法在对数域变成了减法,计算更稳定。
我的经验:千万别小看这个「对数域操作」。我早期直接做除法,结果图像噪声被放大了好几倍。转到对数域后,数值范围被压缩,处理起来稳得多。
单尺度Retinex(SSR)
SSR是最基础的版本。它的做法很简单:用高斯滤波对原图做平滑,把平滑结果当作光照分量L,然后在对数域减去它。
公式长这样:
R(x,y) = log I(x,y) - log [G(x,y) * I(x,y)]
其中G是高斯核,*是卷积操作。高斯核的尺度σ(标准差)决定了平滑程度。
σ的影响:
| σ值 | 效果 | 典型问题 |
|---|---|---|
| σ 较小(< 30) | 细节增强明显,边缘锐利 | 光晕伪影严重,颜色偏灰 |
| σ 中等(30~100) | 细节和颜色平衡较好 | 动态范围压缩不够 |
| σ 较大(> 100) | 颜色保真度高,光晕少 | 细节增强不足,图像偏暗 |
避坑指南:我曾经在σ=15时跑过一张城市雾景,结果建筑物边缘出现了一圈圈的光晕,像漫画特效一样。后来发现是高斯核太小,把边缘也当成了光照变化。记住:σ至少要比你想保留的细节尺度大2~3倍。
多尺度Retinex(MSR)
SSR的问题在于——一个σ搞不定所有场景。细节和颜色保真度总是打架。MSR的思路很直接:用多个不同尺度的SSR结果加权平均。
公式:
R_msr(x,y) = Σ w_i × R_i(x,y)
其中R_i是第i个尺度下的SSR结果,w_i是权重,通常Σ w_i = 1。
我常用的三尺度配置:
- 小尺度(σ=15~30):抓细节,权重0.3~0.4
- 中尺度(σ=50~100):平衡色调,权重0.4~0.5
- 大尺度(σ=150~300):保颜色,权重0.2~0.3
你想想看,小尺度负责把雾气边缘的细节拉出来,大尺度保证天空不会变成灰色,中尺度在中间做调和。三个尺度各司其职,效果比单尺度稳得多。
MATLAB实现与参数调优
下面是我在实际项目中打磨过的MSR实现。代码不长,但每个参数都有讲究。
function R = msr_dehaze(I, sigmas, weights)
% I: 输入图像 (double, [0,1])
% sigmas: 高斯尺度数组,如 [15, 80, 200]
% weights: 对应权重,如 [0.3, 0.4, 0.3]
I = im2double(I);
[h, w, c] = size(I);
R = zeros(h, w, c);
% 对数域处理
log_I = log(I + 1e-6); % 加小常数防止log(0)
for i = 1:length(sigmas)
% 高斯滤波估计光照
hsize = 2 * ceil(3 * sigmas(i)) + 1;
G = fspecial('gaussian', hsize, sigmas(i));
log_L = zeros(h, w, c);
for ch = 1:c
log_L(:,:,ch) = imfilter(log_I(:,:,ch), G, 'replicate');
end
% SSR结果
R_i = log_I - log_L;
% 加权累加
R = R + weights(i) * R_i;
end
% 指数变换回强度域
R = exp(R);
% 对比度拉伸到[0,1]
for ch = 1:c
R(:,:,ch) = imadjust(R(:,:,ch));
end
end
参数调优的几条铁律:
- σ不要取整十数。我习惯用15、80、200这种非整数倍关系,避免尺度重叠产生条纹。
- 权重分配看场景。雾浓时加大中尺度权重(0.5以上),雾薄时加大尺度权重(0.4以上)。
- 高斯核尺寸要自适应。hsize = 2×ceil(3×σ)+1,保证核覆盖99.7%的能量。
- 对比度拉伸别太猛。imadjust的饱和度参数设0.01~0.02就够了,拉太猛噪声全出来了。
我的调参习惯:先在σ=15、80、200这个组合上跑一遍,看结果。如果细节不够,把小尺度σ降到10;如果颜色偏灰,加大尺度σ到250。每次只调一个参数,别贪心。
效果对比:SSR vs MSR
| 指标 | SSR(σ=80) | MSR(15,80,200) |
|---|---|---|
| 细节还原 | 中等,边缘有光晕 | 好,多尺度互补 |
| 颜色保真 | 偏灰,饱和度下降 | 接近真实色彩 |
| 计算时间 | 快(~0.3s) | 中等(~0.8s) |
| 适用场景 | 均匀薄雾 | 浓雾、不均匀光照 |
说实话,MSR比SSR慢一倍左右,但效果提升是肉眼可见的。我个人建议:除非你对实时性要求极高,否则直接上MSR。
最后说一句:Retinex不是万能的。它在天空区域有时会过增强,把蓝天变成灰蓝。这时候可以结合暗通道先验做区域融合——不过那是后面章节的内容了。先把MSR跑通,参数调顺手,你就能处理80%的雾图场景。