图像去雾:从直方图增强到暗通道先验【计算机视觉】
- [图像去雾(Image Dehazing)](#图像去雾(Image Dehazing))
图像去雾(Image Dehazing)
图像去雾是计算机视觉中针对雾天退化图像的核心复原技术,主要分为两类核心思路:基于直方图均衡化的对比度增强方法(快速提升视觉清晰度)、基于暗通道先验的物理模型复原方法(精准还原无雾场景),是自动驾驶、安防监控、遥感影像分析的关键预处理环节。
注意:本文所有代码均可导入Jupyter Notebook
完整代码仓库地址:🔗 GitHub:https://github.com/KnifeWen007/CV---StudyNotebook
Ⅰ、引言
在计算机视觉领域,雾天、霾天等恶劣天气会导致图像出现对比度降低、细节模糊、色彩失真等问题------ 这本质是大气散射效应破坏了光线的传播路径,让原本清晰的空域像素信息被雾效掩盖。
图像去雾技术为解决这一问题提供了两套核心方案:一类是基于直方图均衡化的工程化方法,通过调整像素亮度分布快速增强对比度,实现"视觉上的去雾";另一类是基于暗通道先验的物理建模方法,通过还原大气散射的物理过程,精准计算透射率与大气光,实现"物理层面的无雾复原",二者分别适配快速处理与高精度复原。
Ⅱ、基于直方图均衡化的图像去雾
一、核心原理
直方图均衡化(Histogram Equalization, HE)是一种空域图像增强方法,核心目标是通过重新分配图像的像素亮度值,拉伸像素的动态范围,从而提升雾天图像的对比度,弱化雾效带来的"灰蒙蒙"视觉效果。
雾天图像的直方图通常集中在窄带的中低亮度区间(雾的散射光导致像素亮度分布集中),直方图均衡化的本质是将这种"集中型直方图"转换为"均匀分布直方图":
- 统计亮度分布 :计算图像中每个亮度级(0-255)的像素数量,生成亮度直方图 H ( k ) H(k) H(k)( k k k 为亮度值, 0 ≤ k ≤ 255 0≤k≤255 0≤k≤255);
- 计算累积分布函数(CDF) :对直方图进行积分,得到 C D F ( k ) = ∑ i = 0 k H ( i ) / N CDF(k) = \sum_{i=0}^k H(i) / N CDF(k)=i=0∑kH(i)/N( N N N 为图像总像素数);
- 亮度映射 :将原亮度值通过 CDF 映射到新的亮度区间,公式为:
s k = 255 × C D F ( k ) s_k = 255 \times CDF(k) sk=255×CDF(k)
其中 s k s_k sk 为均衡化后的新亮度值。
需要注意的是,直方图均衡化并非真正的物理去雾------ 它没有还原大气散射的物理过程,仅通过对比度增强实现"视觉去雾",优点是计算速度快、工程实现简单,缺点是可能过度增强噪声,或导致图像色彩失真(尤其雾浓度较高时)。
二、代码实现(直方图均衡化去雾)
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 解决中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def histogram_dehazing(img_path):
"""
直方图均衡化图像去雾
:param img_path: 雾天图像路径
:return: 原图、去雾图、原图直方图、去雾图直方图
"""
# 读取图像
img = cv2.imread(img_path)
if img is None:
raise ValueError("图像路径错误或文件不存在!")
# 转换为YCrCb,分离亮度通道
ycrcb_img = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
y_channel, cr_channel, cb_channel = cv2.split(ycrcb_img)
# 亮度通道直方图均衡化
eq_y_channel = cv2.equalizeHist(y_channel)
# 合并通道,转回BGR
eq_ycrcb_img = cv2.merge([eq_y_channel, cr_channel, cb_channel])
dehazed_img = cv2.cvtColor(eq_ycrcb_img, cv2.COLOR_YCrCb2BGR)
# 计算亮度直方图
hist_original = cv2.calcHist([y_channel], [0], None, [256], [0, 256])
hist_dehazed = cv2.calcHist([eq_y_channel], [0], None, [256], [0, 256])
return img, dehazed_img, hist_original, hist_dehazed
# 测试代码
img_path = "foggy_image.png"
try:
original_img, dehazed_img, hist_ori, hist_dehaze = histogram_dehazing(img_path)
# 可视化
plt.figure(figsize=(16, 8))
plt.subplot(2, 2, 1)
plt.imshow(cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB))
plt.title("雾天原图", fontsize=12)
plt.axis("off")
plt.subplot(2, 2, 2)
plt.imshow(cv2.cvtColor(dehazed_img, cv2.COLOR_BGR2RGB))
plt.title("直方图均衡化去雾结果", fontsize=12)
plt.axis("off")
plt.subplot(2, 2, 3)
plt.plot(hist_ori, color="blue")
plt.title("原图亮度直方图", fontsize=12)
plt.xlabel("亮度值(0-255)")
plt.ylabel("像素数量")
plt.xlim([0, 256])
plt.subplot(2, 2, 4)
plt.plot(hist_dehaze, color="red")
plt.title("去雾图亮度直方图", fontsize=12)
plt.xlabel("亮度值(0-255)")
plt.ylabel("像素数量")
plt.xlim([0, 256])
plt.tight_layout()
plt.show()
except Exception as e:
print(f"运行错误:{e}")

