Retinex理论去雾:从光照模型到MATLAB实战

Retinex这个词,是Retina(视网膜)和Cortex(大脑皮层)的合成词。说白了,这套理论想模拟的是——人眼怎么在千变万化的光照下,还能看出物体本来的颜色。

我最早接触Retinex是在做遥感图像增强的项目。那时候雾霾图一上来,直方图均衡化效果太生硬,暗通道先验在某些场景又失效。后来试了Retinex,嗯,真香。今天我就把这块内容掰开揉碎讲给你。

核心思想:一张图像可以分解为光照分量(决定亮度范围)和反射分量(决定物体本色)。去雾的本质,就是想办法把反射分量提取出来。

Retinex去雾核心逻辑 输入雾图 I(x,y) 光照分量 L(x,y) 反射分量 R(x,y) I = L × R 对数域:log I = log L + log R 去雾结果 R(x,y) 关键操作 1. 用高斯滤波估计光照 L 2. 对数域相减得到 log R 3. 指数变换 + 对比度拉伸

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

参数调优的几条铁律:

  1. σ不要取整十数。我习惯用15、80、200这种非整数倍关系,避免尺度重叠产生条纹。
  2. 权重分配看场景。雾浓时加大中尺度权重(0.5以上),雾薄时加大尺度权重(0.4以上)。
  3. 高斯核尺寸要自适应。hsize = 2×ceil(3×σ)+1,保证核覆盖99.7%的能量。
  4. 对比度拉伸别太猛。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%的雾图场景。