目录
- 图像去雾算法:从物理模型到深度学习实现
-
- 概述
- [1. 大气散射模型与去雾原理](#1. 大气散射模型与去雾原理)
-
- [1.1 大气散射模型](#1.1 大气散射模型)
- [1.2 问题逆向求解](#1.2 问题逆向求解)
- [2. 基于暗通道先验(DCP)的图像去雾算法](#2. 基于暗通道先验(DCP)的图像去雾算法)
-
- [2.1 暗通道先验的定义](#2.1 暗通道先验的定义)
- [2.2 基于DCP的透射率估计](#2.2 基于DCP的透射率估计)
- [2.3 大气光 A A A 的估计](#2.3 大气光 A A A 的估计)
- [2.4 透射率细化与图像恢复](#2.4 透射率细化与图像恢复)
- [3. Python实现基于暗通道先验的去雾算法](#3. Python实现基于暗通道先验的去雾算法)
-
- [3.1 环境配置与依赖](#3.1 环境配置与依赖)
- [3.2 完整代码实现](#3.2 完整代码实现)
- [3.3 代码分析与解释](#3.3 代码分析与解释)
- [3.4 算法局限性及改进](#3.4 算法局限性及改进)
- [4. 基于深度学习的图像去雾简介](#4. 基于深度学习的图像去雾简介)
-
- [4.1 基本思想](#4.1 基本思想)
- [4.2 经典网络结构示例](#4.2 经典网络结构示例)
- [4.3 优势与挑战](#4.3 优势与挑战)
- [5. 总结](#5. 总结)
图像去雾算法:从物理模型到深度学习实现
概述
在计算机视觉和图像处理领域,雾、霾等恶劣天气条件会严重降低图像质量,导致图像对比度下降、颜色失真、细节丢失,进而影响后续高级视觉任务(如目标检测、分割、识别)的性能。图像去雾技术旨在从退化的雾天图像中恢复出清晰的场景,具有重要的理论研究价值和广泛的应用前景,如自动驾驶、视频监控、航空遥感等。本文将系统性地介绍图像去雾的基本原理、经典算法,并重点使用Python实现基于暗通道先验(Dark Channel Prior, DCP)的去雾算法,同时探讨基于深度学习的去雾方法。我们将从物理模型出发,深入算法细节,并提供完整的、可读性强的代码实现。
1. 大气散射模型与去雾原理
图像去雾问题的研究建立在大气散射模型(Atmospheric Scattering Model)之上,该模型从物理上描述了雾天图像的成像过程。
1.1 大气散射模型
该模型将观测到的有雾图像 I ( x ) I(x) I(x) 表示为两部分的和:
I ( x ) = J ( x ) t ( x ) + A ( 1 − t ( x ) ) I(x) = J(x)t(x) + A(1 - t(x)) I(x)=J(x)t(x)+A(1−t(x))
其中:
- x x x 表示图像中像素的空间坐标。
- I ( x ) I(x) I(x) 是观测到的有雾图像(输入)。
- J ( x ) J(x) J(x) 是待恢复的无雾清晰图像(我们的目标)。
- A A A 是全球大气光值(Global Atmospheric Light),通常假设为全局常量,代表无穷远处的大气光强度。
- t ( x ) t(x) t(x) 是媒介透射率(Medium Transmission),表示场景辐射在传播过程中的衰减程度,取值范围为 [ 0 , 1 ] [0, 1] [0,1]。 t ( x ) t(x) t(x) 越小,表示该位置雾浓度越高。
t ( x ) t(x) t(x) 与场景深度 d ( x ) d(x) d(x) 的关系由下式给出:
t ( x ) = e − β d ( x ) t(x) = e^{-\beta d(x)} t(x)=e−βd(x)
其中 β \beta β 是大气散射系数。去雾的核心任务就是从已知的 I ( x ) I(x) I(x) 中,估算出未知的 A A A 和 t ( x ) t(x) t(x),从而最终恢复出 J ( x ) J(x) J(x)。
1.2 问题逆向求解
从大气散射模型可以推导出恢复清晰图像的公式:
J ( x ) = I ( x ) − A t ( x ) + A J(x) = \frac{I(x) - A}{t(x)} + A J(x)=t(x)I(x)−A+A
然而,这是一个病态问题 (Underconstrained Problem),因为每个像素我们只有一个观测值 I ( x ) I(x) I(x),却需要求解 J ( x ) J(x) J(x)、 A A A 和 t ( x ) t(x) t(x) 三个未知数。为了解决这个问题,研究人员引入了各种先验知识(Prior)或假设来约束解空间。
2. 基于暗通道先验(DCP)的图像去雾算法
何恺明等人于2009年提出的暗通道先验是图像去雾领域的里程碑式工作。其核心思想来自于对大量无雾户外图像的统计观察。
2.1 暗通道先验的定义
暗通道先验指出:在绝大多数非天空的户外无雾图像局部区域中,至少存在一个颜色通道的某些像素其强度值非常低,甚至接近于零。
对于一个清晰图像 J J J,其暗通道 J d a r k J^{dark} Jdark 的数学定义为:
J d a r k ( x ) = min y ∈ Ω ( x ) ( min c ∈ { r , g , b } J c ( y ) ) J^{dark}(x) = \min_{y \in \Omega(x)} \left( \min_{c \in \{r, g, b\}} J^c(y) \right) Jdark(x)=y∈Ω(x)min(c∈{r,g,b}minJc(y))
其中:
- J c J^c Jc 表示 J J J 图像的某一个颜色通道。
- Ω ( x ) \Omega(x) Ω(x) 是一个以像素 x x x 为中心的局部图像块(Patch)。
- min c ∈ { r , g , b } \min_{c \in \{r, g, b\}} minc∈{r,g,b} 是在 RGB 三个通道上取最小值。
- min y ∈ Ω ( x ) \min_{y \in \Omega(x)} miny∈Ω(x) 是在一个局部块内取最小值。
根据先验, J d a r k ( x ) → 0 J^{dark}(x) \to 0 Jdark(x)→0。
2.2 基于DCP的透射率估计
- 假设大气光 A A A 已知 ,且透射率在局部块 Ω ( x ) \Omega(x) Ω(x) 内为常数 t ~ ( x ) \tilde{t}(x) t~(x)。
- 将大气散射模型两边同时除以 A c A^c Ac(各通道的大气光)并进行归一化:
I c ( x ) A c = t ( x ) J c ( x ) A c + 1 − t ( x ) \frac{I^c(x)}{A^c} = t(x)\frac{J^c(x)}{A^c} + 1 - t(x) AcIc(x)=t(x)AcJc(x)+1−t(x) - 对上述等式两边同时进行两次 min \min min 操作:
min y ∈ Ω ( x ) ( min c I c ( y ) A c ) = t ~ ( x ) min y ∈ Ω ( x ) ( min c J c ( y ) A c ) + 1 − t ~ ( x ) \min_{y \in \Omega(x)} \left( \min_{c} \frac{I^c(y)}{A^c} \right) = \tilde{t}(x) \min_{y \in \Omega(x)} \left( \min_{c} \frac{J^c(y)}{A^c} \right) + 1 - \tilde{t}(x) y∈Ω(x)min(cminAcIc(y))=t~(x)y∈Ω(x)min(cminAcJc(y))+1−t~(x) - 根据暗通道先验,无雾清晰图像的归一化暗通道 min y ∈ Ω ( x ) ( min c J c ( y ) A c ) \min_{y \in \Omega(x)} \left( \min_{c} \frac{J^c(y)}{A^c} \right) miny∈Ω(x)(mincAcJc(y)) 趋近于0。
- 因此,我们可以简化上式,得到透射率 t ~ ( x ) \tilde{t}(x) t~(x) 的粗略估计:
t ~ ( x ) = 1 − ω ⋅ min y ∈ Ω ( x ) ( min c I c ( y ) A c ) \tilde{t}(x) = 1 - \omega \cdot \min_{y \in \Omega(x)} \left( \min_{c} \frac{I^c(y)}{A^c} \right) t~(x)=1−ω⋅y∈Ω(x)min(cminAcIc(y))
其中引入了一个因子 ω \omega ω ( 0 < ω ≤ 1 0 < \omega \leq 1 0<ω≤1,通常取0.95) 来保留少量的雾,使得恢复后的图像看起来更自然,具有深度感。
2.3 大气光 A A A 的估计
原论文中的估计方法如下:
- 从有雾图像 I I I 的暗通道图中选取最亮的(即前0.1%)的像素点。
- 这些像素点通常对应着图像中雾浓度最高的区域(可能是天空或白色物体)。
- 在这些像素点对应的原始有雾图像 I I I 的位置中,选取强度最大的点的值作为全局大气光 A A A。通常取RGB三个通道中的最大值。
2.4 透射率细化与图像恢复
- 软抠图(Soft Matting):最初使用Matting算法来细化粗估计的透射率图,但其计算复杂度非常高。
- 导向滤波(Guided Filter) :何恺明等在后续工作中提出了使用导向滤波来细化透射率图,其在效果和效率上取得了非常好的平衡。导向滤波能够保持边缘特性,同时平滑同质区域。
- 恢复清晰图像 :在得到细化的透射率图 t ( x ) t(x) t(x) 和大气光 A A A 后,代入恢复公式即可。为避免除以零和保证数值稳定性,通常设置一个透射率下限 t 0 t_0 t0(如0.1):
J ( x ) = I ( x ) − A max ( t ( x ) , t 0 ) + A J(x) = \frac{I(x) - A}{\max(t(x), t_0)} + A J(x)=max(t(x),t0)I(x)−A+A - 结果后处理 :恢复出的 J ( x ) J(x) J(x) 值可能超出 [ 0 , 255 ] [0, 255] [0,255] 的范围,需要进行截断和归一化,或者进行简单的自动色阶/对比度拉伸以改善视觉效果。
3. Python实现基于暗通道先验的去雾算法
下面我们将使用NumPy和OpenCV等库,逐步实现DCP去雾算法。
3.1 环境配置与依赖
首先,确保安装了必要的库:
bash
pip install opencv-python numpy scipy
3.2 完整代码实现
python
import cv2
import numpy as np
from scipy import ndimage
from scipy.spatial import distance
def guided_filter(I, p, win_size, eps):
"""
导向滤波实现
参考: K.He, J.Sun, and X.Tang. Guided Image Filtering. ECCV 2010.
Args:
I: 导向图像(Guidance Image),灰度图,范围[0, 1]
p: 输入图像(Input Image),与I形状相同,范围[0, 1]
win_size: 局部窗口半径
eps: 正则化参数,防止a过大
Returns:
q: 滤波输出图像
"""
mean_I = cv2.boxFilter(I, cv2.CV_64F, (win_size, win_size))
mean_p = cv2.boxFilter(p, cv2.CV_64F, (win_size, win_size))
mean_Ip = cv2.boxFilter(I * p, cv2.CV_64F, (win_size, win_size))
cov_Ip = mean_Ip - mean_I * mean_p
mean_II = cv2.boxFilter(I * I, cv2.CV_64F, (win_size, win_size))
var_I = mean_II - mean_I * mean_I
a = cov_Ip / (var_I + eps)
b = mean_p - a * mean_I
mean_a = cv2.boxFilter(a, cv2.CV_64F, (win_size, win_size))
mean_b = cv2.boxFilter(b, cv2.CV_64F, (win_size, win_size))
q = mean_a * I + mean_b
return q
def estimate_transmission_rough(img, atmos_light, patch_size=15, w=0.95):
"""
粗略估计透射率图
Args:
img: 输入有雾图像,归一化到[0, 1]
atmos_light: 估计的大气光值,形状为(3,)
patch_size: 计算暗通道的局部块大小,必须是奇数
w: 雾保留系数,控制去雾程度,默认0.95
Returns:
transmission_rough: 粗略的透射率图,范围[0, 1]
"""
# 将图像除以大气光进行归一化
normalized_img = img / atmos_light
# 计算归一化图像的暗通道
min_channel = np.min(normalized_img, axis=2)
# 使用最小值滤波(形态学腐蚀)计算暗通道
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (patch_size, patch_size))
dark_channel_norm = cv2.erode(min_channel, kernel)
# 根据公式计算粗略透射率: t = 1 - w * dark_channel
transmission_rough = 1 - w * dark_channel_norm
# 限制透射率范围
transmission_rough = np.clip(transmission_rough, 0.1, 0.9)
return transmission_rough
def estimate_atmospheric_light(img, dark_channel, percent=0.001):
"""
估计全局大气光值A
Args:
img: 输入有雾图像,范围[0, 255] (uint8)
dark_channel: 对应的暗通道图,与img同宽高
percent: 选取暗通道中 brightest 像素的比例,默认0.1%
Returns:
A: 估计的大气光值,形状为(3,)
"""
# 计算要选取的像素数量
num_pixels = int(dark_channel.size * percent)
# 找到暗通道图中前%num_pixels个最亮的像素的坐标(扁平化后的索引)
indices = np.argsort(dark_channel, axis=None)[-num_pixels:]
# 将扁平化的索引转换为二维坐标
coords = np.unravel_index(indices, dark_channel.shape)
# 获取这些坐标点在原始有雾图像中的像素强度
candidate_pixels = img[coords]
# 计算每个像素点的亮度(例如,取RGB三通道的最大值)
brightness = np.max(candidate_pixels, axis=1)
# 找到最亮的候选像素点
brightest_pixel_idx = np.argmax(brightness)
# 将该点的RGB值作为大气光估计值
A = candidate_pixels[brightest_pixel_idx]
return A
def compute_dark_channel(img, patch_size=15):
"""
计算图像的暗通道
Args:
img: 输入图像,可以是归一化[0,1]或[0,255]
patch_size: 局部块大小
Returns:
dark_channel: 暗通道图,与img同宽高,单通道
"""
# 如果图像是3通道,则在每个通道上取最小值
if len(img.shape) == 3:
min_channel = np.min(img, axis=2)
else:
min_channel = img.copy()
# 使用最小值滤波(形态学腐蚀)得到暗通道
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (patch_size, patch_size))
dark_channel = cv2.erode(min_channel, kernel)
return dark_channel
def dehaze_dcp(img, patch_size=15, w=0.95, eps=1e-6, refine=True):
"""
基于暗通道先验的主去雾函数
Args:
img: 输入有雾图像(BGR格式,uint8)
patch_size: 用于计算暗通道和透射率的块大小,必须是奇数
w: 雾保留系数,0<w<=1,越大保留雾越多,通常0.95
eps: 导向滤波的正则化参数
refine: 是否使用导向滤波细化透射率图
Returns:
result: 去雾后的图像(BGR格式,uint8)
transmission: 最终估计的透射率图
atmos_light: 估计的大气光值
"""
# 0. 将图像从BGR转换为RGB并归一化到[0, 1]
img_float = img.astype(np.float32) / 255.0
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
# 1. 计算暗通道(用于估算A)
dark_channel = compute_dark_channel(img_rgb, patch_size)
# 2. 估算全局大气光A
# 注意:estimate_atmospheric_light 需要原图(0-255范围)
img_rgb_255 = (img_rgb * 255).astype(np.uint8)
atmos_light = estimate_atmospheric_light(img_rgb_255, dark_channel)
atmos_light_norm = atmos_light.astype(np.float32) / 255.0
# 3. 粗略估算透射率图
transmission_rough = estimate_transmission_rough(img_rgb, atmos_light_norm, patch_size, w)
# 4. 使用导向滤波细化透射率图
if refine:
# 导向图像使用有雾图像的灰度图
guide = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(np.float32) / 255.0
transmission_refined = guided_filter(guide, transmission_rough, win_size=patch_size, eps=eps)
transmission = np.clip(transmission_refined, 0.1, 0.99) # 设置下限防止除零
else:
transmission = transmission_rough
# 5. 根据大气散射模型恢复无雾图像
# J = (I - A) / t + A
J = np.zeros_like(img_rgb)
for c in range(3): # 对RGB三个通道分别处理
J[:, :, c] = (img_rgb[:, :, c] - atmos_light_norm[c]) / transmission + atmos_light_norm[c]
# 6. 将结果截断到[0,1]并转换回[0,255]
J = np.clip(J, 0, 1)
J_uint8 = (J * 255).astype(np.uint8)
# 7. (可选)后处理:简单的对比度拉伸可以改善视觉效果
# 将结果转换回BGR格式用于OpenCV显示和保存
result_bgr = cv2.cvtColor(J_uint8, cv2.COLOR_RGB2BGR)
# 也将透射率图转换为uint8用于显示
transmission_display = (transmission * 255).astype(np.uint8)
return result_bgr, transmission_display, atmos_light
def main():
"""
主函数:读取图像,进行去雾,并显示和保存结果。
"""
# 读取输入有雾图像
input_path = 'hazy_image.jpg' # 替换为你的有雾图像路径
img_hazy = cv2.imread(input_path)
if img_hazy is None:
print(f"错误:无法读取图像 {input_path}")
return
print("正在处理图像,请稍候...")
# 调用去雾函数
dehazed_result, transmission_map, A = dehaze_dcp(img_hazy,
patch_size=15,
w=0.95,
refine=True)
print(f"估计的大气光值 (BGR): {A}")
# 显示原图、透射率图和去雾结果
cv2.imshow('Original Hazy Image', img_hazy)
cv2.imshow('Transmission Map', transmission_map)
cv2.imshow('Dehazed Result', dehazed_result)
# 保存结果
cv2.imwrite('dehazed_result.jpg', dehazed_result)
cv2.imwrite('transmission_map.jpg', transmission_map)
print("去雾结果已保存为 'dehazed_result.jpg'")
print("透射率图已保存为 'transmission_map.jpg'")
# 等待按键后关闭所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
3.3 代码分析与解释
compute_dark_channel
: 通过先取RGB三通道最小值,再进行最小值滤波(形态学腐蚀)来计算暗通道。estimate_atmospheric_light
: 从暗通道中最亮的0.1%像素对应的原始图像位置中,寻找亮度最高的点作为大气光 A A A 的估计。estimate_transmission_rough
: 根据归一化后的图像和大气光 A A A,利用公式 t ~ ( x ) = 1 − ω ⋅ J n o r m d a r k ( x ) \tilde{t}(x) = 1 - \omega \cdot J^{dark}_{norm}(x) t~(x)=1−ω⋅Jnormdark(x) 计算粗透射率图。guided_filter
: 实现导向滤波,使用有雾图像的灰度图作为引导图像,对粗透射率图进行边缘保持的平滑细化,得到更精确的透射率图 t ( x ) t(x) t(x)。dehaze_dcp
: 主函数,协调整个去雾流程:图像预处理 -> 计算暗通道 -> 估计A -> 估计粗透射率 -> 细化透射率 -> 恢复清晰图像 -> 后处理。main
: 处理IO,调用去雾函数,并展示和保存结果。
3.4 算法局限性及改进
- 天空区域失效:天空区域本身不符合暗通道先验,导致该区域透射率被严重低估,去雾后容易出现色差和噪声放大("天空变色"或"halo效应")。
- 计算效率:尽管使用了导向滤波,DCP算法的计算量仍然较大,难以满足实时应用需求。
- 先验假设:依赖于统计先验,对于室内图像或不符合该先验的特殊场景效果不佳。
常见改进方法:
- 天空区域检测与分割:单独处理天空区域,避免对其进行强去雾。
- 更高效的滤波器:使用比导向滤波更快的双边滤波器或其他边缘保持滤波器。
- 融合其他先验:结合颜色一致性等其他先验来提高鲁棒性。
4. 基于深度学习的图像去雾简介
近年来,深度学习在图像去雾领域取得了巨大成功,逐渐成为主流方法。
4.1 基本思想
深度学习模型(主要是CNN)通过学习大量有雾-无雾图像对,直接建立从 I ( x ) I(x) I(x) 到 J ( x ) J(x) J(x) 的端到端映射,或者学习估算大气散射模型中的参数 A A A 和 t ( x ) t(x) t(x)。
4.2 经典网络结构示例
一个简单的去雾CNN可能包含以下结构:
python
# 一个简化的示例性去雾CNN结构(基于PyTorch框架伪代码)
import torch.nn as nn
class SimpleDehazeNet(nn.Module):
def __init__(self):
super(SimpleDehazeNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# ... 更多卷积层 ...
nn.Conv2d(64, 3, kernel_size=3, padding=1), # 输出3通道
nn.Sigmoid() # 将输出限制在[0,1]
)
def forward(self, x):
# x: 输入有雾图像 [batch, 3, H, W]
residual = self.features(x)
# 学习的是残差:J = I - R,或者直接学习J
return residual # 或者 return x - residual
# 更先进的网络会使用U-Net、GAN、注意力机制等复杂结构。
4.3 优势与挑战
- 优势 :
- 效果卓越:在大量数据上训练的模型往往能产生比传统方法更清晰、更自然的结果。
- 端到端:避免了复杂的手工设计特征和先验。
- 速度快:前向推断速度可以非常快,易于部署。
- 挑战 :
- 数据依赖:需要大量成对的(有雾-无雾)训练数据,获取真实世界的数据对非常困难。
- 泛化能力:在训练集分布之外的雾天图像上可能表现不佳。
- 模型可解释性:不如传统模型物理意义清晰。
5. 总结
本文详细介绍了图像去雾的物理模型------大气散射模型,并重点阐述了基于暗通道先验这一经典算法的原理与实现细节。我们使用Python和OpenCV从头实现了一个完整的DCP去雾流程,包括暗通道计算、大气光估计、透射率估计与细化以及清晰图像恢复。代码结构清晰,注释完整,便于理解和实验。
尽管DCP算法存在一些局限性,但它奠定了现代图像去雾研究的基础,其思想至今仍有重要价值。同时,我们也简要介绍了基于深度学习的去雾方法,这代表了该领域的未来发展方向。在实际应用中,可以根据具体需求(对效果、速度、资源的要求)选择合适的算法。