- 色彩空间分离 :放弃直接对RGB通道操作,转而转换为YCrCb空间,仅提取亮度通道Y进行处理,从根源避免均衡化导致的色彩失真,这是彩色图去雾的关键优化。
- 核心去雾操作 :通过
cv2.equalizeHist()对亮度通道执行均衡化,将雾天图像集中的亮度分布拉伸至全范围,从数值层面消除"灰蒙蒙"的视觉特征。 - 直方图量化验证 :利用
cv2.calcHist()分别计算原图与去雾图的亮度直方图,将抽象的去雾效果转化为直观的数值分布对比,清晰呈现亮度动态范围的扩展。 - 四视图可视化:采用2×2子图布局,同步展示「雾天原图、去雾结果、原图直方图、去雾直方图」,实现"视觉效果"与"数据分布"的双重验证,完美匹配实验分析场景。
Ⅲ、基于暗通道先验的图像去雾
一、核心原理
大气散射模型描述了雾天图像的退化本质:光线在传播过程中被雾气散射,导致清晰场景被"蒙上"一层灰白色雾效。其数学表达为:
I ( x ) = J ( x ) t ( x ) + A [ 1 − t ( x ) ] (1) I(x) = J(x)t(x) + A[1-t(x)] \tag{1} I(x)=J(x)t(x)+A[1−t(x)](1)
其中:
- I ( x ) I(x) I(x):雾图(输入)
- J ( x ) J(x) J(x):无雾图(待恢复的目标)
- t ( x ) t(x) t(x):透射率(描述光线穿透雾气的能力, t ∈ [ 0 , 1 ] t \in [0,1] t∈[0,1])
- A A A:大气光(代表空气中散射的白光,是雾的"底色")
为了消除大气光 A A A 的影响,我们对模型进行通道归一化 处理,将每个颜色通道的值除以对应通道的大气光 A c A^c Ac( c c c 代表R/G/B通道):
I c ( x ) A c = J c ( x ) A c ⋅ t ( x ) + 1 − t ( x ) (2) \frac{I^c(x)}{A^c} = \frac{J^c(x)}{A^c} \cdot t(x) + 1-t(x) \tag{2} AcIc(x)=AcJc(x)⋅t(x)+1−t(x)(2)
这一步的目的是将图像的亮度基准统一到大气光上,为后续提取暗通道特征做准备。
接下来对等式两边执行暗通道操作 :即在每个像素 x x x 的局部窗口 Ω ( x ) \Omega(x) Ω(x) 内,先取R/G/B三个通道的最小值,再取窗口内所有像素的最小值。
- 左侧: min y ∈ Ω ( x ) ( min c I c ( y ) A c ) \min_{y\in\Omega(x)}\big(\min_c \frac{I^c(y)}{A^c}\big) miny∈Ω(x)(mincAcIc(y))
- 右侧: min y ∈ Ω ( x ) ( min c ( J c ( y ) A c t ( y ) + 1 − t ( y ) ) ) \min_{y\in\Omega(x)}\big(\min_c \big(\frac{J^c(y)}{A^c}t(y)+1-t(y)\big)\big) miny∈Ω(x)(minc(AcJc(y)t(y)+1−t(y)))
暗通道操作是整个算法的"眼睛",它能捕捉到图像中最暗的区域,而这些区域在无雾场景中本应趋近于纯黑。
由于透射率 t ( y ) t(y) t(y) 在一个很小的局部窗口内变化非常平缓,可近似看作常数 t ( x ) t(x) t(x),因此可以将其从最小值操作中提取出来,得到:
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 ) (3) \min_{y\in\Omega(x)}\big(\min_c \frac{I^c(y)}{A^c}\big) = t(x) \cdot \min_{y\in\Omega(x)}\big(\min_c \frac{J^c(y)}{A^c}\big) + 1-t(x) \tag{3} y∈Ω(x)min(cminAcIc(y))=t(x)⋅y∈Ω(x)min(cminAcJc(y))+1−t(x)(3)
这是一个关键的数学简化,将复杂的窗口内最小值运算,转化为了关于 t ( x ) t(x) t(x) 的线性方程。
暗通道先验 :这是算法的核心假设------在绝大多数无雾的户外场景中,任意一个局部小窗口内,总会至少有一个颜色通道的像素值非常低,趋近于0。因此:
min y ∈ Ω ( x ) ( min c J c ( y ) ) ≈ 0 \min_{y\in\Omega(x)}\big(\min_c J^c(y)\big) \approx 0 y∈Ω(x)min(cminJc(y))≈0
将其代入上式可得:
min y ∈ Ω ( x ) ( min c J c ( y ) A c ) ≈ 0 A c = 0 \min_{y\in\Omega(x)}\big(\min_c \frac{J^c(y)}{A^c}\big) \approx \frac{0}{A^c} = 0 y∈Ω(x)min(cminAcJc(y))≈Ac0=0
正是利用了"无雾区域必有暗像素"这一统计规律,我们才能将未知的 J ( x ) J(x) J(x) 信息消去,从而解出透射率 t ( x ) t(x) t(x)
将暗通道先验的结论代入式 (3),即可直接解出透射率的初始估计:
min y ∈ Ω ( x ) ( min c I c ( y ) A c ) ≈ t ( x ) ⋅ 0 + 1 − t ( x ) \min_{y\in\Omega(x)}\big(\min_c \frac{I^c(y)}{A^c}\big) \approx t(x) \cdot 0 + 1-t(x) y∈Ω(x)min(cminAcIc(y))≈t(x)⋅0+1−t(x)
整理后得到:
t ( x ) = 1 − min y ∈ Ω ( x ) ( min c I c ( y ) A c ) t(x) = 1 - \min_{y\in\Omega(x)}\big(\min_c \frac{I^c(y)}{A^c}\big) t(x)=1−y∈Ω(x)min(cminAcIc(y))
这个公式直观地告诉我们:雾越浓( I c ( y ) I^c(y) Ic(y) 越亮,越接近 A c A^c Ac),计算出的透射率 t ( x ) t(x) t(x) 就越小。
为了避免过度去雾(完全去雾会让图像看起来不自然、偏暗),引入一个去雾强度系数 ω \omega ω(通常取 0.95 0.95 0.95),保留一点点雾的氛围感,更符合人眼视觉习惯:
t ( x ) = 1 − ω ⋅ min y ∈ Ω ( x ) ( min c I c ( y ) A c ) t(x) = 1 - \omega \cdot \min_{y\in\Omega(x)}\big(\min_c \frac{I^c(y)}{A^c}\big) t(x)=1−ω⋅y∈Ω(x)min(cminAcIc(y))
最后,我们回到最初的大气散射模型 (1),将已经估算出的 t ( x ) t(x) t(x) 和 A A A 代入,直接求解我们最终想要的无雾图 J ( x ) J(x) J(x):
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
这个公式的物理意义非常清晰:从雾图 I ( x ) I(x) I(x) 中减去大气光 A A A,再除以透射率 t ( x ) t(x) t(x) 恢复光线的衰减,最后加回大气光 A A A 保证场景亮度。
为了防止在雾气极浓的区域, t ( x ) t(x) t(x) 趋近于0导致分母为0、图像被除得过曝发白,我们设置一个最小透射率阈值 t 0 t_0 t0(通常取 0.1 0.1 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
max ( t ( x ) , t 0 ) \max(t(x), t_0) max(t(x),t0) 确保了分母永远不会小于 t 0 t_0 t0,是保证算法鲁棒性的关键一步。
二、代码实现(暗通道先验去雾)
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 解决中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def dark_channel(img, window_size=15):
"""计算暗通道图像"""
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (window_size, window_size))
min_channel = cv2.min(cv2.min(img[:, :, 0], img[:, :, 1]), img[:, :, 2])
dark_img = cv2.erode(min_channel, kernel)
return dark_img
def estimate_atmospheric_light(img, dark_img):
"""估算大气光A"""
h, w = img.shape[:2]
num_pixels = int(h * w * 0.001)
dark_flat = dark_img.flatten()
img_flat = img.reshape(-1, 3)
idx = dark_flat.argsort()[::-1][:num_pixels]
atmospheric_light = np.mean(img_flat[idx], axis=0)
return atmospheric_light
def estimate_transmission(img, atmospheric_light, window_size=15, omega=0.95):
"""估算透射率t(x)"""
norm_img = img / atmospheric_light
norm_dark = dark_channel(norm_img, window_size)
transmission = 1 - omega * norm_dark
return transmission
def guided_filter(img, guide, radius=60, eps=1e-3):
"""引导滤波优化透射率"""
img_32f = img.astype(np.float32)
guide_8u = (guide * 255).astype(np.uint8) if guide.max() <= 1 else guide.astype(np.uint8)
return cv2.ximgproc.guidedFilter(guide=guide_8u, src=img_32f, radius=radius, eps=eps)
def recover_scene(img, atmospheric_light, transmission, t0=0.1):
"""恢复无雾彩色图像"""
transmission = np.maximum(transmission, t0)
transmission = np.expand_dims(transmission, axis=2)
recover_img = (img - atmospheric_light) / transmission + atmospheric_light
recover_img = np.clip(recover_img, 0, 1)
recover_img = (recover_img * 255).astype(np.uint8)
return recover_img
def dark_channel_dehazing(img_path, window_size=15):
"""
暗通道先验去雾主函数
:return: original_rgb(原图RGB), dehazed_rgb(去雾后RGB彩色图)
"""
img_bgr = cv2.imread(img_path)
if img_bgr is None:
raise ValueError("图像路径错误或文件不存在!")
original_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_norm = original_rgb.astype(np.float64) / 255.0
dark_img = dark_channel(img_norm, window_size)
atmospheric_light = estimate_atmospheric_light(img_norm, dark_img)
transmission = estimate_transmission(img_norm, atmospheric_light, window_size)
transmission_refine = guided_filter(transmission, original_rgb[:, :, 0])
dehazed_rgb = recover_scene(img_norm, atmospheric_light, transmission_refine)
return original_rgb, dehazed_rgb, dark_img, transmission_refine
# 测试代码
img_path = "foggy_image.png"
try:
original_rgb, dehazed_rgb, dark_img, transmission = dark_channel_dehazing(img_path)
plt.figure(figsize=(16, 8))
plt.subplot(2, 2, 1)
plt.imshow(original_rgb)
plt.title("雾天原图(彩色)", fontsize=12)
plt.axis("off")
plt.subplot(2, 2, 2)
plt.imshow(dehazed_rgb)
plt.title("暗通道去雾结果(彩色)", fontsize=12)
plt.axis("off")
plt.subplot(2, 2, 3)
plt.imshow(dark_img, cmap='gray')
plt.title("暗通道图像", fontsize=12)
plt.axis("off")
plt.subplot(2, 2, 4)
plt.imshow(transmission, cmap='gray')
plt.title("优化后透射率", fontsize=12)
plt.axis("off")
plt.tight_layout()
plt.show()
print("去雾后彩色图形状(高×宽×通道):", dehazed_rgb.shape)
print("去雾后彩色图数据类型:", dehazed_rgb.dtype)
print("去雾后彩色图示例像素值(左上角):", dehazed_rgb[0, 0, :])
except Exception as e:
print(f"运行错误:{e}")

