简介一份基于Matlab实现的Retinex去雾项目面向图像处理学习者和算法研究人员可用于雾霾图像的清晰化复原与效果对比。项目从Retinex理论出发通过分离图像反射与透射分量来消除雾霾影响同时兼顾色彩校正与局部对比度增强适合用于摄影、监控及无人机影像等低能见度场景的预处理。压缩包共14个文件以MATLAB脚本.m为主辅以示例图片.jpg/.png和可复用的功能压缩包整体大小约170KB源码脚本涵盖Retinex主算法、直方图均衡、小波变换及本地直方图均衡等多种可对照的实现方式便于学习者逐段调试并对比去雾效果。已有375人学习浏览资源虽小巧但代码结构完整适合初学者从零跑通去雾流程并修改参数观察结果也能为进阶研究者提供算法改进的参考起点。1. 为什么 Retinex 去雾在 MATLAB 里值得自己写一遍雾天图像最直观的问题是对比度低、色彩偏灰白但真正难处理的是场景中不同景深位置的雾浓度不一样。远处物体被雾遮挡得厉害近处相对轻微用固定参数去提对比度往往远处还没拉开、近处就已经过曝。Retinex 去雾走的是另一条路它不直接估计雾浓度而是假设图像由入射光照和物体反射率相乘得到雾的影响被归入照度分量把照度估计出来并去除剩下的反射率就是“本来应该看到的样子”。这个思路在光照不均、逆光和薄雾场景下比直方图均衡和暗通道先验更稳也更容易在 MATLAB 里快速验证。这个标题指向的是一次 MATLAB 上的 Retinex 去雾实现涉及 SSR、MSR 以及改进变体。下面从原理、最小实现、改进方法到参数调优逐步展开所有代码基于 MATLAB R2023b 编写不依赖额外工具箱Image Processing Toolbox 就足够跑通。2. Retinex 去雾原理与 MATLAB 里的两个经典实现2.1 Retinex 假设图像是反射率与照度的乘积Retinex 理论把观察到的图像 (S(x,y)) 建模为照度 (L(x,y)) 和反射率 (R(x,y)) 的乘积[ S(x,y) L(x,y) \times R(x,y) ]照度分量表示环境光源的分布变化缓慢、低频为主反射率分量表示物体本身的材质特性包含边缘和高频细节。去雾或者增强的实质是保留反射率、抑制照度的影响。由于乘法在对数域变成加法处理起来更方便[ \log S \log L \log R ]因此估计出照度 (\log L) 后用减法就能得到反射率。MATLAB 里实现时关键是选一种方式从原图中估计照度。最常用的是高斯模糊因为雾和光照在空间上都是平滑变化的高斯核的尺度决定照度估计的细腻程度。这个假设解释了 Retinex 为什么对薄雾有效雾相当于给场景叠加了一层不均匀的白色面纱在乘性模型里它属于照度的一部分去除照度等价于揭掉面纱。但如果雾太浓反射率信号本身被严重衰减乘性模型不够精确效果会打折。2.2 用 MATLAB 手写 SSR 最小实现单尺度 RetinexSSR是理解整套思路的最小单元。对输入图像做高斯模糊得到照度估计然后原图对数减照度对数最后做增益补偿和动态范围拉伸。function out retinex_ssr(img, sigma) % img: 双精度灰度图或彩色图范围 [0,1] % sigma: 高斯核标准差控制照度估计的尺度 img im2double(img); if size(img, 3) 3 out zeros(size(img)); for c 1:3 out(:, :, c) retinex_ssr_single(img(:, :, c), sigma); end else out retinex_ssr_single(img, sigma); end end function r retinex_ssr_single(channel, sigma) L imgaussfilt(channel, sigma); % 估计照度 logS log(channel eps); % 原图对数eps 防 log(0) logL log(L eps); % 照度对数 r logS - logL; % 反射率对数域结果 r (r - min(r(:))) / (max(r(:)) - min(r(:))); % 线性拉伸到 [0,1] end代码里imgaussfilt直接用高斯模糊估计照度sigma是核心参数。log之前加eps是为了避免像素值为 0 时出现负无穷。拉伸操作不可省略因为对数域反射率的动态范围通常远小于原图不拉伸直接转 uint8 会得到一片灰蒙蒙的图像。SSR 的缺陷在于sigma的选择是两难的核太小照度估计会包含边缘纹理去雾后出现光晕核太大照度估计过于平滑近处的增强效果不足。实际调参时薄雾图用 ( \sigma \in [80, 120] )浓雾图要降到 ([40, 60])但牺牲的是边缘自然度。2.3 从 SSR 到 MSR 的 MATLAB 实现与参数表多尺度 RetinexMSR解决单尺度顾此失彼的问题思路是多个不同sigma的 SSR 结果加权平均。大尺度照顾整体光照均匀性小尺度负责细节对比度中尺度做过渡。function out retinex_msr(img, sigmas, weights) % sigmas: 高斯核标准差数组例如 [15 80 250] % weights: 对应权重数组需要和为 1例如 [0.333 0.333 0.334] img im2double(img); if size(img, 3) 3 out zeros(size(img)); for c 1:3 out(:, :, c) retinex_msr_single(img(:, :, c), sigmas, weights); end else out retinex_msr_single(img, sigmas, weights); end % 归一化到 [0,1] for c 1:size(out, 3) ch out(:, :, c); ch (ch - min(ch(:))) / (max(ch(:)) - min(ch(:))); out(:, :, c) ch; end end function r retinex_msr_single(channel, sigmas, weights) acc zeros(size(channel)); for i 1:length(sigmas) L imgaussfilt(channel, sigmas(i)); acc acc weights(i) * (log(channel eps) - log(L eps)); end r acc; endMSR 的参数比 SSR 多一组权重默认配置如下尺度类型sigma 范围权重占比适用场景小尺度细节5 ~ 200.2 ~ 0.4增强边缘和纹理中尺度过渡50 ~ 1000.3 ~ 0.4保持局部对比度大尺度光照200 ~ 3000.3 ~ 0.5均衡全局光照不均权重不需要严格相等雾浓度越高大尺度权重应适当增大因为雾的影响更接近全局均匀的照度。MSR 对 RGB 三个通道独立处理时会出现颜色偏移的问题这留到第 3 章专门讨论。3. 改进 Retinex用引导滤波代替高斯环绕3.1 高斯环绕的“光晕”问题从哪来高斯环绕在强边缘附近会越过边界弥散导致照度估计在边缘两侧出现渐变过渡反映到反射率上就是边缘周围一圈亮边或暗边俗称光晕伪影。去雾场景中天空与山脊、建筑轮廓这类大梯度边界处最明显。引导滤波是一种保边滤波器它利用引导图的结构信息来决定滤波权重在平坦区域类似均值滤波在边缘区域保留边界。MATLAB 里可以直接用imguidedfilter也可以用locallapfilt做边缘保持平滑两者的区别在于imguidedfilter更接近传统引导滤波速度更快。3.2 MATLAB 里实现引导滤波 Retinex 的步骤用引导滤波替换高斯模糊只需要改照度估计这一步。引导图可以选择原图本身也可以选择原图的高斯平滑版本后者能进一步抑制噪声干扰。function out retinex_guided(img, sigma, eps) % sigma: 引导滤波的邻域半径 % eps: 正则化参数值越小边缘保持越强一般取 0.01 ~ 0.1 img im2double(img); if size(img, 3) 3 % 对亮度分量做 Retinex色彩用原始比例恢复 I rgb2hsv(img); V I(:, :, 3); V_log log(V eps); L imguidedfilter(V, V, NeighborhoodSize, sigma, DegreeOfSmoothing, eps); L_log log(L eps); R_log V_log - L_log; R exp(R_log); R (R - min(R(:))) / (max(R(:)) - min(R(:))); I(:, :, 3) R; out hsv2rgb(I); else L imguidedfilter(img, img, NeighborhoodSize, sigma, DegreeOfSmoothing, eps); R_log log(img eps) - log(L eps); out exp(R_log); out (out - min(out(:))) / (max(out(:)) - min(out(:))); end end这段代码把处理对象从 RGB 三个通道改为 HSV 空间的 V 通道这是 Retinex 去雾中非常实用的变体。原因是 Retinex 本身设计用于灰度图像直接套用到 RGB 会把三个通道的相对关系打乱导致颜色失真。在 HSV 空间只处理亮度颜色由 H 和 S 通道保留结果更自然。NeighborhoodSize对应引导滤波的窗口半径推荐值是floor(size(img, 1) / 50)左右图像越小取值越小。DegreeOfSmoothing控制边缘保留程度雾图建议从 0.05 起步如果光晕还是明显就下调到 0.01如果出现斑块状噪声就上调。3.3 亮度分量与颜色保真的两个细节在 HSV 空间做 Retinex 之后V 通道的直方图往往集中在中间区域整体偏灰。常见的处理是用自动增益来拉伸 V 通道的对比度但增益太大会导致色彩饱和度看起来不足。另一种更稳定的做法是引入颜色恢复因子在 RGB 空间计算原始图像的色彩比例然后对 Retinex 结果按比例修正。function out retinex_msrcr(img, sigmas, weights, alpha, beta, G, b) % MSRCR: 多尺度 Retinex 带颜色恢复 % alpha: 颜色恢复强度典型值 125 % beta: 增益控制典型值 46 % G、b: 最终线性变换的增益和偏移 img im2double(img); msr retinex_msr(img, sigmas, weights); % 复用前一节的 MSR msr msr * G b; for c 1:3 sum_channel sum(img, 3) eps; % 三个通道之和防除零 color_restore beta * (log(alpha * img(:, :, c) 1) - log(sum_channel)); msr(:, :, c) msr(:, :, c) .* color_restore; end out msr; for c 1:3 ch out(:, :, c); ch (ch - min(ch(:))) / (max(ch(:)) - min(ch(:))); out(:, :, c) ch; end end颜色恢复因子的数学含义是某个通道在原图中的占比越高恢复权重越大。这样能遏制 MSR 把灰雾去除后所有颜色都往灰色拉的倾向。alpha和beta的取值在不同论文里有差异上述两个值是实践中最常用的起点实际项目中按雾浓度微调浓雾时alpha降到 80 左右。4. 去雾效果上的 3 个关键参数与 MATLAB 调试方法4.1 多尺度权重和半径怎么配合参数不是孤立生效的大半径配合大权重容易产生色彩漂移小半径配合大权重则保留过多雾感。这里给出一个经过多次验证的配合模式场景sigmasweights引导滤波半径DegreeOfSmoothing薄雾、风景[15 80 200][0.3 0.4 0.3]150.08中等雾、街景[10 50 150][0.25 0.35 0.4]200.05浓雾、特殊场景[5 30 100][0.2 0.3 0.5]300.02权重向大尺度倾斜的好处是整体亮度更均匀但反过来会损失远处的细节对比度。调试时先固定权重为均值只调sigmas观察边界的伪影程度再固定sigmas调权重观察整体亮度。两个方向反复切换不要同时调整全部参数。4.2 增益与偏移输出动态范围拉伸Retinex 输出的对数域结果数值范围不确定直接线性拉伸每个通道会让颜色比例失真。retinex_msrcr中已经看到 G 和 b 的存在它们用来替代线性拉伸function out retinex_gain_offset(msr, G, b, low_clip, high_clip) out msr * G b; % 基于百分位裁剪代替 min-max 拉伸防止个别噪点主导范围 for c 1:size(out, 3) ch out(:, :, c); low prctile(ch(:), low_clip); high prctile(ch(:), high_clip); ch(ch low) low; ch(ch high) high; out(:, :, c) (ch - low) / (high - low); end end百分位裁剪的好处是抗离群值。min-max 拉伸对单像素噪点极其敏感一个过亮的高光点会把整个动态范围拉偏。low_clip和high_clip一般取 1 和 99意思是把上下各 1% 的极端像素截断。如果图像里有大块天空high_clip可以考虑放到 99.5。4.3 失效场景与调试顺序Retinex 去雾不是万能的遇到三类情况需要先处理再跑浓雾导致反射率信号低于噪声底图像大气光不均匀类似雾中还有雾的场景以及夜间低照度图像。这些场景下 Retinex 只会增强噪声。调试时按以下顺序排查先看log之前的输入范围必须是 [0,1] 的双精度uint8 直接运算会溢出。再看照度图L如果L和原图几乎一样说明模糊半径太小或eps太大边缘的照度变化被当成了反射率。接着看反射率直方图如果集中在零附近说明雾过浓需要调大尺度权重而不是盲目调增益。最后检查 RGB 输出比例拿原图的天空区域做参考如果输出天空偏红或偏蓝颜色恢复因子没有生效。5. 验证去雾效果的量化指标与 MATLAB 脚本5.1 用熵、对比度和暗通道先验评估主观视觉判断容易陷入“看起来更鲜亮就是更好”的误区量化指标才能客观比较不同参数的效果。常用的三个指标信息熵反映细节丰富度均方差对比度反映边缘锐度暗通道均值反映雾残留程度。暗通道均值越低说明雾去除越彻底。function metrics evaluate_dehaze(original, dehazed) % 信息熵 ent_orig entropy(im2gray(im2double(original))); ent_dehazed entropy(im2gray(im2double(dehazed))); % 均方差对比度 contrast_orig std2(im2gray(im2double(original))); contrast_dehazed std2(im2gray(im2double(dehazed))); % 暗通道均值基于暗通道先验的定义 dc_orig dark_channel(original); dc_dehazed dark_channel(dehazed); metrics table([ent_orig; ent_dehazed], [contrast_orig; contrast_dehazed], ... [mean(dc_orig, all); mean(dc_dehazed, all)], ... VariableNames, {Entropy, Contrast, DarkChannel}, ... RowNames, {Original, Dehazed}); end function dc dark_channel(img) img im2double(img); dc min(img, [], 3); % 逐像素取 RGB 最小值 dc imboxfilt(dc, 15); % 局部最小值相当于先取 min 再盒式滤波 dc imboxfilt(dc, 15); % 两次盒式滤波近似最小值滤波 end评估时重点看熵和暗通道的变化方向去雾后熵应当上升细节增多暗通道均值应当下降雾残留减少。对比度不一定单调上升因为去雾会压缩动态范围。如果熵上升但暗通道均值也上升说明算法把噪声也增强了。5.2 批处理多张图并输出对比图的技巧调参过程中需要跑多张图对比手动画图浪费时间。用 MATLAB 的montage配合imtile可以快速拼出对比网格。function compare_results(orig, dehazed, save_path) % orig 和 dehazed 可以是多张图的 cell 数组 all_images {}; for i 1:numel(orig) all_images{end1} orig{i}; all_images{end1} dehazed{i}; end figure(Visible, off); montage(all_images, Size, [numel(orig), 2]); if nargin 2 ~isempty(save_path) exportgraphics(gcf, save_path, Resolution, 150); end endexportgraphics对精度控制比print更好输出到 PNG 时不会出现压缩伪影。保存图像时记得把图像数据转回 uint8否则部分查看器会显示成纯白。最后还有一个实用技巧在处理一批图片时先用其中一张图把参数调到熵和暗通道指标的拐点位置也就是继续调参指标不再明显改善的点然后把所有参数固定批量跑。一次参数调优的结果通常能覆盖同一批次拍摄的图不需要逐张单独调参。这是 Retinex 在实际工程里落地时最省时间的做法。本文还有配套的精品资源点击获取