基本原理来源于何凯明大神的CVPR09的论文Single Image Haze Removal Using Dark Channel Priorpython
暗通道先验理论指出:c++
暗通道其实是在rgb三个通道中取最小值组成灰度图,而后再进行一个最小值滤波获得的。咱们来看一下有雾图像和无雾图像暗通道的区别:git
能够发现,有雾的时候会呈现必定的灰色,而无雾的时候咋会呈现大量的黑色(像素为接近0),做者统计了5000多副图像的特征,基本都符合这样一条先验定理。github
其中I(x)是现有的图像(待去雾),J(x)是要恢复的原无雾图像,A是全球大气光成分,t(x)是透射率,如今的条件就是已知I(x),来求J(x),显然不加任何限制的话是有无穷多个解的。算法
可是现实生活中,即便是晴天白云,空气中也会存在一些颗粒,看远方的物体仍是可以感受到雾的影响,另外,雾的存在可让人们感受到景深的存在,因此咱们保留一部分的雾,上式修正为:其中w是[0-1]之间的一个值,通常取0.95差很少。ide
上面的推导都是假设全球大气光是已知的,实际中,咱们能够借助暗通道图来从有雾图像中来获取该值:函数
- 从暗通道图中按照亮度大小取前0.1%的像素。
- 在这些位置中,在原始图像中寻找对应具备最高亮度点的值,做为A值。
到这里,咱们就能够进行无雾图像的恢复了:测试
当投射图t很小时,会致使J的值偏大,会致使图片某些地方过爆,因此通常能够设置一个阈值来限制,咱们设置一个阈值:通常设置较小,0.1便可。优化
利用这个理论的去雾效果就不错了,下面是我在网上找的例子:ui
可是这个去雾效果仍是挺粗糙的,主要缘由是因为透射率图过于粗糙了,何凯明在文章中提出了soft matting方法,而后其缺点是速度特别慢,不适用在实时场合,2011年,又提出可使用导向滤波的方式来得到更细腻的结果,这个方法的运算主要集中在方框滤波(均值滤波),而这种操做在opencv或者其余的图像库中都有快速算法。能够考虑使用。
我很快在网上找到一个python版本的算法:
1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sat Jun 9 11:28:14 2018 4 5 @author: zhxing 6 """ 7 8 import cv2 9 import numpy as np 10 11 def zmMinFilterGray(src, r=7): 12 '''''最小值滤波,r是滤波器半径''' 13 return cv2.erode(src,np.ones((2*r-1,2*r-1))) 14 # ============================================================================= 15 # if r <= 0: 16 # return src 17 # h, w = src.shape[:2] 18 # I = src 19 # res = np.minimum(I , I[[0]+range(h-1) , :]) 20 # res = np.minimum(res, I[range(1,h)+[h-1], :]) 21 # I = res 22 # res = np.minimum(I , I[:, [0]+range(w-1)]) 23 # res = np.minimum(res, I[:, range(1,w)+[w-1]]) 24 # ============================================================================= 25 # return zmMinFilterGray(res, r-1) 26 27 def guidedfilter(I, p, r, eps): 28 '''''引导滤波,直接参考网上的matlab代码''' 29 height, width = I.shape 30 m_I = cv2.boxFilter(I, -1, (r,r)) 31 m_p = cv2.boxFilter(p, -1, (r,r)) 32 m_Ip = cv2.boxFilter(I*p, -1, (r,r)) 33 cov_Ip = m_Ip-m_I*m_p 34 35 m_II = cv2.boxFilter(I*I, -1, (r,r)) 36 var_I = m_II-m_I*m_I 37 38 a = cov_Ip/(var_I+eps) 39 b = m_p-a*m_I 40 41 m_a = cv2.boxFilter(a, -1, (r,r)) 42 m_b = cv2.boxFilter(b, -1, (r,r)) 43 return m_a*I+m_b 44 45 def getV1(m, r, eps, w, maxV1): #输入rgb图像,值范围[0,1] 46 '''''计算大气遮罩图像V1和光照值A, V1 = 1-t/A''' 47 V1 = np.min(m,2) #获得暗通道图像 48 V1 = guidedfilter(V1, zmMinFilterGray(V1,7), r, eps) #使用引导滤波优化 49 bins = 2000 50 ht = np.histogram(V1, bins) #计算大气光照A 51 d = np.cumsum(ht[0])/float(V1.size) 52 for lmax in range(bins-1, 0, -1): 53 if d[lmax]<=0.999: 54 break 55 A = np.mean(m,2)[V1>=ht[1][lmax]].max() 56 57 V1 = np.minimum(V1*w, maxV1) #对值范围进行限制 58 59 return V1,A 60 61 def deHaze(m, r=81, eps=0.001, w=0.95, maxV1=0.80, bGamma=False): 62 Y = np.zeros(m.shape) 63 V1,A = getV1(m, r, eps, w, maxV1) #获得遮罩图像和大气光照 64 for k in range(3): 65 Y[:,:,k] = (m[:,:,k]-V1)/(1-V1/A) #颜色校订 66 Y = np.clip(Y, 0, 1) 67 if bGamma: 68 Y = Y**(np.log(0.5)/np.log(Y.mean())) #gamma校订,默认不进行该操做 69 return Y 70 71 if __name__ == '__main__': 72 m = deHaze(cv2.imread('test.jpg')/255.0)*255 73 cv2.imwrite('defog.jpg', m)
最小值滤波我给用腐蚀来替代了,其实腐蚀就是最小值滤波,最大值滤波是膨胀。这个测试效果还不错。
这份python代码中使用的是暗通道和RGB图像的最小值图像(其实是一种灰度图)来进行导向滤波,我试着用灰度图和暗通道来作,也是能够的,效果区别不大。
这个python版本跑的仍是挺慢的,600-500的图像须要花费近0.1s的时间,我照着这个写了一个c++版本的,速度立马提升一倍,代码比python要长一些,就不在这里贴了,相同的图像速度能够提升一倍以上,若是加上GPU加速的话应该能够实现实时处理。
c++ code,这个工程里还包含了视频去抖,图像灰度对比对拉伸,以及去燥(这个效果还很差)的代码。