- 暗通道特征提取 :放弃直接对图像做对比度增强,转而通过
dark_channel()函数计算图像局部窗口内的暗通道(RGB三通道最小值+腐蚀操作),捕捉无雾场景"局部必有暗像素"的核心特征,从物理规律层面定位雾效区域。 - 大气光精准估算 :通过
estimate_atmospheric_light()函数从暗通道最亮的0.1%区域提取原图对应像素的均值作为大气光,精准定位雾的"底色",为后续去除雾效奠定基础。 - 透射率建模求解 :基于大气散射模型和暗通道先验,通过
estimate_transmission()函数推导出透射率的数学表达式,将雾的浓度转化为可量化的透射率值(雾越浓透射率越小);再通过guided_filter()函数执行引导滤波优化透射率,避免去雾后出现块效应。 - 物理层面场景复原 :通过
recover_scene()函数代入大气散射模型反推无雾图像,执行"减大气光→除透射率→加回大气光"的核心步骤,从物理层面还原无雾场景(而非单纯增强对比度);同时设置最小透射率阈值,避免图像过曝发白。 - 四维可视化验证:采用2×2子图布局,同步展示「雾天原图、去雾彩色结果、暗通道图像、优化后透射率」,实现"视觉效果+物理特征"的双重验证,清晰呈现暗通道去雾的全流程效果。
Ⅳ、总结
一、两种去雾方法核心对比
| 对比维度 | 直方图均衡化去雾 | 暗通道先验去雾 |
|---|---|---|
| 核心原理 | 空域对比度增强(视觉层面) | 大气散射模型还原(物理层面) |
| 处理逻辑 | 仅拉伸亮度通道动态范围 | 提取暗通道→估算大气光→求解透射率→复原场景 |
| 色彩处理 | 分离YCrCb亮度通道,避免色彩失真 | 直接对RGB通道做物理建模,保留原始色彩 |
| 去雾效果 | 对比度提升明显,细节还原有限 | 细节还原精准,雾效去除更彻底 |
| 计算复杂度 | 低(仅均衡化+直方图计算) | 高(暗通道+引导滤波等多步运算) |
结果对比

二、核心差异与适用场景
- 直方图均衡化去雾:优势是计算速度快、实现简单,适合对实时性要求高、雾效较浅的场景(如监控画面快速去雾);缺点是仅为"视觉优化",无法从根本上还原雾天遮挡的细节,雾效较浓时易出现色彩失真。
- 暗通道先验去雾:优势是基于物理模型的"真正去雾",能精准还原被雾气遮挡的纹理、色彩细节,适合对去雾精度要求高的场景(如航拍图像、户外视觉检测);缺点是计算成本较高,需依赖引导滤波优化透射率,实时性较差。
三、工程实践建议
- 若项目追求轻量、快速,优先选择直方图均衡化去雾,可结合自适应直方图均衡化(CLAHE)进一步优化效果;
- 若项目追求精准、真实,优先选择暗通道先验去雾,可通过调整窗口大小、ω系数、引导滤波参数适配不同雾浓度图像;
- 两种方法均需注意像素值裁剪(0-255)、异常处理(如图像读取失败),保证工程鲁棒性。
上一章
小波变换:多分辨率分析与图像小波去噪 / 增强 / 融合【计算机视觉】https://blog.csdn.net/R_Feynman_/article/details/158892445