基于暗通道先验的图像去雾MATLAB实现:从原理到参数调优
发布时间:2026/9/15 21:29:55来源:尧图网络
简介何凯明图像去雾算法的MATLAB程序包围绕图像去雾这一经典难题提供从代码实现到界面交互的完整方案面向图像处理学习者、计算机视觉研究者与相关课程设计开发者等群体。压缩包共15.42MB内含12个文件以m源码、jpg/png测试图、fig界面和pptx展示为主涵盖暗通道先验估计、引导滤波细化和盒滤波预处理等核心算法模块hazeremoval.m为可直接运行的主算法DIPGUI.m与DIPGUI.fig构成图形界面可交互导入图像并调整参数实时观察去雾效果为算法复现提供完整支撑。全套资源已有763人学习下载适合作为课堂演示、课程作业或算法比对的参考材料。读者既能对照源码理清何凯明算法从大气散射模型到结果输出的完整脉络又可利用多张不同雾度与场景的测试图验证鲁棒性还可借助附带的PPT思路解读提炼改进要点快速形成展示或汇报内容兼顾学习与实用。1. 从暗通道先验到图像去雾 MATLAB 实现为什么还值得读去雾任务里最容易被忽视的一个事实是一张没有雾的户外图像其局部小方块里总存在一些像素RGB 三个通道的最低值接近零。何凯明团队把这个统计规律写成暗通道先验再用单张输入图同时估计大气光和透射率绕开了机器学习数据训练的依赖。本资源包提供的hazeremoval.m、guidedfilter.m、boxfilter.m和DIPGUI.fig就是这条经典路线的一个完整落地实现既可以直接跑测试图也适合图像处理大作业的二次开发。对于已经习惯用深度学习做图像复原的人来说这个传统方案仍然适合作为去雾链路中的轻量基线和可视化教学素材。2. 大气散射模型与 hazeremoval.m 主流程拆解2.1 模型和暗通道先验透射率从哪里来有雾图像的退化过程通常用大气散射模型描述I(x)J(x)t(x)A(1-t(x))其中I是观测到的有雾图像J是希望恢复的清晰图像A是全局大气光t(x)是透射率。这个式子本身是一个欠定问题因为已知I要求J、A、t三个未知量。暗通道先验在这里提供了核心约束对无雾图像J任意局部窗口Ω(x)内总存在某一点在某一个颜色通道上的强度接近 0所以定义暗通道为J_dark(x)min_{c∈{r,g,b}} min_{y∈Ω(x)} J_c(y) → 0把它回代到散射模型在局部窗口内假设t(x)恒定就可以把透射率初值解出来t_est(x)1 - min_{c} min_{y∈Ω(x)} I_c(y)/A_c这一步是整个hazeremoval.m的数学地基也是后边所有参数调试的参照物。需要留意的是暗通道的“局部窗口”尺寸直接决定了先验假设是否成立窗口太大物体边界会被抹平窗口太小暗通道不再趋近 0透射率估计会系统失真。所以实现拆解要从暗通道计算开始。2.2 暗通道与大气光的 MATLAB 实现资源包里没有单独拆出dark_channel.m但这个步骤通常内联在hazeremoval.m前部。我会在调试时单独抽出它方便观察中间量。常见做法是先把 RGB 三通道逐点取最小值再做一次局部最小值滤波function Jdark dark_channel(I, w) % I: double 类型的 RGB 图像取值范围 0~1 % w: 局部窗口边长建议奇数默认 15 I im2double(I); Jmin min(I, [], 3); % 取三通道逐点最小值 se strel(square, w); % 正方形结构元 Jdark imerode(Jmin, se); % 腐蚀等价于局部最小值滤波 end逻辑很好懂min(I,[],3)拿到每个像素的 RGB 最低值imerode求邻域内最小值。工程上用它而不是colfilt或nlfilter原因是imerode是底层 C 实现几百万像素的图像也能毫秒级完成。窗口w如果设成偶数窗口中心会偏移半格所以我习惯固定为 9、15、21 这类奇数。大气光A的估计对恢复结果亮度影响最大。He 的经典做法是取暗通道最亮的 0.1% 像素再回到原图中取这些位置的 RGB 平均作为Afunction A estimate_atmosphere(I, Jdark) % I: 原始有雾图double 类型 % Jdark: 暗通道图 [m, n, ~] size(I); num max(floor(m * n * 0.001), 1); % 前0.1%像素 Jdark_vec reshape(Jdark, m * n, 1); [~, idx] maxk(Jdark_vec, num); % 提取最大暗通道值的线性索引 R I(:,:,1); G I(:,:,2); B I(:,:,3); A [mean(R(idx)), mean(G(idx)), mean(B(idx))]; end这里有个版本兼容问题maxk要求 MATLAB R2017b 及以上旧版本可以用sort先降序排列再取前num个索引。选取比例不是越大越好取 1% 以上容易被路面高光、白色车辆拉高A导致恢复图整体发暗取太少又会让A对噪点敏感。0.1% 在大多数室内外雾图上都有不错的稳定性。2.3 粗透射率恢复与 t0 下限拿到A之后粗透射率计算公式是A_rgb reshape(A, 1, 1, 3); % 对齐到空间维 normalized I ./ A_rgb; % R2016b以后支持隐式扩展 t 1 - 0.95 * dark_channel(normalized, patch_size); t max(t, 0.1); % 限制透射率下限在旧版 MATLAB 上I ./ A_rgb需要用bsxfun(rdivide, I, A_rgb)代替。0.95是omega表示保留少量雾以维持深度感和观感不需要追求完全去雾。max(t,0.1)是关键保护当暗通道值很大时粗透射率会逼近 0直接反解图像会造成天空区域的极亮噪声下限t0通常取 0.05 到 0.15 之间。在hazeremoval.m里很多变体是在这个步骤之后直接接恢复公式J(I-A)./max(t,t0)A。如果你打开资源包直接看代码可能会发现它已经把下一章的guidedfilter插入到了t和J之间。粗透射率图是一块一块的直接恢复必然出白边所以接下来的引导滤波不是可选项而是保证输出能看的必要环节。3. 引导滤波和 boxfilter去掉光晕的关键一笔3.1 粗透射率图为什么会产生 halo粗透射率是基于窗口计算的在每个局部小方块内部透射率被认为恒定。窗口落在物体边缘附近时通常一半是前景一半是背景估算出的透射率既不是前景透射率也不是背景透射率而是一个折中值。把这个块状透射率回代到J(I-A)/tA边缘两侧的像素会被强行除以同一个近似值导致前景边缘外侧出现一条亮边这就是 halo 光晕。He 的原始论文使用软抠图优化透射率效果干净但需要解大型稀疏线性方程内存和时间成本都不低。后来提出的引导滤波使用局部线性模型可以在保边缘的前提下把透射率磨平速度接近线性复杂度。资源包里的guidedfilter.m正是为替代软抠图而产生的工程方案。3.2 guidedfilter 的局部线性模型与代码引导滤波的核心假设是输出图q与引导图I在局部窗口内满足q_i a_k * I_i b_k。当引导图存在边缘时a_k和b_k由局部方差和协方差决定输出图会继承引导图的边缘结构同时又要让q尽量接近输入透射率图p。MATLAB 实现里最典型的是这一版function q guidedfilter(I, p, r, eps) % I: 灰度引导图, 取值0~1 % p: 待滤波的透射率图 % r: 局部窗口半径, 常用40 % eps: 正则化参数, 常用1e-3 I im2double(I); p im2double(p); N boxfilter(ones(size(I)), r); mean_I boxfilter(I, r) ./ N; mean_p boxfilter(p, r) ./ N; corr_I boxfilter(I .* I, r) ./ N; corr_Ip boxfilter(I .* p, r) ./ N; var_I corr_I - mean_I .* mean_I; cov_Ip corr_Ip - mean_I .* mean_p; a cov_Ip ./ (var_I eps); b mean_p - a .* mean_I; mean_a boxfilter(a, r) ./ N; mean_b boxfilter(b, r) ./ N; q mean_a .* I mean_b; end这里的N是每个像素邻域内的有效像素数boxfilter用积分图做了 O(N) 的盒式滤波。参数eps控制边缘感知强度eps越大方差在分母上的权重越低结果越接近均值滤波eps太小图像噪声会被当成边缘保留下来。我一般固定eps1e-3只调r。r是窗口半径窗口大小是2r1所以r40对应 81×81 的邻域适合几百像素尺度的图像4K 图需要把r降到 20 以下否则会明显磨平纹理。3.3 boxfilter 积分图实现与工程替换boxfilter.m在这里起的是加速作用。一个直接的 MATLAB 写法是imfilter(imSrc, ones(2*r1,2*r1), same)复杂度随r平方增长在引导滤波里会被调用多次速度非常难看。资源包中的boxfilter.m是典型的两步累计和实现function imDst boxfilter(imSrc, r) % imSrc: 待滤波矩阵 % r: 窗口半径, 实际窗口大小为 2r1 [m, n] size(imSrc); imDst zeros(size(imSrc)); imCum cumsum(imSrc, 1); imDst(r1:m-r, :) imCum(2*r1:m, :) - imCum(1:m-2*r, :); imCum cumsum(imDst, 2); imDst(:, r1:n-r) imCum(:, 2*r1:n) - imCum(:, 1:n-2*r); end逻辑分两步先沿行方向做累计和用差分得到每个窗口的列向和再沿列方向做同样的操作得到二维窗口中所有像素的和。边界位置的像素没有被赋值保持 0引导滤波中乘以N的分母后影响可忽略。如果你使用的是 MATLAB R2020a 之后的版本可以直接把boxfilter(im, r)替换成imboxfilt(im, 2*r1)内部同样使用积分图还省去手动处理边界。注意guidedfilter中的引导图应该用原图的灰度图而透射率p是单通道两者尺寸必须一致。很多去雾实现把rgb2gray(I)写成I本身导致矩阵维度对不上这是最常见的运行报错来源。4. DIPGUI 交互端跑通测试图像并完成课程设计演示4.1 DIPGUI.m / .fig 的结构和资源包里的测试图DIPGUI.fig保存的是界面布局DIPGUI.m保存回调函数。两者文件名和路径必须严格对应否则 MATLAB 会报找不到回调函数。打开界面的方式是在资源包目录下执行open DIPGUI.fig或者直接运行DIPGUI。资源包里的test1.jpg、test11.jpg、test3.jpg、test7.png、test6.png、test9.png覆盖了几类典型场景带天空的城市远景、近景山体、室内人造光源、均匀雾和低对比度场景。测试时建议先从test3.jpg开始因为它雾气分布均匀暗通道假设容易成立能快速判断算法主流程是否正常test11.jpg这种带大片天空的图是压力测试专门用来观察天空区域是否发黑。4.2 回调函数与参数滑块的典型写法课程设计里的 GUI 一般不需要很复杂比较完整的结构是“加载图像 去雾按钮 两个参数滑块 前后两个 axes”。回调函数是界面逻辑的最关键部分最常见的问题是忘记用guidata(hObject, handles)保存 handle导致滑块值永远不变。下面的结构是从DIPGUI.m里抽象出来的核心骨架function btnLoad_Callback(hObject, eventdata, handles) % 读取图像并缓存到 handles 中 [filename, pathname] uigetfile({*.jpg;*.png;*.bmp, 图像文件}); if isequal(filename, 0) return; end handles.currentImage imread(fullfile(pathname, filename)); guidata(hObject, handles); % 关键把句柄结构写回 GUI axes(handles.axesBefore); imshow(handles.currentImage); end function btnDehaze_Callback(hObject, eventdata, handles) I handles.currentImage; patch_size round(get(handles.sldPatch, Value)); omega get(handles.sldOmega, Value); I im2double(I); tic; J hazeremoval(I, patch_size, omega, 0.1); elapsed toc; axes(handles.axesAfter); imshow(J); set(handles.txtTime, String, sprintf(耗时 %.3f 秒, elapsed)); endguidata(hObject, handles)这一行很容易漏漏掉之后btnDehaze_Callback里读取到的handles.currentImage还是空值。另一个细节是滑块控件要设置Min、Max和SliderStep否则拖动手感不对。我习惯把sldPatch的范围设为 5 到 41sldOmega设为 0.80 到 0.99这样在界面上能覆盖大多数异常状态。4.3 新版 MATLAB 打开旧 .fig 的兼容处理这里有一个不小的兼容性问题如果当前 MATLAB 版本高于 R2019bGUIDE 已经不再默认维护直接用open DIPGUI.fig可能报错或者打开后控件失效。不要急着删掉.fig先尝试openfig(DIPGUI.fig, invisible)如果返回句柄再set(fig, Visible, on)。实在打不开时可以新建一个 App Designer 空白界面从旧的.fig里读出控件的 Tag、Position、String 属性再手动重建。对于期末答辩这类一次性用途最省事的方案其实是安装 R2018a 或 R2019b或者把 MATLAB Online 切到旧版本环境里运行。注意直接双击.fig文件不一定会启动 MATLABWindows 可能用别的程序打开。建议总在 MATLAB 命令行里用open命令加载。5. 参数调节、失效场景与容差机制补丁5.1 参数行为表patch_size、omega、t0、r、epshazeremoval.m里的默认参数只能在一个平均水平上工作实际使用中需要按场景调整。五个关键参数的影响表能省下大量试错时间参数位置增大时的效果推荐范围patch_size暗通道窗口暗通道更容易接近 0恢复图偏亮但边缘块状感加重925omega透射率强度去雾越彻底过大导致颜色过饱和和噪声放大0.850.97t0透射率下限数值越大天空越稳定但雾残留变多0.050.15r引导滤波半径平滑更强halo 减少边缘会被抹平2080eps引导滤波正则项越大越接近均值滤波边缘保持能力下降1e-41e-2omega是最常被误解的参数。它代表去雾保留量0.95意味着只去掉 95% 的雾刻意保留一点景深层次。如果把omega调到 1恢复图像立刻变成“重度锐化滤镜”的观感颜色也容易溢出。5.2 三类现场问题定位发灰、白边、偏紫实际跑起来后问题主要集中在三类。第一类是恢复图整体变灰且亮度低这时打印A的数值如果三个通道均值大于 0.9说明大气光被高光像素拉高了可以降低第 2 章中maxk的比例从 0.1% 降到 0.05%。第二类是物体边缘出现一圈白边这是透射率图没有被引导滤波处理或者r太小把r80换上要同时检查引导图是否用rgb2gray(I)。第三类是恢复图偏紫或偏绿原因是A的三个通道差异过大。一个快速补救办法是让A的三个通道取公共亮度值L0.299*A(1)0.587*A(2)0.114*A(3)再令A[L,L,L]。这破坏了物理上的波长差异但对多数自然图像挺有效。5.3 用容差机制修补天空和白色区域暗通道先验的致命弱点是图像中大面积高亮区域例如天空、白色墙面和水面这些地方的暗通道不接近 0透射率会被系统低估结果天空处出现深色污迹。比较经济的补丁是加一个容差阈值K当像素与大气光的距离小于K时认为该区域基本没有雾透射率直接置 1。代码放在guidedfilter之后% 容差机制修正, 解决天空过饱和问题 K 0.2; dist sqrt(sum((I - A) .^ 2, 3)); t_refined(dist K) 1;这段的逻辑是计算每个像素到大气光的欧氏距离距离小于K的像素被判为高亮近景区域不参与去雾。K0.2是个起始点天空占比越大K可以适当增大到 0.3代价是天空区域保留更多原图中的雾。在test11.jpg这类天空测试图上这个补丁能让天空从发黑变回亮灰色是教学展示里最容易出效果的一处改动。6. 定量验收与自动选参从肉眼到可量化6.1 用暗通道比值 R 评估恢复质量课程设计答辩时只放两张去雾前后对比图还不够评委很可能会问“效果怎么评价”。可以用一个简单的量化指标去雾后的暗通道均值与原图暗通道均值的比值。无雾图像的暗通道应该很暗去雾不彻底的图像暗通道均值仍在 0.3 以上过度去雾则会让图像发黑。比值R定义成mean(dark_channel(J,15)) / mean(dark_channel(I,15))合理的取值范围在 0.1 到 0.3。这个指标不能替代 SSIM但对单幅无参考图像的去雾验收足够直观。6.2 网格搜索最优参数并输出对比图多张测试图同时调参时手工调会花很长时间。写一个双层循环让程序在候选参数里自动搜索最优组合是更稳妥的工程做法function [best_patch, best_omega] grid_search(I) target_ratio 0.2; best_score inf; for patch 9:4:25 for omega 0.85:0.03:0.97 J hazeremoval(I, patch, omega, 0.1); ratio mean(dark_channel(J, 15), all) / ... max(mean(dark_channel(I, 15), all), eps); score abs(ratio - target_ratio) 0.1 * (1 - ssim(rgb2gray(J), rgb2gray(I))); if score best_score best_score score; best_patch patch; best_omega omega; end end end end这段代码里的ssim(rgb2gray(J), rgb2gray(I))用来约束恢复图不要偏离原图亮度结构太多避免算法为了压低暗通道均值而把整张图调黑。如果你在 MATLAB 里跑这段注意mean(..., all)需要 R2018b 以上旧版本改成mean(dark(:))。搜索完成后把原图、暗通道、透射率、恢复结果拼成一张四宫格再保存成 PNG 用于答辩展示。保存时要留意输出分辨率建议把 figure 的PaperPosition设成和图像宽高一致避免文字缩放后发虚。本文还有配套的精品资源点击获取
网站建设高端定制企业官网