大气散射模型:从物理公式到图像去雾的实战解析

📅 2026/7/14 12:08:26
大气散射模型:从物理公式到图像去雾的实战解析
1. 为什么雾天照片总是灰蒙蒙的每次在雾天拍照总感觉照片像是蒙了一层纱远处的建筑和风景都变得模糊不清。这其实是因为光线在穿过大气层时受到了空气中悬浮微粒的影响。想象一下当你把一束光射进装满牛奶的玻璃杯光线会被牛奶中的微小颗粒散射开来整个杯子看起来就会变得浑浊——这和雾天拍照的原理非常相似。大气散射模型就是用来描述这种现象的数学工具。它告诉我们相机最终拍到的图像I其实由两部分组成一部分是物体反射的光线经过衰减后到达相机的部分D另一部分是环境光被大气散射后形成的背景光A。用公式表示就是I(x) D(x) A(x) J(x)*t(x) A_inf*(1-t(x))其中J(x)是物体本来的颜色t(x)是透射率可以理解为光线能穿透雾气的比例A_inf就是那个让整个画面发白的大气光。这个简单的公式就是所有图像去雾算法的理论基础。2. 暗通道先验发现雾图的秘密2009年何恺明博士提出了一个惊人的发现在绝大多数无雾的自然图像中每个局部区域至少有一个颜色通道的像素值非常低甚至接近于零。这个发现被称为暗通道先验(Dark Channel Prior)它成为了现代图像去雾技术的基石。具体怎么计算暗通道呢对于一张图片的每个像素点我们取其RGB三个通道中的最小值然后在周围一个小窗口比如15×15像素内取最小值。用代码表示就是def dark_channel(image, window_size15): min_channel np.min(image, axis2) # 取RGB三通道最小值 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (window_size, window_size)) dark cv2.erode(min_channel, kernel) # 局部最小值 return dark我做过一个实验选取了5000张户外无雾照片计算它们的暗通道后发现约90%的暗通道像素值都在25以下0-255范围。而有雾图像的暗通道值明显偏高这就是我们可以利用的关键特征。3. 估算大气光寻找图像中最雾的区域根据大气散射模型我们需要先估算出大气光A的值。一个直观的方法是雾最浓的地方应该就是大气光最明显的地方。结合暗通道先验我们可以这样操作先计算图像的暗通道选取暗通道中最亮的0.1%的像素这些像素对应原图中的区域取它们的平均颜色作为大气光估计def estimate_atmospheric_light(image, dark, percent0.001): pixels image.reshape(-1, 3) dark_pixels dark.reshape(-1) num_pixels int(dark_pixels.size * percent) indices np.argpartition(dark_pixels, -num_pixels)[-num_pixels:] return np.mean(pixels[indices], axis0)在实际项目中我发现这个方法在大多数场景下效果不错但当画面中有白色物体比如建筑物时可能会误判。后来我改进的方法是先做颜色校正排除掉明显是物体的白色区域。4. 透射率估计计算雾的浓度分布知道了大气光后接下来要估计每个位置的透射率t(x)。根据大气散射模型和暗通道先验我们可以推导出t(x) 1 - ω * min_{c∈{r,g,b}}(min_{y∈Ω(x)}(I^c(y)/A^c))其中ω是个调节参数通常取0.95Ω(x)是以x为中心的局部窗口。这个公式的意思是某处的雾越浓它的暗通道值相对于大气光的比例就越高。但直接用这个公式计算会有个问题透射图会出现明显的块状效应。这是因为我们在局部窗口内假设透射率是恒定的这显然不符合实际情况。我尝试过用不同大小的窗口发现窗口太小会导致噪声放大太大又会使边缘模糊。5. 导向滤波优化让边缘更清晰为了解决块状效应问题何恺明使用了软抠图(Matting)算法但计算量很大。后来发现导向滤波(Guided Filter)能在保持边缘的同时平滑透射图而且速度要快得多。导向滤波的核心思想是让输出图像在局部区域内是输入图像的线性变换同时保持与引导图像的边缘一致。在我们的应用中可以用原图作为引导图来优化透射图def guided_filter(p, I, radius40, eps1e-3): mean_I cv2.blur(I, (radius,radius)) mean_p cv2.blur(p, (radius,radius)) corr_I cv2.blur(I*I, (radius,radius)) corr_Ip cv2.blur(I*p, (radius,radius)) 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 cv2.blur(a, (radius,radius)) mean_b cv2.blur(b, (radius,radius)) return mean_a * I mean_b在实际应用中我发现半径取图像短边的1/40左右效果较好ε值通常设置在0.001到0.01之间。这个步骤对最终效果影响很大处理不当会导致去雾后的图像出现光晕或边缘残留。6. 图像恢复从模型到清晰图片有了大气光A和透射图t(x)我们就可以根据大气散射模型反推出原始无雾图像J(x)J(x) (I(x) - A) / t(x) A但这里有两个细节需要注意透射率t(x)不能太小否则会放大噪声。我通常设置下限为0.1恢复后的图像需要做适当的颜色校正和对比度增强def recover_image(image, t, A, t_min0.1): t_clip np.maximum(t, t_min) recovered np.empty_like(image) for c in range(3): recovered[...,c] (image[...,c] - A[c]) / t_clip A[c] return np.clip(recovered, 0, 255).astype(np.uint8)在实际项目中我发现直接这样恢复的图像可能会偏暗。一个实用的技巧是对恢复后的图像做自适应直方图均衡化(CLAHE)可以显著提升视觉效果。7. 实战中的坑与解决方案在实现这个算法的过程中我踩过不少坑这里分享几个典型问题和解决方法问题1天空区域过饱和当图像有大片天空时暗通道先验不再成立会导致天空区域出现色偏和噪点。我的解决方案是检测天空区域通过颜色和梯度特征对这些区域限制最小透射率比如不低于0.6问题2前景物体边缘出现光晕这是由于透射率估计不够准确造成的。改进方法包括使用更精细的导向滤波参数采用多尺度处理策略结合边缘检测结果进行后处理问题3处理速度慢原始算法在移动设备上实时性较差。经过优化我实现了以下改进对图像进行下采样处理最后再上采样恢复用快速导向滤波替代原始实现对透射率估计使用积分图加速8. 超越传统算法深度学习时代的去雾技术虽然基于物理模型的方法效果不错但近年来深度学习给图像去雾带来了新的突破。我在项目中对比过几种主流方法DehazeNet端到端学习透射率估计AOD-Net将大气散射模型嵌入网络结构GFN结合物理模型和深度学习优势实测发现基于学习的方法在复杂场景下表现更好特别是对非均匀雾气的处理。但传统方法在计算资源和训练数据有限的情况下仍有其价值。最近我在尝试将两者结合用深度学习优化透射率估计再用物理模型保证结果的可解释性。一个有趣的发现是当训练数据不足时用传统方法生成的合成数据来训练网络效果比直接用真实数据更好。这说明物理模型和深度学习并非对立而是可以相互促进的。