一、算法背景
何凯明团队于2009年提出的暗通道先验去雾算法《single image haze removal using dark channel prior》,通过统计发现:在无雾图像的局部区域中,至少存在一个颜色通道的像素值趋近于零。这一发现为图像去雾提供了重要的理论依据,其数学模型可表示为:
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))
其中:
- I ( x ) I(x) I(x):观测到的有雾图像
- J ( x ) J(x) J(x):待恢复的无雾图像
- t ( x ) t(x) t(x):透射率
- A A A:全局大气光值
二、算法原理
1. 暗通道计算
通过取RGB三通道最小值并进行形态学腐蚀操作:
def dark_channel(img, size=15):r, g, b = cv2.split(img)min_img = cv2.min(r, cv2.min(g, b))kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))return cv2.erode(min_img, kernel)
2. 大气光估计
选取图像中最亮像素的0.1%作为大气光值:
def get_atmo(img, percent=0.001):mean_perpix = np.mean(img, axis=2).reshape(-1)mean_topper = mean_perpix[:int(img.shape[0] * img.shape[1] * percent)]return np.mean(mean_topper)
3. 透射率估计
t ( x ) = 1 − ω ⋅ d a r k _ c h a n n e l ( I A ) t(x) = 1 - \omega \cdot dark\_channel\left(\frac{I}{A}\right) t(x)=1−ω⋅dark_channel(AI)
def get_trans(img, atom, w=0.95):x = img / atomreturn 1 - w * dark_channel(x, 7)
4. 引导滤波优化
使用灰度图作为引导图像进行透射率优化:
# 引导滤波
def guided_filter(p, i, r, e):""":param p: input image:param i: guidance image:param r: radius:param e: regularization:return: filtering output q"""# 1mean_I = cv2.boxFilter(i, cv2.CV_64F, (r, r))mean_p = cv2.boxFilter(p, cv2.CV_64F, (r, r))corr_I = cv2.boxFilter(i * i, cv2.CV_64F, (r, r))corr_Ip = cv2.boxFilter(i * p, cv2.CV_64F, (r, r))# 2var_I = corr_I - mean_I * mean_Icov_Ip = corr_Ip - mean_I * mean_p# 3a = cov_Ip / (var_I + e)b = mean_p - a * mean_I# 4mean_a = cv2.boxFilter(a, cv2.CV_64F, (r, r))mean_b = cv2.boxFilter(b, cv2.CV_64F, (r, r))# 5q = mean_a * i + mean_breturn q
三、完整实现代码
import cv2
import numpy as np
import os# 计算雾化图像的暗通道
def dark_channel(img, size=15):r, g, b = cv2.split(img)min_img = cv2.min(r, cv2.min(g, b)) # 取最暗通道kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))dc_img = cv2.erode(min_img, kernel)return dc_img# 估计全局大气光值
def get_atmo(img, percent=0.001):mean_perpix = np.mean(img, axis=2).reshape(-1)mean_topper = mean_perpix[:int(img.shape[0] * img.shape[1] * percent)]return np.mean(mean_topper)# 估算透射率图
def get_trans(img, atom, w=0.95):x = img / atomt = 1 - w * dark_channel(x, 15)return t# 引导滤波
def guided_filter(p, i, r, e):""":param p: input image:param i: guidance image:param r: radius:param e: regularization:return: filtering output q"""# 1mean_I = cv2.boxFilter(i, cv2.CV_64F, (r, r))mean_p = cv2.boxFilter(p, cv2.CV_64F, (r, r))corr_I = cv2.boxFilter(i * i, cv2.CV_64F, (r, r))corr_Ip = cv2.boxFilter(i * p, cv2.CV_64F, (r, r))# 2var_I = corr_I - mean_I * mean_Icov_Ip = corr_Ip - mean_I * mean_p# 3a = cov_Ip / (var_I + e)b = mean_p - a * mean_I# 4mean_a = cv2.boxFilter(a, cv2.CV_64F, (r, r))mean_b = cv2.boxFilter(b, cv2.CV_64F, (r, r))# 5q = mean_a * i + mean_breturn qdef dehaze(im):img = im.astype('float64') / 255img_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY).astype('float64') / 255atom = get_atmo(img)trans = get_trans(img, atom)trans_guided = guided_filter(trans, img_gray, 20, 0.0001)trans_guided = cv2.max(trans_guided, 0.25)result = np.empty_like(img)for i in range(3):result[:, :, i] = (img[:, :, i] - atom) / trans_guided + atomreturn result * 255if __name__ == '__main__':image_path= 'images/img.png'im = cv2.imread(image_path)img = im.astype('float64') / 255img_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY).astype('float64') / 255atom = get_atmo(img)trans = get_trans(img, atom)trans_guided = guided_filter(trans, img_gray, 10, 0.0001)trans_guided = cv2.max(trans_guided, 0.25)result = np.empty_like(img)for i in range(3):result[:, :, i] = (img[:, :, i] - atom) / trans_guided + atomcv2.imwrite('images/img.png', result * 255)