
1. 先搞清楚加雾到底在模拟什么物理过程加雾不是简简单单地把图像变白、变灰、降低对比度。如果只是这样做出来的图要么像蒙了一层塑料膜要么像曝光过度完全没有雾天那种“空气里有悬浮颗粒”的层次感。在做MATLAB图像合成加雾之前我建议你先花十分钟想清楚真实雾天里相机传感器接收到的光到底经历了什么。雾天成像可以拆成两部分。第一部分是场景物体本身反射的光这部分光经过空气中悬浮水滴、尘埃的衰减到达相机时已经削弱了一部分对应物理里的“透射衰减”。第二部分是环境光太阳光、天空光的散射光直接被空气中微粒散射进相机镜头这部分光不携带场景细节只贡献一层叠加的光幕对应大气散射模型里的“大气光”。最终拍到的带雾图像是这两部分按一定比例叠加的结果。这个物理过程用公式表达就是I(x) J(x)·t(x) A·(1 - t(x))其中I(x)是带雾图像J(x)是清晰无雾图像A是大气光值t(x)是透射率。t(x)越小说明衰减越厉害雾越浓t(x)越接近1图像越清晰。这个模型最早是McCartney在1976年提出的后来被去雾算法广泛应用但反过来用——给清晰图像“人工加雾”——一样成立只是我们要主动构造J、A和t然后把它们合回去。这里有一个容易搞混的点。加雾不是图像合成完之后的“滤镜”而是合成过程中的“光照合成”。你在MATLAB里用alpha通道把两张图合成一张那是空间域的叠加加雾是亮度域的叠加。空间域决定“哪些像素来自哪张图”亮度域的叠加决定“这些像素最终看起来受雾影响多少”。如果你先合成再做亮度修正边缘容易出现脏感因为两张图的拼接边界在加雾后仍然突兀。更自然的做法是先把场景合成干净再基于深度或区域信息赋予不同的透射率让雾同时影响合成后的整体画面。我自己最常用的场景是自动驾驶仿真数据集生成。真实雨天雾天数据太难采集而且不同路段浓度不一样用MATLAB对合成场景批量加雾再配合深度图做透射率分布能快速生成几千张训练图成本几乎为零。除此之外遥感图像模拟、监控视频画质评估、无人机航拍仿真也会用到这个思路。2. 环境准备与核心工具链梳理2.1 MATLAB版本与工具箱选择MATLAB做图像处理最核心的工具箱是Image Processing Toolbox。加雾这件事理论上只要会用矩阵运算就行连工具箱都能不用但有几个函数还是能省不少事imread、im2double、repmat、imshow、imwrite这些是基本功如果要生成深度渐变、做掩膜还会用到meshgrid、fspecial或imgaussfilt。版本方面我建议至少R2020a以上。倒不是说老版本跑不了而是新版对图像类型处理更友好比如im2double和im2gray的行为更一致转uint8再转double不容易出现数据溢出。另外新版imageshow比旧版imshow在交互缩放上更好用调试局部雾效果时方便很多。如果你做的是批量数据生成建议加一个Parallel Computing Toolboxparfor直接并行处理几千张图省下来的时间能让你多调好几版参数。这个工具箱不是必须但一旦图量上来你会感谢自己装了它。2.2 关键函数的功能边界先梳理一下这个项目里最常用的几个函数别过度依赖但要知道它们适合干什么。imread读图但要注意它返回的可能是uint8、uint16或logical类型。如果你用旧版MATLABREADME里没强调类型很容易在后续矩阵运算时炸掉。im2double把图像归一化到[0,1]区间同时转为double类型。这是加雾计算的基本盘所有乘法、指数运算都在这之后进行。repmat把灰度矩阵复制成三通道用于和RGB图像做逐像素乘法。很多人喜欢在这用cat(3, t, t, t)效果一样看个人习惯。meshgrid生成x、y网格坐标用于构造深度渐变透射率图。这是实现“近处清楚、远处模糊”的关键工具。imgaussfilt高斯滤波。有时候直接用rand生成噪声透射率会太碎先滤波一下雾的分布会更自然。imwrite导出图像。注意导出uint8格式时记得先把double数据im2uint8直接写double会得到全黑或奇怪的报错。2.3 数据流设计从输入到输出的完整链路加雾处理的代码不长但数据链路的顺序特别重要。我踩过最蠢的坑是先对uint8图像直接做乘法结果非负像素全被截断成255输出一片白。正确链路是读入原图uint8转double并归一化到[0,1]读取或生成对应的深度图、掩膜、透射率图按公式做矩阵运算得到带雾图像double用imshow查看效果必要时用imhist检查直方图调整参数输出最终图像转回uint8后imwrite这六步看着简单但每一步都有细节。第2步漏了后面全错第4步矩阵维度不匹配MATLAB会直接报错第6步不转数据类型存下来的图会跟预览差别很大。我的习惯是在关键节点用size、class、min/max三个命令检查变量状态尤其在debug一个莫名其妙的“图怎么变白了”问题时这招十次里有八次能快速定位。3. 图像合成加雾的完整代码实现3.1 基础版均匀雾效三步走最简单的加雾效果整张图同一浓度适合模拟阴天、霾天气。代码如下% 读取图像并归一化 im imread(scene.jpg); im im2double(im); % 设置大气光和透射率 A [1, 1, 1]; % 白色雾 t 0.55; % 透射率数值越小雾越浓 % 合成加雾图像 J im .* t A .* (1 - t); % 显示与保存 imshow(J); imwrite(im2uint8(J), scene_fog.jpg);这里A取了[1,1,1]也就是纯白光。如果你在白天薄雾场景拍过照片会看到远处物体不是单纯变白而是带一点灰蓝调那是因为大气光本身也受天空散射影响。想模拟这种效果可以把A改成[0.95, 0.95, 1]之类的偏蓝值不同色道的衰减也就会有差异。这个基础版有个问题它是全局均匀加雾没有远近层次。真实雾天里3米外的树可能还很清晰50米外的建筑就只剩轮廓了。全局均匀雾只适合做“霾”效果或者素材本身景深很浅的情况。3.2 进阶版用深度图做渐变透射率要让雾有纵深关键在于让透射率t随深度变化。公式不变t变成一张和图像同尺寸的矩阵。比如模拟一条从近到远的道路远处雾浓% 读取图像 im im2double(imread(road.jpg)); [H, W, ~] size(im); % 构造深度矩阵从下到上或从近到远线性增大 [x, y] meshgrid(1:W, 1:H); depth (y - 1) / (H - 1); % 归一化到[0,1]底部为0顶部为1 % 透射率随深度指数衰减 beta 1.2; % 雾气衰减系数 t exp(-beta * depth); t repmat(t, [1, 1, 3]); % 扩展成三通道 % 大气光 A [1, 1, 1]; % 合成 J im .* t A .* (1 - t); imshow(J);depth的构造是这个方法的核心。你可以根据实际场景的深度关系改depth的生成方式如果是无人机下视拍地面可能中心近、四周远如果是山景可能是下方近、上方远。beta值控制衰减快慢beta越大远处雾越浓。这里我补充一个物理逻辑透射率和深度的关系是t exp(-βd)不是线性关系。很多人会直觉上觉得线性递减就行但真实大气散射是指数衰减。用指数近处雾的变化比较平缓远处急剧加深视觉上更接近实拍雾天。3.3 进阶版二合成两张图后统一加雾如果项目叫“图像合成加雾”大概率不只处理一张图而是把前景、背景或者其他素材合成到同一个画面再加雾统一光照氛围。比如你要把一个人物抠出来贴到山区背景然后模拟“清晨山区薄雾”的效果。步骤分三段。第一段前景背景做空间域合成% 读取前景和背景 fg im2double(imread(person.png)); % 带alpha通道的PNG bg im2double(imread(mountain.jpg)); fg_rgb fg(:, :, 1:3); fg_alpha fg(:, :, 4); % alpha通道 % 合成抠图边缘可能有色差先高斯羽化alpha边缘 fg_alpha_smooth imgaussfilt(fg_alpha, 1.5); comp bg .* (1 - fg_alpha_smooth) fg_rgb .* fg_alpha_smooth;第二段构造深度图。背景整体看作远处前景人物看作近处。深度值需要和画面内容匹配人物所在区域深度小雾薄背景区域深度大雾浓。depth zeros(size(bg_rgb, 1), size(bg_rgb, 2)); % 实际上是单通道 depth 0.8; % 默认背景深度0.8 depth repmat(depth, size(bg_depth, 1), size(bg_depth, 2)); % 然后用alpha掩膜把前景区域深度设小 fg_region fg_alpha_smooth 0.5; depth(fg_region) 0.2;第三段基于深度和通道做加雾合成beta 0.6; t exp(-beta * depth); t3 repmat(t, [1, 1, 3]); A [0.9, 0.9, 0.95]; % 偏蓝的清晨大气光 J comp .* t3 A .* (1 - t3);到这里你会看到人物和背景不仅空间上拼在了一起光照上也有了统一的雾面像是真的站在那里拍的。边缘处因为之前做了羽化雾的过渡也自然。3.4 带局部雾效的掩膜法想去哪儿去哪儿全局浓度或者简单渐变通常还不够很多实际需求是“只在某个区域有雾”——比如山间云雾缭绕的带状雾、清晨湖面贴地雾。这时需要一个局部掩膜来控制透射率。构造掩膜的方法不唯一最简单的办法是用交互式工具或手绘二值图。比如在MATLAB里可以用drawfreehand交互选区域也可以直接用代码生成圆形、椭圆渐变% 图像尺寸 [H, W, ~] size(im); [x, y] meshgrid(1:W, 1:H); % 椭圆区域掩膜 cx W / 2; cy H / 2; rx W * 0.3; ry H * 0.15; mask ((x - cx) / rx).^2 ((y - cy) / ry).^2 1; mask double(mask); % 对掩膜羽化避免边缘生硬 mask imgaussfilt(mask, 20);之后把掩膜映射到透射率上。期望逻辑掩膜1的区域浓雾0的区域无雾。那么透射率t 1 - 0.7 * mask; % 透射率从1降到0.3再乘一个整体浓度系数就能控制局部的雾厚。我把mask和t之间的关系理解成“雾的分布图”而不是“透射率图”因为这样更直观你想让雾出现在哪里就先在mask上画哪里然后约定mask1表示雾最浓、透射率最低。如果你连交互式都不想用还可以从图像本身提取区域。比如根据亮度天空区域通常更亮给天空加雾用im本身的亮度值做mask就行。这就是为什么图像合成加雾能和语义分割、显著性检测这类前置处理搭在一起。4. 参数调优与效果对比雾的浓度、颜色和均匀性4.1 透射率与浓度别只调一个系数很多人调参时只会动t或者beta结果要么雾太厚细节全丢要么雾太薄变化不明显。关键是搞清楚每个参数的物理含义和交互关系。透射率t的取值范围是0到1。t1意味着完全无雾t0意味着物体反射光完全被阻挡图像只剩下大气光A的颜色。实际经验是t在0.8以上只有轻微薄雾适合做“清晨轻雾”氛围t在0.5~0.8中等雾远景轮廓还在颜色偏灰t在0.2~0.5浓雾近景勉强能看远景接近纯色t在0.2以下几乎完全被雾覆盖除非模拟极端天气否则不建议如果用的是深度渐变模型控制的是β。β的作用是改变“雾随距离增长的速度”。β0.3一百米外才明显有雾β1.5十米外就开始发虚。0.6~1.0是我个人最常用的区间覆盖大部分中等雾场景。4.2 大气光颜色决定雾的整体色温大气光A不再是白色时雾的观感会发生明显变化。[1,1,1]标准白雾适用性强[0.95,0.95,1]偏蓝模拟天空散射光主导的雾[1,0.95,0.9]偏黄模拟沙尘或日落余晖下的雾[0.8,0.85,0.9]灰冷调模拟阴雨天气调整A时要注意一个细节A的数值会影响图像整体亮度。A1时雾最浓的地方亮度接近255A0.8时雾区会偏灰黑看上去像阴天。如果你的雾天图像整体偏暗可以适当调高A。4.3 均匀性、噪声与颗粒感真实雾天不是绝对均匀的空气中水汽有湍流远处雾还会有轻微颗粒感。纯公式算出来的雾过于“干净”像软件渲染出来的3D雾效。如果你追求照片级真实感可以在透射率图上加一点低频噪声% 给透射率加随机扰动 noise randn(size(t)); noise imgaussfilt(noise, 30); % 低频噪声 t t .* (1 0.08 * noise); t min(max(t, 0), 1);这里有两个关键点扰动系数不能大0.05~0.1比较合适否则雾会出现一块浓一块淡的斑驳噪声必须高斯滤波滤掉高频成分才能模拟大尺度空气湍流而不是感光元件的噪点。4.4 对比试验同一张图在不同参数下的结果我用一张标准测试图比如住宅街景做了几步对比结果如下表参数组合视觉效果适用场景t0.85, A[1,1,1]极淡的白薄雾整体亮度和对比度略微下降阴天镀膜感t0.6, A[1,1,1]常见雾天近景清晰远景有点灰自动驾驶黄昏数据t0.6, A[0.9,0.9,1]中等灰蓝雾莫名有“寒气”冬季清早、山区depth渐变, beta1.0近处清远处白纵深感强道路监控、航拍局部mask, 最大雾厚0.7想去哪去哪画面叙事感强艺术视角、游戏CG从这个表可以看出参数是组合拳不能只看单一数值。我调参的顺序一般是先定透射率下限雾最浓程度再定大气光颜色色温最后加噪声和边缘处理。反过来调容易陷入局部最优怎么看都差点意思。5. 实测中容易踩的坑完整排查链路5.1 图像突然全白或全黑的排查思路这个问题新手遇到概率极高。排查链路如下第一步看变量类型。MATLAB命令行输入class(im)如果是uint8而你的代码里直接做了im .* 0.5那结果会被截断成0~255的整数明亮区域会直接变成127附近暗部变成0以下直接被钳到0。正确做法是先用im2double。第二步检查大气光A的维度。size(A)应该是1×3如果是单个数广播的时候MATLAB可能报维度不匹配。第三步检查透射率t的维度。如果是全局标量没问题如果是矩阵确保size(t)和size(im(:,:,1))一致。矩阵维度对不上时MATLAB会报错但有一种隐蔽情况是t是H×W×1图像是H×W×3此时用.*可能会报错需要用repmat扩展。第四步查看中间结果的min/max。如果max(im)1或min(im)0说明在double域的计算出了问题比如没归一化就做指数运算。5.2 边缘色差和光晕怎么处理做前景背景合成再加雾人物轮廓边缘常出现两种问题一是边缘有半透明色边二是加雾后在轮廓周边形成一圈光晕。先说色边。前景图如果是JPEG抠图边缘残留背景颜色和背景合成后色边在加雾后会被放大。解决办法是在合成前对alpha通道做轻微侵蚀或羽化削减半透明边缘alpha im2double(fg(:, :, 4)); alpha_eroded imerode(alpha, strel(disk, 1)); alpha_smooth imgaussfilt(alpha_eroded, 1.2);再说光晕。加雾时透射率t在边缘突变导致雾的强弱在边缘跳变看起来像边缘轮廓发光。解决办法是平滑透射率的过渡区域。如果把注意力放在mask上它和alpha边缘重叠的地方需要用较大标准差的高斯平滑比如imgaussfilt(mask, 20)让过渡带延展到几十个像素光晕就消失得无影无踪。5.3 批量处理时的图形窗口内存溢出如果你用imshow在循环里预览效果跑了几百张图后MATLAB可能变慢甚至“Out of memory”。这不是加雾计算的问题是图形句柄没释放。处理办法显示后立即close(gcf)或者干脆在批量模式里不预览只保存到文件。我通常在循环外用figure单独调试一张确认无误后再全量跑。5.4 不同格式图像的位深和色彩空间MATLAB里如果输入图像是16位TIFF或浮点HDR直接转double后可能超出[0,1]范围。HDR图像可能值远大于1这时加雾公式里的指数项可能失去意义。统一先做亮度归一化im im ./ max(im(:))再进入加雾流程。另外注意RGB和YCbCr差异。如果直接在Lab或HSV空间做加雾效果会有些微妙不同。我的建议是默认在RGB空间做因为大气散射模型本身就是基于RGB三通道的独立衰减。想调整色彩氛围可以在加雾前后增加颜色映射变换而不要轻易在Lab空间重写公式——除非你明确知道自己在做什么色彩管理工作。6. 扩展思路与二次开发方向6.1 把加雾变成数据增强管道合成加雾最实用的大方向是给深度学习目标检测或分割模型做训练数据增强。思路很直接已有清晰无雾的数据集通过加雾生成带雾样本增强模型在恶劣天气下的鲁棒性。具体落地时有几个经验可以分享每张原图生成多种雾浓度版本比如透射率从0.9到0.3均匀采样不要只输出一种浓度使用随机深度图避免同一位置雾厚模式固定对mask做随机形态学操作模拟不同风向、地形造成的雾分布差异配合翻转、旋转做联合变换保证加雾后的图像和标签一一对应我在实际项目中用这种方式生成过两万张带雾市政道路图配合YOLO系列模型训练雨天雾天的检测准确率提升了将近10个百分点。成本只是几百行MATLAB代码和数据集的整理时间。6.2 结合真实深度传感器数据做物理仿真如果你手边有激光雷达或深度相机输出的深度图加雾质量会再上一个台阶。用真实深度d替换前面代码里的简化depth然后一步到位t exp(-beta * d_normalized);这个做法能根据场景真实的几何关系让雾精确地出现在楼后、树后、街道深处而不是靠手工画mask。做三维重建、城市级数字孪生的朋友通常会对这个思路感兴趣。6.3 做去雾算法的“反向验证”还有一个很少人提但很有意思的场景加雾可以当作去雾算法的测试工具。你想验证一个新的去雾算法好不好与其找大量真实雾天图像很难有Ground Truth不如自己在清晰图上加雾再把带雾图交给去雾算法最后和原始清晰图对比PSNR、SSIM直接算。这样整个闭环都掌握在自己手里能够方便地验证算法对不同浓度、不同颜色大气光的适应能力。顺便说一句如果你以后想从“加雾”转向“去雾”MATLAB自带的imreducehaze函数其实就是上面公式的逆过程但它内部做了不少自动估计大气光和透射率的优化。你自己写的加雾过程正好可以用来设定对照实验帮助理解imreducehaze内置参数的作用。6.4 用App Designer做可视化调参工具最后补一个工程化的思路。如果你经常需要给别人演示或做非编程人员可用的工具可以考虑用MATLAB App Designer做一个小的加雾工具界面左边选择图像中间实时预览右边拖动滑块控制透射率、大气光RGB、噪声幅度。底层逻辑还是这套公式只是包了一层交互壳。我自己给实验室学生做过一个他们拿去生成数据集反馈说比命令行直观太多也少了改参数改错行列的烦恼。核心代码其实没变只是把每个输入都绑定成控件回调函数里的变量。App Designer关于图像显示的坑集中在imshow在坐标轴控件里的句柄更新记得每次刷新前cla清空当前坐标轴。其他的和普通脚本没差别。