资讯详情 MATLAB图像合成加雾实战:从大气散射模型到数据增强
📅 2026/10/4 8:58:53
1. 项目概述与核心需求做图像加雾这事的起因其实挺实在的——前阵子我在跑一个视觉检测的模型晴天场景下效果还行一到雾天就开始频繁误判。网上翻了一堆公开数据集要么分辨率不够要么雾浓度跟实际场景对不上后来干脆想着自己用MATLAB合成点带雾图像。这个项目的核心说白了就是两件事第一把多张源图像通过MATLAB的图像合成手段融合成一张新图第二在合成结果上叠加雾效模拟真实大气散射带来的视觉退化。很多初学者会把这两个步骤割裂开看觉得不就是矩阵加减乘除的事但实际跑下来会发现合成和加雾之间是有耦合关系的——合成时选择的权重、混合方式会直接影响后面雾效的自然程度。为什么非要用MATLAB而不用Python说实话我没有立场去吹某个工具但MATLAB在图像处理这个领域确实有它不可替代的地方。图像在MATLAB里天然就是矩阵所有操作都是矩阵运算调试的时候可以直接imshow看中间结果图像合成加雾这种流程用MATLAB写起来最直接代码量远小于同等功能的Python实现。而且MATLAB自带的图像处理工具箱Image Processing Toolbox里imadd、imlincomb、imfuse这些函数本身就是为图像合成设计的性能也做了优化。这篇内容适合谁看正在做图像增强、目标检测数据增强的研究生需要生成雾天训练样本的算法工程师以及MATLAB图像处理课程的初学者。我会把从零开始的完整思路、代码、参数调试、翻车记录全部整理出来你照着跑就能用。2. 图像合成的基本玩法2.1 三种最常用的合成方式图像合成不是说随便把两张图叠加在一起就叫合成了。我实际用下来MATLAB里最常用的有这三种方法每种用到的场景完全不一样直接像素替换像素级合成这算是合成最底层的操作。把图像A的一个区域直接用图像B的对应区域覆盖掉核心就是矩阵索引。imgA imread(scene1.jpg); % 底图 imgB imread(object.png); % 要贴上去的物体 % 把B图贴到A图的左上角起点(100, 200)处 imgA(100:100size(imgB,1)-1, 200:200size(imgB,2)-1, :) imgB;这种操作适合做目标嵌入比如给一张干净的街道图贴上一个人或一辆车的截图。但有个坑如果你直接这么覆盖边缘会非常生硬像贴纸一样后面我单独讲怎么处理边缘过渡。加权融合alpha混合这张方式用的最多其实就是一个公式合成像素 权重1 × 像素A 权重2 × 像素B。权重之和一般等于1。MATLAB里最标准的实现是用imlincomb比手写循环快得多。blended imlincomb(0.6, imgA, 0.4, imgB);加权融合适合做两张相似场景的过渡合成比如同一地点不同时段的照片做融合能做出时间推移的效果。权重的大小直接决定哪张图占主导权重大的一方信息保留更多。多尺度融合金字塔融合这是进阶玩法适合做全景拼接或是HDR合成这类要求比较高的场合。思路是把图像分解成不同频带的拉普拉斯金字塔在每个频带上分别做加权融合最后重建。这种方法能解决简单的线性融合带来的边缘模糊问题。MATLAB里虽然没有直接的拉普拉斯金字塔融合函数但可以用laplacian_pyramid自己搭配合pyramid_reconstruct重建。这个方法代码量多一些不过做出来效果很专业。2.2 为什么推荐加权融合作为加雾前的合成方式回到加雾这个场景。我做加雾处理前的合成目标是为了得到一张内容可控的清晰底图然后在这个底图上去加雾。加权融合之所以最合适有几个很实际的原因第一线性操作不会引入非线性伪影。你用融合函数合成出来的底图每个像素值都是源图像的线性组合这符合后续加雾公式的假设。如果用多尺度融合图像边缘会带一些高频增强的痕迹加雾时这些痕迹会被放大看起来不自然。第二权重可调方便你做对比实验。加雾效果的好坏跟底图本身的内容复杂度有关系。用加权融合你能通过调节两张图的混合比例生成一系列不同复杂度的底图再对每个底图加雾这样就能系统评估算法在不同场景下的鲁棒性而不是只能测一两张图。第三计算开销低。合成只是一次矩阵运算几乎不耗时方便批量生成样本数据。3. 图像加雾的核心原理3.1 雾是怎么让图像变模糊的在说代码之前必须把雾的物理模型讲清楚。不加雾直接调低对比度、加模糊做出来的东西一眼假——为什么因为真实雾天降质是有明确物理过程的。图像加雾几乎都基于McCartney于1976年提出的大气散射模型公式长这样I(x) J(x) × t(x) A × (1 - t(x))先解释这几个变量的含义这属于必须死磕的核心知识I(x)观测到的图像也就是你相机拍到的带雾图像J(x)场景辐射度也就是清晰无雾的图像你手中的底图A全局大气光通常认为是天空区域的亮度值t(x)透射率取值范围在0到1之间表示场景光经过大气衰减后到达相机的比例推导一下逻辑你就明白了相机接收到的光其实由两部分组成一部分是场景本身反射的光经过大气衰减后剩下来的分量J × t另一部分是大气光散射进入相机光路的杂散光A × (1-t)。当t(x) 1时表示完全没有衰减I(x) J(x)图像清晰当t(x)越接近0表示场景光几乎被衰减完了图像基本上就是大气光的颜色也就是一片灰蒙蒙的。这就是为什么我能通过数学公式来控制雾的浓度——只要控制t(x)和A这两个关键参数就行。3.2 透射率与景深的关系真实场景中透射率跟距离是强相关的。物体离相机越远光在其传播路径上被散射得越厉害到达相机的光越少透射率越低。这个关系通常写成t(x) exp(-β × d(x))β是大气散射系数控制雾的浓度d(x)是场景点与相机之间的距离也就是深度把这个公式加进大气散射模型就变成了I(x) J(x) × exp(-β × d(x)) A × (1 - exp(-β × d(x)))这里的β要解释一下它对应就是雾的浓度。β越大雾越浓。真实大气中β跟波长有关所以远距离的物体通常带点蓝色调但做合成时为了简化可以把它当作一个全局常数效果已经很好了。从公式能看出只要确定了清晰底图J(x)、深度图d(x)、大气光A和散射系数β这四个量雾天图像就能精确地生成出来。对做数据增强的人来说这本质就是一个可控的图像生成管线改β就能控制雾浓度改A就能控制雾的颜色偏暖还是偏冷。4. 加雾处理的完整代码实现4.1 从原理到代码的映射原理搞清楚了接下来就是把它翻译成MATLAB代码。先说明一下技术路线我默认先把图像转成double类型因为指数运算和乘法在uint8下精度不够容易出现截断误差。很多教程不转类型直接算结果出来图像偏暗或者有奇怪的色块root cause往往就在这里。大气光A的估算我这里采用的是直接取全图最亮像素的近似方法效果跟暗通道先验法算出来的差不多对合成数据来说足够了。如果你想做得更精细可以用暗通道图取前0.1%最亮像素的平均值作为A但代码会复杂一些。我优先选简单方案保证流程先跑通。4.2 深度图的生成策略合成加雾跟真实图像去雾不同真实图像去雾需要估计深度但合成加雾大多数情况下是自己控制深度图。如果你的底图本身带深度信息就方便了直接用就行但大部分情况下没有那就得自己想办法伪造一个深度图。我常用的有三种方式渐变深度——距离随水平方向线性或非线性递增适合模拟空旷场景道路、草原。% 图像尺寸 [h, w, ~] size(img); depth repmat(linspace(0, 1, w), h, 1);分块深度——近处小块区域清楚远处大块区域模糊适合模拟有遮挡的场景。随机深度——用高斯随机场生成连续变化的深度适合模拟地形起伏不定的场景。这里有个经验深度图一定要平滑不能有突变。为什么因为透射率是深度坐标的指数函数如果深度图有跳变透射率也会有跳变反映在图像上就是雾浓度突变形成非常明显的水平突变条带一眼看上去就是Bug。4.3 完整代码我把代码组织成一个可复用的函数输入清晰图像输出加雾结果。function fog_img add_fog(img, beta, A, depth_type) % add_fog 图像加雾函数 % 输入: % img - 清晰的RGB图像 (uint8类型) % beta - 雾浓度系数范围通常取0.5~2 % A - 大气光值RGB分量范围0~1 % depth_type - 深度图类型: linear, block, random % 输出: % fog_img - 加雾后的图像 % 转uint8为double范围0~1 img im2double(img); [h, w, ~] size(img); % 生成深度图值域0~1越大越远 switch depth_type case linear depth repmat(linspace(0, 1, w), h, 1); case block depth zeros(h, w); depth(:, floor(w*0.3):end) 0.5; depth(:, floor(w*0.7):end) 1.0; % 高斯模糊平滑深度图消除突变 depth imgaussfilt(depth, 20); case random depth randn(h, w); depth imgaussfilt(depth, 30); depth (depth - min(depth(:))) / (max(depth(:)) - min(depth(:))); otherwise error(未知的深度图类型); end % 计算透射率 t t exp(-beta * depth); % 把透射率扩展成三通道 t3 cat(3, t, t, t); % 大气光向量归一化后 A_vec reshape(A, 1, 1, 3); % 应用大气散射模型 fog_img img .* t3 A_vec .* (1 - t3); % 转回uint8 fog_img im2uint8(fog_img); end调用方式clear; clc; close all; img imread(street.jpg); foggy add_fog(img, 1.2, [0.78 0.8 0.82], linear); figure; subplot(1,2,1); imshow(img); title(Original); subplot(1,2,2); imshow(foggy); title(Foggy);4.4 参数调节的具体实验记录我把参数实验的结果整理成了一张表方便你直观看出各个参数对最终效果的敏感度。这里要强调一下是我在同一张图一张相对复杂的街道图上做的实验beta值A值深度图类型效果表现0.2[0.8 0.8 0.8]linear轻微薄雾细节完全保留适用于轻度天气模拟0.8[0.8 0.8 0.8]linear中等浓度雾近处清晰远处模糊最常用区间1.5[0.8 0.8 0.8]linear浓雾远处细节几乎丢失整图对比度很低3.0[0.8 0.8 0.8]linear极端浓雾整张图灰白一片基本只有轮廓1.0[0.5 0.6 0.7]linear灰蓝色调雾偏冷色有清晨阴天的感觉1.0[0.9 0.85 0.8]linear黄褐色调雾偏暖色有沙尘或黄昏的效果1.0[0.8 0.8 0.8]block纵深方向上雾浓度分层适合模拟大雾聚集效果1.0[0.8 0.8 0.8]random雾浓度随机起伏适合树林、山地等复杂地形4.5 合成与加雾的串联使用到这里可以把两个部分组装起来。我通常的流程是先加载多张源图像按一定权重合成一张清晰的底图再对这个底图做加雾。img1 imread(ground_1.jpg); % 近景 img2 imread(ground_2.jpg); % 远景 % 合成底图 base imlincomb(0.5, img1, 0.5, img2); % 加雾 foggy add_fog(base, 1.0, [0.8 0.8 0.8], linear);这个操作看起来简单但有个细节需要留意合成前最好对两张图做亮度均衡不然合成后的底图本身明暗差异就很大加雾后这些差异会被雾效放大出现亮度不均匀的斑点。关于亮度均衡可以用histeq做直方图均衡化或者用imhistmatch把一张图的直方图匹配到另一张图上效果立竿见影。5. 图像合成的进阶处理技巧5.1 让合成边缘更自然的羽化处理之前提到直接像素替换的边缘会生硬现在专门说一下解决思路。羽化的核心思想是对融合区域做逐像素权重过渡。具体做法先确定一个合成掩膜mask不需要精确的alpha抠图只需要一个大致的范围然后对这个mask做高斯模糊使边缘变成渐变过渡再按这个浮点mask在多张图之间做混合。function blended featherComposite(base, overlay, mask, blurSize) % 对mask做高斯模糊得到软过渡 maskSmooth imgaussfilt(im2double(mask), blurSize); % 扩展成3通道作为权重 mask3 cat(3, maskSmooth, maskSmooth, maskSmooth); % 加权融合 baseD im2double(base); overD im2double(overlay); blended overD .* mask3 baseD .* (1 - mask3); blended im2uint8(blended); endmask本身可以用roipoly画一个多边形区域也可以用亮度阈值分割生成。模糊半径blurSize一般取10到50具体的取决于图像尺寸和你想要过渡区有多宽。我以前刚做的时候不明白这个参数的作用设太小边缘还是会有痕迹设太大整个物体也跟着半透明了调试了几轮才找到手感。5.2 频域视角的融合优化如果只做线性混合或羽化部分场景下合成后的质量还是不够好主要问题是纹理细节会被平均掉比如一张图里有清晰的牌照融合后牌照就糊了。这是空域混合的通病。改进思路是用金字塔融合。具体来说对每张输入图像构建拉普拉斯金字塔提取不同尺度的高频细节对权重掩膜构建高斯金字塔逐步模糊作为每层融合权重每一层融合图像 拉普拉斯塔图像 × 高斯塔权重 另一张的拉普拉斯塔图像 × (1 - 高斯塔权重)把所有层做一个重建得到最终的融合结果这样做的好处是高频细节在每一层都被独立保留不会出现简单的加权平均导致的细节丢失。MATLAB里实现这个需要自己写金字塔构建代码量不算小但如果你是认真做图像合成相关的工程这个技能值得花时间学会。我实践下来这个优化能把合成的质量提升一个档次。5.3 颜色一致性问题做多图合成最容易忽略的问题就是颜色一致性。不同时间拍摄的图片白平衡可能不同色温也不同直接合成后就能看到明显分界线。快速解决方案是用色彩迁移的思路搭一个简单校准流程计算两张图在Lab色彩空间的L明度和ab色彩通道的均值与标准差然后把待合成图的统计量匹配到参考图上。MATLAB里可以用rgb2lab和lab2rgb配合实现代码不算很长。为什么必须在RGB之外的颜色空间做因为RGB的三通道相关性太强直接每个通道去做统计匹配会破坏色彩比例关系容易产生色偏。Lab空间把亮度跟颜色分离匹配起来更符合人对色彩的感觉。6. 常见问题与排查技巧实录6.1 雾天图像为什么偏色得厉害有读者跑完代码反馈加雾后图像整体发红或发蓝。这个问题绝大多数情况下出在大气光A的取值上。你做RGB三通道分别加入不同的大气光值时通道间的非线性差异会在雾区被放大。比如你设置A [0.8, 0.8, 0.8]三个通道一致雾就是中性的灰白色。如果设成A [0.9, 0.75, 0.7]R通道高、B通道低薄雾区域会明显偏暖。这并不是代码bug而是物理模型本身表达的特性——大气散射本身就带颜色。做合成数据有一个原则先让雾是中性灰白的确认形状和深度没问题后再去引入色偏。一上来直接做带色的雾出了问题不好定位是深度图错了还是颜色分量配错了。6.2 输出图像出现奇怪的黑点或白点黑点通常是因为depth值接近0或者为负时在高斯随机场场景下比较常见。depth一旦出现负值exp(-beta*depth)就大于1透射率超过1图像某些像素的亮度会异常。解决办法是在生成深度图后立即做归一化和截断depth max(depth, 0); % 保证非负 depth depth / max(depth(:)); % 归一化到0-1白点则通常是因为大气光A接近1跟t值较小的区域乘出来的结果溢出了im2uint8在转换时会直接裁掉超过255的值形成一个白色死区。检查你的A值是不是设置得太高一般A取0.7到0.9就够了不需要接近于1。6.3 雾效分布太平,缺少层次感很多初学加雾的人都会遇到这个困惑参数都按公式设了雾也有了但看起来就是假像蒙了一层半透明膜没有远近深度感。原因几乎都在深度图。如果你用了全图均匀的透射率比如直接在公式里设固定t值那雾在整张图上浓度一样当然像蒙了一层纱。真实场景中近处透射率接近1远处透射率降低两者之间存在连续过渡。解决办法是给深度图加入递进变化并且用模糊让过渡区域更平滑。如果用的是线性深度可以把linspace改成非线性单调函数比如depth (x/w).^2这种平方关系能模拟出近处细节多、远处衰减加快的真实感视觉上层次感一下就出来了。6.4 常见错误速查表现象可能原因解法加雾后整图变黑忘记把图片转为double就做乘法uint8截断用im2double先转类型雾区出现噪点深度图有突变透射率跳变用imgaussfilt平滑深度图雾的颜色不符合预期大气光RGB比例失衡先设成[0.8 0.8 0.8]再调色偏近景也被雾糊住beta太大或depth整体偏大减小beta或缩小depth取值范围合成接缝明显权重突变用羽化mask或金字塔融合结果图过亮A值太接近1把A降低到0.9以下7. 更深层的思考加雾处理的实际应用场景7.1 与深度学习的结合现在合成加雾最常见的用途之一就是做数据增广。目标检测模型在真实雾天场景中性能会大幅下降但你又很难在短时间内收集大量真实雾天标注图像。用合成加雾的方式可以在已有清晰数据集上自动生成雾天版本标注框完全不用重新标。我在实际实验中发现一个关键点合成加雾时最好随机化参数beta和A。如果用固定参数生成所有雾图模型会学到雾天等于某种固定风格迁移到真实场景时效果依然不好。把beta在0.5~1.5之间随机取值A在0.75~0.9之间随机取值生成的训练样本多样性更高模型鲁棒性明显好一个档次。7.2 配合深度信息的扩展如果你手头有深度相机或雷达数据可以把真实的深度图替代我代码里的合成深度图。这样可以做出跟实际场景几何关系严格一致的雾天图像。这种方案的优越性是合成深度图无法比的雾的浓度真的跟距离绑定远处的建筑、山体自然被雾掩盖近处的车辆、行人保持细节视觉上几乎可以以假乱真。MATLAB里读取深度图一般用load或者imread关键是深度图的数值范围需要跟你的图像尺寸对齐。如果深度图分辨率跟RGB图不一致先做resize再送入加雾函数。7.3 其他特殊效果的扩展这套模型不止能做雾稍作修改就能模拟霾、烟、沙尘等天气效果。沙尘天气的本质是把大气光A换成偏黄褐色的值霾则可以用大颗粒模糊加低对比度来模拟。更进一步如果你把透射率从全局改成随空间变化的函数还能模拟局部雾气流动的效果——配合视频处理一帧一帧设定不同的beta就能生成雾气逐渐加重的动态过程。这个玩法扩展性挺强的基本上天气仿真领域里能用到的技术点都能从这一个基础管线出发延伸出来。8. 项目总结与实操心得做这个合成加雾项目前后用了不到一周时间最难的不是写代码而是把物理模型的直觉建立起来。大气散射模型公式就一行但真正理解t、A、beta各自控制什么效果、改哪个参数会引起什么连锁反应必须自己上手调试几轮才会有体感。我个人在实际操作中的几条体会供你参考第一代码写出来是第一步参数调试才是真正的重头戏。我建议你拿到这个函数后第一件事是跑一组beta从0.2到3.0的梯度对比图从效果上建立beta数值对应多少浓度的雾的直觉后面不管做数据增强还是效果仿真都能很快找到合适参数。第二深度图质量决定了雾效质量这句话怎么强调都不过分。如果你发现自己生成的雾图看起来假八成是深度图有问题别急着调beta和A。第三如果做学术实验合成加雾的参数应该跟评估指标一起报出来。我见过不少论文里写we generate foggy images for training但参数完全不透明别人根本无法复现。至少把beta、A、深度图类型这三个变量写清楚。这个项目的后续扩展空间还在比如结合暗通道先验算法把合成雾图送进去做去雾评估或者接上YOLO等目标检测模型统计不同雾浓度下的精度衰减曲线。无论往哪个方向走你现在手里的这个基础管线都够用剩下的就是往上面堆自己的需求了。