在计算机视觉领域,图像去雾一直是个极具挑战性的课题。2009年,何恺明等人提出的暗通道先验算法以其简洁优雅的数学形式和出色的去雾效果,一举斩获CVPR最佳论文奖。这个算法不需要多幅图像或深度信息,仅凭单张雾图就能实现令人惊艳的去雾效果。本文将带你从零开始,用Python完整复现这一经典算法。
暗通道先验的发现源于对大量自然图像的统计分析。何恺明团队观察了5000多张无雾图像后发现,在绝大多数非天空区域,至少存在一个颜色通道的某些像素值非常低,甚至接近于零。这个看似简单的观察,却成为解决单图像去雾问题的关键钥匙。
暗通道的数学定义可以表示为:
python复制def dark_channel(image, window_size=15):
# 取RGB三通道的最小值
min_channel = np.min(image, axis=2)
# 进行最小值滤波
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (window_size, window_size))
dark = cv2.erode(min_channel, kernel)
return dark
这段代码实现了暗通道的计算过程:
提示:窗口大小(window_size)是影响结果的关键参数,通常取值在15-30之间。窗口越大,暗通道越"暗",但计算量也会增加。
有了暗通道图后,我们需要估计大气光值A。这个值代表了环境中雾的浓度,通常对应于图像中最亮的区域。以下是估计大气光的步骤:
python复制def estimate_atmospheric_light(image, dark, percent=0.001):
# 计算要选取的像素数
num_pixels = int(dark.size * percent)
# 找到暗通道中最亮的像素位置
flat_dark = dark.flatten()
indices = np.argpartition(flat_dark, -num_pixels)[-num_pixels:]
# 获取原图中对应位置的像素
pixels = image.reshape(-1, 3)[indices]
# 返回亮度最高的像素值
return np.max(pixels, axis=0)
透射率t(x)描述了光线在到达相机前被散射的程度,是去雾算法的核心。基于暗通道先验,透射率可以表示为:
python复制def estimate_transmission(image, atmospheric_light, omega=0.95, window_size=15):
# 归一化图像
normalized = image / atmospheric_light
# 计算归一化图像的暗通道
dark = dark_channel(normalized, window_size)
# 估计透射率
transmission = 1 - omega * dark
return np.clip(transmission, 0.1, 1.0)
这里有几个关键参数需要注意:
| 参数 | 作用 | 典型值 | 影响 |
|---|---|---|---|
| omega(ω) | 控制去雾程度 | 0.95 | 值越小,保留的雾越多 |
| window_size | 最小值滤波窗口大小 | 15 | 越大,透射率估计越平滑 |
| t0 | 透射率下限 | 0.1 | 防止过度去雾导致的噪声 |
有了大气光A和透射率t(x),我们就可以根据大气散射模型复原无雾图像:
python复制def recover_scene(image, transmission, atmospheric_light, t0=0.1):
# 防止透射率过小
transmission = np.maximum(transmission, t0)
# 复原图像
recovered = np.zeros_like(image, dtype=np.float32)
for i in range(3): # 对RGB三个通道分别处理
recovered[:,:,i] = (image[:,:,i] - atmospheric_light[i]) / transmission + atmospheric_light[i]
# 将像素值限制在0-255范围内
return np.clip(recovered, 0, 255).astype(np.uint8)
原始算法虽然效果不错,但仍有改进空间。我们可以通过以下方法进一步提升去雾效果:
导向滤波优化透射率图
python复制def guided_filter(image, transmission, radius=40, epsilon=0.0001):
# 将图像转换为灰度图作为引导图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY).astype(np.float32) / 255
transmission = transmission.astype(np.float32)
# 应用导向滤波
refined = cv2.ximgproc.guidedFilter(gray, transmission, radius, epsilon)
return refined
自动曝光调整
python复制def auto_exposure_adjustment(image, gamma=1.2):
# 将图像转换为YUV色彩空间
yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV)
# 对亮度通道进行gamma校正
yuv[:,:,0] = np.clip(np.power(yuv[:,:,0]/255.0, gamma)*255, 0, 255)
# 转换回BGR
return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR)
将上述步骤整合起来,我们得到完整的去雾流程:
python复制def dehaze(image, window_size=15, omega=0.95, t0=0.1, refine=True):
# 转换为浮点类型
image = image.astype(np.float32)
# 1. 计算暗通道
dark = dark_channel(image, window_size)
# 2. 估计大气光
atmospheric_light = estimate_atmospheric_light(image, dark)
# 3. 估计透射率
transmission = estimate_transmission(image, atmospheric_light, omega, window_size)
# 4. 可选:使用导向滤波优化透射率
if refine:
transmission = guided_filter(image, transmission)
# 5. 复原图像
recovered = recover_scene(image, transmission, atmospheric_light, t0)
# 6. 后处理
final = auto_exposure_adjustment(recovered)
return final
在实际应用中,我发现窗口大小对结果影响显著。对于高分辨率图像(>1080p),窗口大小可能需要增加到25-30;而对于小图像(<720p),15左右的窗口通常足够。ω参数控制去雾强度,当设置为0.9时能保留适度的景深效果,0.95-0.98则会产生更强的去雾效果。