基本原理來源於何凱明大神的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,這個工程裏還包含了視頻去抖,圖像灰度對比對拉伸,以及去燥(這個效果還很差)的代碼。