圖像去霧----暗通道

暗通道去霧算法原理及實現

 

1. 算法原理。

基本原理來源於何凱明大神的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

 
 

上面的推導都是假設全球大氣光是已知的,實際中,咱們能夠藉助暗通道圖來從有霧圖像中來獲取該值:函數

  1. 從暗通道圖中按照亮度大小取前0.1%的像素。
  2. 在這些位置中,在原始圖像中尋找對應具備最高亮度點的值,做爲A值。

到這裏,咱們就能夠進行無霧圖像的恢復了:測試

 
 

當投射圖t很小時,會致使J的值偏大,會致使圖片某些地方過爆,因此通常能夠設置一個閾值來限制,咱們設置一個閾值:通常設置較小,0.1便可。優化

 
 

利用這個理論的去霧效果就不錯了,下面是我在網上找的例子:ui

 
 
 
 

可是這個去霧效果仍是挺粗糙的,主要緣由是因爲透射率圖過於粗糙了,何凱明在文章中提出了soft matting方法,而後其缺點是速度特別慢,不適用在實時場合,2011年,又提出可使用導向濾波的方式來得到更細膩的結果,這個方法的運算主要集中在方框濾波(均值濾波),而這種操做在opencv或者其餘的圖像庫中都有快速算法。能夠考慮使用。

2.代碼實現。

我很快在網上找到一個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,這個工程裏還包含了視頻去抖,圖像灰度對比對拉伸,以及去燥(這個效果還很差)的代碼。

3. 各參數的影響。

  1. 暗通道最小值濾波半徑r。
    這個半徑對於去霧效果是有影響的。必定狀況下,半徑越大去霧的效果越不明顯,建議的範圍通常是5-25之間,通常選擇5,7,9等就會取得不錯的效果。
  2. w的影響天然也是很大的。
    這個值是咱們設置的保留霧的程度(c++代碼中w是去除霧的程度,通常設置爲0.95就能夠了)。這個基本不用修改。
  3. 導向濾波中均值濾波半徑。
    這個半徑建議取值不小於求暗通道時最小值濾波半徑的4倍。由於前面最小值後暗通道時一塊一塊的,爲了使得透射率圖更加精細,這個r不能太小(很容易理解,若是這個r和和最小值濾波的同樣的話,那麼在進行濾波的時候包含的塊信息就不多,仍是容易出現一塊一塊的形狀)。
  4. eps,這個值只是保證除號下面不是0,通常取較小,0.001是一個經常使用的值。

4. notes。

  1. 這個去霧算法只針對彩色圖像,並且對於低對比度的天空或者水面背景的去霧效果會產生塊效應,去霧效果很差,並且這種效應並不能經過調參來避免。
  2. 暗通道去霧使得圖像總體的亮度有所下降,因此在最後能夠自適應的提升亮度來減輕這種現象。
  3. 導向濾波在matlab中有現成函數,在opencv contrib裏也有函數能夠調用,另外爲了加速運算能夠下采樣以後進行濾波而後再上採樣恢復。
相關文章
相關標籤/搜索