圖像去霧----暗通道


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

 

1. 算法原理。

基本原理來源於何凱明大神的CVPR09的論文Single Image Haze Removal Using Dark Channel Prior

  • 暗通道。
    所謂暗通道是一個基本假設,這個假設認為,在絕大多數的非天空的局部區域中,某一些像素總會有至少一個顏色通道具有很低的值。這個其實很容易理解,實際生活中造成這個假設的原因有很多,比如汽車,建築物或者城市中的陰影,或者說色彩鮮艷的物體或表面(比如綠色的樹葉,各種鮮艷的花,或者藍色綠色的睡眠),顏色較暗的物體或者表面,這些景物的暗通道總是變現為比較暗的狀態。
    所以暗通道是什么呢?其實比較簡單,作者認為暗通道是:
 
 

暗通道先驗理論指出:

 
 

暗通道實際上是在rgb三個通道中取最小值組成灰度圖,然后再進行一個最小值濾波得到的。我們來看一下有霧圖像和無霧圖像暗通道的區別:

 
 
 
 

可以發現,有霧的時候會呈現一定的灰色,而無霧的時候咋會呈現大量的黑色(像素為接近0),作者統計了5000多副圖像的特征,基本都符合這樣一條先驗定理。

  • 霧圖形成模型
    計算機視覺中,下面這個霧圖形成模型是被廣泛使用的:
 
 

其中I(x)是現有的圖像(待去霧),J(x)是要恢復的原無霧圖像,A是全球大氣光成分,t(x)是透射率,現在的條件就是已知I(x),來求J(x),顯然不加任何限制的話是有無窮多個解的。

 
 

但是現實生活中,即使是晴天白雲,空氣中也會存在一些顆粒,看遠方的物體還是能夠感覺到霧的影響,另外,霧的存在可以讓人們感覺到景深的存在,所以我們保留一部分的霧,上式修正為:其中w是[0-1]之間的一個值,一般取0.95差不多。

 
 

上面的推導都是假設全球大氣光是已知的,實際中,我們可以借助暗通道圖來從有霧圖像中來獲取該值:

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

到這里,我們就可以進行無霧圖像的恢復了:

 
 

當投射圖t很小時,會導致J的值偏大,會導致圖片某些地方過爆,所以一般可以設置一個閾值來限制,我們設置一個閾值:一般設置較小,0.1即可。

 
 

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

 
 
 
 

但是這個去霧效果還是挺粗糙的,主要原因是由於透射率圖過於粗糙了,何凱明在文章中提出了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里也有函數可以調用,另外為了加速運算可以下采樣之后進行濾波然后再上采樣恢復。





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM