ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB图像加雾实战:基于大气散射模型与深度图的物理合成方法

MATLAB图像加雾实战:基于大气散射模型与深度图的物理合成方法 从接到“MATLAB图像合成加雾”这个活儿开始我猜你大概率不是在做毕业设计就是在给自动驾驶数据集做仿真补充再不然就是准备拿合成雾图去验证自己的去雾算法。不管哪一种图像合成加雾这件事本身特别容易上手出结果但想把雾加得“像回事儿”尤其是那种近处清晰、远处白茫茫的物理真实感就绕不开背后的成像模型和深度信息。这篇就把我从最开始的“拿灰度图层往上盖”到“基于大气散射模型做逐像素合成”的完整过程写出来代码可以直接抄参数怎么调我也会讲清楚。1. 做图像加雾之前思路先要理清楚1.1 为什么要给图像加雾三个典型场景先把需求说透。图像加雾在工程里最常见的用途有三类你要先想清楚自己是哪一个因为不同需求对应完全不同的实现档次。第一类是视觉效果类。比如做短视频特效、游戏后期、平面设计里的氛围渲染这类需求的核心是“看起来像那么回事”处理速度可以慢但对雾的形态、颜色、动态变化有要求。第二类是算法评测类。很多人在做暗通道去雾、深度学习去雾网络的时候手里没有成对的清晰图和有雾图最稳妥的办法就是自己合成雾图对用来自测指标。这类需求最看重“物理正确性”因为你的去雾算法如果在一个不符合物理规律的人工雾图上表现好那在真实场景里大概率会翻车。第三类是数据增强类比如自动驾驶感知模型训练时缺少雾天样本把正常的街景图像批量加不同浓度的雾扩充训练集让模型在雾天也能稳定工作。我经常遇到有人一上来就直接在MATLAB里用imadd或者灰度叠加做雾做完发现暗处被“糊住”了、亮处又没什么变化原因就是你根本没有按照大气散射模型来做只是在RGB空间里做了一个线性混合。所以这篇文章的思路是先讲物理模型再给代码最后说坑。这样不管你是为了毕设还是为了工作做完心里都是透亮的。1.2 核心数学模型一套公式吃透大气散射模型图像加雾的本质是模拟光线在雾霾介质中传播时发生的散射和衰减。目前行业里最通用、解释性最强的是大气散射模型公式长这样[ I(x) J(x) \cdot t(x) A \cdot (1 - t(x)) ]先拆开解释你以后写代码全靠它。(I(x))是你最后看到的有雾图像也就是我们要生成的图(J(x))是原始清晰图像也就是无雾输入(A)是全局大气光可以简单理解成天空或者远处最亮区域的亮度值(t(x))是透射率表示物体反射的光线经过雾层之后还有多少能到达相机。透射率(t(x))本身由介质的散射系数(\beta)和场景深度(d(x))决定[ t(x) e^{-\beta \cdot d(x)} ]这个式子才是加雾的核心。(\beta)越大雾越浓同样的距离能见度越低(d(x))是每个像素对应的实际物理距离单位通常是米。(x)处的物体离相机越远透射率越小雾对图像的覆盖就越大。当(d(x)0)时(t(x)1)画面完全不雾当(d(x))趋于无穷时(t(x))趋于0画面只剩大气光呈现出白茫茫一片。这个模型的价值在于它把“加雾”这件事从“滤镜式混合”变成了“物理仿真”。你不用再手动调每个区域的透明度只要给定深度图雾气分布自动就有了近浓远淡、远近不同的层次感。1.3 方案选型从“像素直接叠灰”到“物理模型合成”方案上我建议你一步到位直接用物理模型做。但为了让你理解为什么要这么做我可以先告诉你最简单的做法长什么样读入一张图生成一个和原图同尺寸的灰度矩阵比如值全为200的矩阵然后用alpha混合公式[ I alpha \cdot \text{fogLayer} (1 - alpha) \cdot J ]这套做法代码就十行效果是整幅图均匀地被罩上一层白纱没有远近深浅。问题很明显现实中雾不是均匀铺在画面上的近处的物体纹理依旧清晰远处的桥和山才会被雾吞掉。如果你拿这样的图去验证去雾算法算法的暗通道估计都会错乱。所以我的建议是如果只是临时凑个效果均匀加雾够用如果是做数据、做评测、做仿真默认就上大气散射模型加深度图。这也是目前CV领域合成有雾图像的主流标准。后面所有的实操我都会围绕这个物理模型展开。2. 十分钟跑通第一版均匀加雾与基础合成2.1 环境准备与输入数据先说环境。我这里用的是MATLAB R2021a但其实这段代码对版本要求极低凡是能跑基础图像处理的MATLAB都能用不需要额外安装工具箱。唯一建议你把Image Processing Toolbox装上因为里面im2double、im2uint8、imshow这几个函数用起来太顺手了没装的话你得自己写类型转换和显示偏麻烦。测试图像我建议先用一张色调正常、带城市或户外场景的图片比如MATLAB自带的street.jpg或者你随便找一张城市街景。不要用纯色背景图来做验证那样看不出深度和雾浓度的变化。我在测试过程中自己反复用的是一张无人机俯拍的工业园区照片近处有道路、树、车远处有建筑轮廓层次感很足加雾后效果一眼就能看出好坏。2.2 最简单的均匀加雾代码先上一个不加深度信息的“快速版”适合你确认整体流程没问题或者做批量数据初筛时用。代码如下% 均匀加雾快速版 img imread(street.jpg); % 读入原始清晰图像 img im2double(img); % 转double范围0~1 fogColor [0.8 0.85 0.9]; % 雾的颜色偏白且微微偏蓝 alpha 0.5; % 雾的强度0~1越大雾越浓 % 大气散射模型的简化假设深度处处相同t为常数 foggy (1 - alpha) * img alpha * fogColor; figure; subplot(1,2,1); imshow(img); title(原图); subplot(1,2,2); imshow(foggy); title(均匀加雾); imwrite(foggy, foggy_uniform.png);这段代码里fogColor我特意给的是[0.8 0.85 0.9]而不是[1 1 1]纯白。原因在于真实雾气对光线的散射有轻微的波长依赖性偏向蓝灰色直接用纯白会显得“塑料感”很强。alpha是雾强度对应大气散射模型里全局透射率的倒数关系(alpha)越大相当于雾越浓。跑完这个版本你就有第一张合成雾图了。不过说句实话这个版本只能用来跑通流程别指望它能交付。因为你会发现画面近处地面的纹理已经被糊掉一半而远处该糊的地方也就那样。接下来我带你把它升级成带深度感知的物理版本。2.3 理解alpha混合的局限这里重点解释一下为什么均匀加雾只能算“入门操作”。均匀加雾相当于假设场景中每个像素到相机的距离都是一样的这在实际图像里几乎不成立。你的画面里路面延伸出去距离在连续变化天空作为最远的背景应该完全被雾覆盖。但均匀加雾对所有位置一视同仁结果就是近景细节丢失严重远景和天空反而没有表现出应有的“淹没感”。如果只是给视频加个滤镜做氛围这种局限还能接受但你要是拿来做算法验证这种雾图会造成严重误导。比如去雾算法里的暗通道先验方法它假设有雾图像的暗通道在局部区域趋近于大气光值如果你用均匀加雾的图去测试算法会把整张图当成浓雾来处理导致输出颜色严重失真。所以我的建议非常明确哪怕稍微麻烦一点也用带深度的模型。3. 进阶核心基于深度图的物理模型加雾3.1 深度图从哪里来四种获取方式对比现在到了让雾“活”起来的关键——深度图。既然透射率(t(x))要靠深度(d(x))算那深度图怎么来我按使用门槛从低到高给你梳理一下。方式一手动构造模拟深度图。如果你并不需要物理精确只是想要“近处清楚、远处雾”的自然层次完全可以自己生成一个渐变矩阵。比如想让图像上方远、下方近就从上到下线性增大深度值想让中间区域突出就用高斯形状构造深度。这种方式最灵活三分钟就能搞定。方式二从双目/多目图像用视差估计深度。如果你手里恰好有一对双目相机拍摄的图像可以用disparity函数估算视差图再换算成深度。精确度更高但需要标定参数步骤繁琐。方式三用深度估计模型或网络生成。比如在Python侧用MiDaS这类单目深度估计模型输出深度图后存成.mat或.png再导入MATLAB。这种方式效果最真实适合对合成质量有高要求的场景。方式四从三维模型或点云投影得到精确深度。如果你做的是自动驾驶仿真场景本身就是三维重建出来的直接投影深度通道即可。这是精度最高的方式但门槛也最高。我做数据集时常用的是方式三用MiDaS模型生成深度图再在MATLAB里做后续合成。整个过程不复杂深度图的精度也足够。如果你只是做毕设演示方式一完全够用。3.2 手动构建深度图的一个通用示例这里我提供一个生成“上远下近”线性深度图的MATLAB函数可以快速在任意尺寸图像上使用function depthMap makeLinearDepth(h, w, nearDist, farDist) % makeLinearDepth 生成从上到下由近到远的线性深度图 % h, w : 图像高和宽 % nearDist : 图像底部最远处距离单位米 % farDist : 图像顶部最远处距离单位米 [Y, ~] ndgrid(1:h, 1:w); depthMap nearDist (farDist - nearDist) * (Y - 1) / (h - 1); end调用方式% 读图 img imread(street.jpg); [h, w, ~] size(img); % 假设图像底部约10米顶部约300米 depth makeLinearDepth(h, w, 10, 300); % 可视化深度图 figure; imagesc(depth); axis image; colorbar; title(线性深度图);这段代码生成的深度图像素值代表每个位置的物理距离。底部路面10米顶部天空300米。如果你觉得线性太生硬可以把(Y)坐标做平方或指数映射得到近处距离变化快、远处变化慢的效果更贴近透视关系depth nearDist (farDist - nearDist) * ((Y - 1) / (h - 1)).^2;这种方式会让近处深度增长缓慢远处迅速拉伸合成出来的雾在近处很淡、远处急剧变浓视觉上更自然。3.3 完整实现基于大气散射模型的加雾函数有了深度图我们就可以写出真正基于物理模型的加雾函数。我把核心实现写成一个独立的MATLAB函数方便你在任何项目里直接调用。function foggy fogSynthesis(clean, depth, beta, A) % FOGSYNTHESIS 基于大气散射模型的图像加雾 % clean : 输入清晰图像MxNx3uint8或double均可 % depth : 深度图MxNdouble单位为米 % beta : 大气散射系数标量值越大雾越浓 % A : 大气光强度标量表示灰白雾1x3向量表示彩色雾 % 输出 : uint8类型的有雾图像 % 统一到double且范围[0,1] clean im2double(clean); % 透射率t(x) exp(-beta * d(x)) t exp(-beta * depth); t min(max(t, 0.02), 1); % 截断到[0.02, 1]避免数值问题 % 扩展到三通道 t3 repmat(t, [1, 1, 3]); % 大气光向量的维度处理 if isscalar(A) A3 cat(3, A, A, A); else A3 reshape(double(A(:)), 1, 1, 3); end % 大气散射模型核心公式 foggy clean .* t3 A3 .* (1 - t3); % 转回uint8范围[0,255] foggy im2uint8(foggy); end这个函数看起来不长但已经包含了几个关键设计。我在t上加了下限0.02而不是让它等于0是为了避免在远景处透射率完全为0时像素直接被大气光代替偶尔会产生黑色噪点。这个细节在实际使用中特别重要很多人合成出来发现天空处有一种“脏”的感觉其实就是在透射率接近0的地方与大气光的计算不够平滑。调用示例img imread(street.jpg); [h, w, ~] size(img); depth makeLinearDepth(h, w, 10, 300); beta 0.03; % 散射系数约能见度130米 A [0.85 0.88 0.92]; % 天空色微微偏蓝 foggy fogSynthesis(img, depth, beta, A); figure; subplot(1,2,1); imshow(img); title(原图); subplot(1,2,2); imshow(foggy); title(物理模型加雾); imwrite(foggy, foggy_physical.png);跑完这张你立刻能看出和均匀加雾的差异近处地面、车辆、行人细节保留得很完整远处建筑逐渐没入雾气天空区域接近纯白。这就是带深度信息的物理模型带来的质感。3.4 散射系数β、大气光A和能见度的换算关系参数选对了效果就成功了一半。这里我专门把β的物理含义说透。在气象学里能见度距离(V)与散射系数(\beta)有一个经典近似关系[ V \approx \frac{3.912}{\beta} ]这个公式的推导基于人眼对比度阈值0.05用在合成图像上很实用。比如你想要一个能见度约130米的雾天那(\beta)就取[ \beta \frac{3.912}{130} \approx 0.03 ]我整理了一个常用参数表你在实际操作中可以直接对着选天气状态能见度V (米)推荐β值视觉感受轻雾500~10000.004~0.008远处轻微泛白薄雾200~3000.013~0.02有一定雾气感中雾100~1500.026~0.04中等强度雾浓雾50~800.05~0.08近处也会受影响特浓雾10~200.2~0.4几乎只能看清近处大气光(A)的选择也有讲究。如果你的输入图像里有天空区域直接用天空区域的平均像素值作为(A)是常见做法如果没有天空就需要凭经验设定。纯白[1,1,1]在大多数情况下过于刺眼我一般用[0.85, 0.88, 0.92]左右能模拟出蓝灰色雾感。如果你想要黄昏时的雾可以适当增加R通道的值比如[0.95, 0.85, 0.8]让雾层带一点暖色调。这点灵活性是均匀加雾方案很难具备的。4. 实战中的坑与排错实录4.1 雾层不自然透射率越界和颜色空间问题我在最早写这个函数的时候犯过一个特别典型的错误没有给透射率做截断结果有雾图像在天空区域出现了一些“斑驳”的暗块。原因就是在(d(x))特别大的地方(t(x))已经小到接近0计算机浮点数再一转uint8误差就被放大了。解决办法就是我在函数里那行t min(max(t, 0.02), 1);加一个下限一切就正常了。另一个常见问题是雾的颜色失真。有人把A设成纯白[1,1,1]之后发现合成图像整体“发飘”黑色细节丢失严重。这是因为纯白大气光在雾区域把RGB三通道同时加到了很亮的值损失了对比度。处理办法是我前面提到的把A调成带轻微蓝或灰的色调人眼对这类自然色雾的接受度高出不少。4.2 加雾之后图像变得很暗或很刺眼“变暗”通常发生在把A设得太低的情况下。比如有人把A设成[0.5, 0.5, 0.5]结果雾不是白的而是灰的整幅图像压暗了。记住大气光本质上是环境光被散射到相机里的能量它应该是接近亮度上限的值这一点即使在没有天空的夜景里也不例外。夜景灯光照明条件下的雾图A值一般取0.3到0.6否则没有雾的闪烁感一片死黑。反过来“很刺眼”是因为整个图像被雾均匀提亮后高光区域失去了层次感。解决办法是保持深度图中近处区域距离合理比如近处至少5到10米让近景透射率尽量接近1保留细节。如果你把深度的最小值设成0米那么画面里有一个无穷近的拍摄平面那里永远不会有雾雾只从远处出现效果会好很多。4.3 性能优化矩阵化代替逐像素循环初学MATLAB的人经常写两个嵌套的for循环去逐像素计算t(x)和加雾公式遇到1920x1080的图就会卡得难受。我见过有人加雾一张1080P的图跑了快两分钟最后才发现是他自己用循环写的。我的建议是从第一版代码开始就坚持矩阵化操作。上面给出的fogSynthesis函数里repmat和点乘运算.*都是对整个矩阵操作的MATLAB内部高度优化处理1080P图像的时间在零点几秒级别。如果你需要给几千张训练图批量加不同参数的雾矩阵化是唯一能实际跑完的方案。批量处理时还可以用parfor并行循环配合不同(\beta)值生成多样化雾图。4.4 常见问题速查表我把这个任务里出现频率最高的问题整理成一个速查表方便你排查问题现象可能原因解决办法整张图均匀发白没有远近层次没有使用深度图生成深度数据用透射率公式替代常量alpha天空区域出现暗斑透射率0.02未截断给透射率加下限0.02雾色发灰发脏大气光A取值过低提高到0.7以上纯白建议改[0.85,0.88,0.92]近处细节全部糊掉深度最小值过小或beta过大深度最近值调大到5米以上降低beta合成后图像曝光刺眼A接近1且beta过低配合能见度公式选择beta避免雾过淡但光过强运行速度极慢使用逐像素循环全部用矩阵点运算用repmat扩展通道批量处理一张张手动调参缺少封装函数写成函数外层循环控制beta和A5. 加雾图像能拿来做什么以及几个扩展方向5.1 用合成雾图验证去雾算法合成雾图最常见的用途就是验证去雾算法。比如暗通道先验去雾算法你手上现在有了大量成对的清晰图和合成雾图就能定量分析去雾前后的PSNR和SSIM。我在做这个方向的时候习惯把去雾结果和原图对比而不是只和有雾图对比这样能直观看出算法有没有过度增强或色彩偏移。特别提醒一句用合成数据评测算法时务必在报告中写明深度图和β参数。因为不同的合成设置会直接影响评测结果。比如你用浅雾数据测出来的PSNR普遍会高一些用浓雾数据测出来的指标会偏低这并不代表算法变差只是因为任务本身难度增加了。相关的评测说明里必须写清这一点否则数据对比没有意义。5.2 与图像合成技术结合的进阶玩法图像合成不只有“给干净图加雾”这一个方向。如果你把多张图像先合成一张全景图或拼接图再统一做加雾处理出来的效果会非常自然。我在一个项目里把无人机拍摄的序列图像先用MATLAB的image stitching合成成一张完整的俯视图然后叠加地形深度信息做雾天仿真成功模拟了大雾天气下的正射影像效果后续直接用于飞行器的视觉导航算法测试。另外你也可以把多尺度雾叠加进同一张图比如在距离近的地方保持清晰在中等距离叠加一层薄薄的白雾在远景处再加一层浓雾用不同区域的掩膜把这几层拼起来。这种多层级雾效果特别有层次感配合MATLAB的roifilt2或自定义掩膜就能实现。5.3 在深度学习数据增强里的一个实用套路现在做深度学习的人喜欢要求“多一点数据”雾天合成正好可以发挥作用。我实验过一个方案用不同β和A组合把一张原图扩成20张不同雾浓度的图标签保留原图然后喂给一个去雾网络训练。训练出来的网络对真实雾图的泛化能力比只用原图训练要强不少。不过要注意一点单纯的线性组合数据会让网络对“均匀雾”产生过拟合真实世界里的雾很少均匀。我的经验是在深度图生成阶段加入随机扰动比如在深度图上加一些高斯噪声模拟场景中局部区域浓度不均匀的现象。这样生成的数据多样性会明显提升网络的实际泛化能力。另外MATLAB里有专门的Deep Learning Toolbox可以配合imageDatastore和augmentedImageDatasource把合成雾图直接做成数据管道不需要手写一堆训练循环。写这套流程的时候记得开启“随机扔出一点深度扰动”的选项能让你的模型在真实场景里更稳。6. 参数调节的一些个人习惯分享给你最后这部分我不讲大道理就说我在多次实验后固定的几套“惯用参数”你可以直接拿去当初始值。对于普通城市街景图像尺寸我习惯定义深度距离从5米到300米β取0.03A取[0.85, 0.88, 0.92]这个搭配得到的雾图在近处保留纹理中远处逐渐模糊视觉上和真实雾天很接近。如果想模拟更严重的雾天β改到0.07能见度迅速降到60米以内近处也会受到明显影响适合做自动驾驶安全测试。每次生成完雾图后我还会再叠加一个强度很低的随机高斯噪声在深度图里幅度大约在2%左右。这个小技巧可以让雾的边界不会那么机械、平滑增添一点点真实世界的“脏感”不少去雾模型在评测时就会在这类细节上拉开差距。另外批处理测试时我喜欢把对比图按三列排布原图、深度图、加雾图。这个习惯陪我做了很多次效果调优一眼就能看出哪个区域深度给得不合理。强烈建议你也做成这样能省下大量反复试错的时间。
返回列表