图像去雾的研究算法有很多,应用比较广泛的有:
其主要可以分为 3 类:基于图像增强的去雾算法、基于图像复原的去雾算法和基于 CNN 的去雾算法。
(1) 基于图像增强的去雾算法,通过图像增强技术突出图像细节,提升对比度,使之看起来更加清晰,这类算法的适用性较广。具体的算法有:
此外,在这类算法的基础上出现了众多的基于图像增强原理的改进算法。
(2) 基于图像复原的去雾算法,主要是基于大气散射物理学模型,通过对大量有雾图像和无雾图像进行观察总结,得到其中存在的一些映射关系,然后根据有雾图像的形成过程来进行逆运算,从而恢复清晰图像。其中最经典的要属何恺明提出的:
(3) 基于CNN的去雾算法 使用 CNN 建立一个端到端的模型,通过有雾图像恢复出无雾图像,目前使用神经网络进行去雾的算法主要有两种思路:
CNN 因其强大的学习能力在多个领域得到应用,因此也出现了采用 CNN 进行去雾的算法。2016年CAI等首次提出了一种名为DehazeNet的去雾网络,用于估计有雾图像的透射率。DehazeNet 将有雾的模糊图像作为输入,输出其透射率,基于大气散射模型理论恢复出无雾的清晰图像。
这里采用基于图像增强的自动色彩均衡(Automatic Color Enhancement,ACE) 算法和基于图像复原的暗通道先验(Dark Channel Prior, DCP)去雾算法来实现图像的去雾效果。
1.算法原理&步骤、主要公式
自动色彩均衡(Automatic Color Enhancement,ACE) 算法通过计算图像目标像素点和周围像素点的明暗程度及其关系来对最终的像素值进行校正,实现图像的对比度调整,产生类似人体视网膜的色彩恒常性和亮度恒常性的均衡,具有很好的图像增强效果。
ACE算法包括两个步骤:
(1) 区域自适应滤波 输入图像 (灰度图为例),该步是对单通道图像 中所有点 的区域自适应滤波,得到完成色差校正,空域重构后的中间结果图像,计算公式如下:
式中: 为 两个像素点间灰度差值,表达拟生物学上的侧抑制性; 表示距离度量函数,使用两点间的欧氏距离,作用上控制点j对 的影响权重,映射出滤波的区域适应性; 是亮度表现函数(奇函数),选择经典 函数。
(2) 色调重整拉伸,对图像动态扩展 将式(1)中得到的中间量拉伸映射到 中,占满动态范围 (8位灰度图像),计算公式如下,式中: 是中间量 的全部定义域,该项使图像达到全局白平衡。
2.主要代码
# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
import math
# 线性拉伸处理
# 去掉最大最小0.5%的像素值 线性拉伸至[0,1]
def stretchImage(data, s=0.005, bins=2000):
ht = np.histogram(data, bins);
d = np.cumsum(ht[0]) / float(data.size)
lmin = 0;
lmax = bins - 1
while lmin < bins:
if d[lmin] >= s:
break
lmin += 1
while lmax >= 0:
if d[lmax] <= 1 - s:
break
lmax -= 1
return np.clip((data - ht[1][lmin]) / (ht[1][lmax] - ht[1][lmin]), 0, 1)
# 根据半径计算权重参数矩阵
g_para = {}
def getPara(radius=5):
global g_para
m = g_para.get(radius, None)
if m is not None:
return m
size = radius * 2 + 1
m = np.zeros((size, size))
for h in range(-radius, radius + 1):
for w in range(-radius, radius + 1):
if h == 0 and w == 0:
continue
m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)
m /= m.sum()
g_para[radius] = m
return m
# 常规的ACE实现
def zmIce(I, ratio=4, radius=300):
para = getPara(radius)
height, width = I.shape
zh = []
zw = []
n = 0
while n < radius:
zh.append(0)
zw.append(0)
n += 1
for n in range(height):
zh.append(n)
for n in range(width):
zw.append(n)
n = 0
while n < radius:
zh.append(height - 1)
zw.append(width - 1)
n += 1
# print(zh)
# print(zw)
Z = I[np.ix_(zh, zw)]
res = np.zeros(I.shape)
for h in range(radius * 2 + 1):
for w in range(radius * 2 + 1):
if para[h][w] == 0:
continue
res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -1, 1))
return res
# 单通道ACE快速增强实现
def zmIceFast(I, ratio, radius):
# print(I)
height, width = I.shape[:2]
if min(height, width) <= 2:
return np.zeros(I.shape) + 0.5
Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))
Rf = zmIceFast(Rs, ratio, radius) # 递归调用
Rf = cv2.resize(Rf, (width, height))
Rs = cv2.resize(Rs, (width, height))
return Rf + zmIce(I, ratio, radius) - zmIce(Rs, ratio, radius)
# rgb三通道分别增强 ratio是对比度增强因子 radius是卷积模板半径
def zmIceColor(I, ratio=4, radius=3):
res = np.zeros(I.shape)
for k in range(3):
res[:, :, k] = stretchImage(zmIceFast(I[:, :, k], ratio, radius))
return res
# 主函数
if __name__ == '__main__':
path = "./wutian" # 文件夹目录
files = os.listdir(path) # 得到文件夹下的所有图像文件名称
for file in files: # 遍历文件夹
if not os.path.isdir(file): # 判断是否是文件夹,不是文件夹才打开
# 下面为处理图像的过程
img = cv2.imread(path+"\\"+file)
H = zmIceColor(img / 255.0)
Q = img.astype('float64') / 255.0
arr = np.hstack((Q, H))
# cv2.imshow('ACE_contrast_%s'%file, arr)
cv2.imwrite('ACE_dehaze_%s'%file, H * 255.0)
cv2.imwrite('ACE_contrast_%s'%file, arr * 255.0)
cv2.waitKey(0)
print("图像去雾完成,可点击对比图查看ACE去雾算法去雾效果")
3.实现结果图像
1.算法原理&步骤、主要公式
暗通道先验(Dark Channel Prior, DCP)去雾算法依赖大气散射模型进行去雾处理,通过对大量有雾图像和无雾图像进行观察总结,得到其中存在的一些映射关系,然后根据有雾图像的形成过程来进行逆运算,从而恢复清晰图像。
(1) 大气散射模型在计算机视觉和计算机图形学中,方程所描述的大气散射模型被广泛使用。参数解释如下:
方程右边第一项为场景直接衰减项,第二项为环境光项。
(2) 暗通道定义:在绝大多数非天空的局部区域中,某些像素总会至少有一个颜色通道的值很低。对于一幅图像 ,其暗通道的数学定义表示如下:
其中, 表示以 为中心的局部区域,上标 表示 三个通道。该公式的意义用代码表达也很简单,首先求出每个像素 分量中的最小值,存入一副和原始图像大小相同的灰度图中,然后再对这幅灰度图进行最小值滤波,滤波的半径由窗口大小决定。
(3) 暗通道先验理论 暗通道先验理论指出:对于非天空区域的无雾图像 的暗通道趋于0,即:
(4) 公式变形根据大气散射模型,将第一个公式稍作处理,变形为下式:
假设每一个窗口的透射率 为常数,记为 ,并且 值已给定,对式两边同时进行两次最小值运算,可得:
其中,J(x)是要求的无雾图像,根据前述的暗通道先验理论可知:
因此可推导出:
(5) 透射率计算:将上式带入可得到透射率t’(x)的预估值,如下所示:
现实生活中,即便晴空万里,空气中也会存在一些颗粒,在眺望远处的景物时,人们还是能感觉到雾的存在。另外,雾的存在让人们感受到景深,因此在去雾的同时有必要保留一定程度的雾。可以通过引入一个 到 之 间 的 因 子 (一 般取0.95)对预估透射率进行修正,如式所示:
以上的推导过程均假设大气光值A是已知的,在实际中,可以借助暗通道图从原始雾图中求取。具体步骤如下:
此外,由于透射率t偏小时,会造成J偏大,恢复的无雾图像整体向白场过度,因此有必要对透射率设置一个下限值 (一般取值为 ),当t值小于 时,取 。将以上求得的透射率和大气光值代入公式,最终整理得到图像的恢复公式如下:
2.主要代码
# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
def zmMinFilterGray(src, r=7):
'''最小值滤波,r是滤波器半径'''
return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1)))
def guidedfilter(I, p, r, eps):
height, width = I.shape
m_I = cv2.boxFilter(I, -1, (r, r))
m_p = cv2.boxFilter(p, -1, (r, r))
m_Ip = cv2.boxFilter(I * p, -1, (r, r))
cov_Ip = m_Ip - m_I * m_p
m_II = cv2.boxFilter(I * I, -1, (r, r))
var_I = m_II - m_I * m_I
a = cov_Ip / (var_I + eps)
b = m_p - a * m_I
m_a = cv2.boxFilter(a, -1, (r, r))
m_b = cv2.boxFilter(b, -1, (r, r))
return m_a * I + m_b
def Defog(m, r, eps, w, maxV1): # 输入rgb图像,值范围[0,1]
'''计算大气遮罩图像V1和光照值A, V1 = 1-t/A'''
V1 = np.min(m, 2) # 得到暗通道图像
Dark_Channel = zmMinFilterGray(V1, 7)
V1 = guidedfilter(V1, Dark_Channel, r, eps) # 使用引导滤波优化
bins = 2000
ht = np.histogram(V1, bins) # 计算大气光照A
d = np.cumsum(ht[0]) / float(V1.size)
lmax: int
for lmax in range(bins - 1, 0, -1):
if d[lmax] <= 0.999:
break
A = np.mean(m, 2)[V1 >= ht[1][lmax]].max()
V1 = np.minimum(V1 * w, maxV1) # 对值范围进行限制
return V1, A
def deHaze(m, r=81, eps=0.001, w=0.98, maxV1=0.80, bGamma=False):
Y = np.zeros(m.shape)
Mask_img, A = Defog(m, r, eps, w, maxV1) # 得到遮罩图像和大气光照
for k in range(3):
Y[:,:,k] = (m[:,:,k] - Mask_img)/(1-Mask_img/A) # 颜色校正
Y = np.clip(Y, 0, 1)
if bGamma:
Y = Y ** (np.log(0.5) / np.log(Y.mean())) # gamma校正,默认不进行该操作
return Y
if __name__ == '__main__':
path = "./wutian" # 文件夹目录
files = os.listdir(path) # 得到文件夹下的所有图像文件名称
for file in files: # 遍历文件夹
if not os.path.isdir(file): # 判断是否是文件夹,不是文件夹才打开
# 下面为处理图像的过程
img = cv2.imread(path + "\\" + file)
H = deHaze(img / 255.0)
Q = img.astype('float64') / 255
arr = np.hstack((Q, H))
# cv2.imshow('DCP_contrast_%s'%file, arr)
cv2.imwrite('DCP_dehaze_%s' % file, H * 255)
cv2.imwrite('DCP_contrast_%s' % file, arr * 255)
cv2.waitKey(0)
print("图像去雾完成,可点击对比图查看暗通道先验去雾算法去雾效果")
3.实现结果图像
采用基于图像增强的自动色彩均衡(Automatic Color Enhancement,ACE) 算法和基于图像复原的暗通道先验(Dark Channel Prior, DCP)去雾算法对不同的雾天图像进行处理,从处理结果可以看出,DCP算法去雾效果不错,但是也还有一些问题,比如对带有天空的雾天图片去雾效果不佳;而ACE算法是通过图像的对比度调整来实现去雾,在实际应用的过程中针对不同的情况会存在一定的失真,需要不断的调整参数来校正。