Python影象處理丨詳解影象去霧處理方法

語言: CN / TW / HK

本文分享自華為雲社群《[Python影象處理] 三十.影象預處理之影象去霧詳解(ACE演算法和暗通道先驗去霧演算法)丨【拜託了,物聯網!】》,作者:eastmount 。

一.影象去霧

隨著社會的發展,環境汙染逐漸加劇,越來越多的城市頻繁出現霧霾,這不僅給人們的身體健康帶來危害,還給那些依賴影象資訊的計算機視覺系統造成了不良影響,因為在霧天採集到的影象對比度和飽和度均較低,顏色易發生偏移與失真等。因此,尋找一種簡單有效的影象去霧方法,對計算機視覺的後續研究至關重要。

該部分主要從下列幾篇論文摘取對影象去霧演算法進行普及,引用及參考中文論文:

影象增強(Image Enhancement)是指按照某種特定的需求,突出影象中有用的資訊,去除或者削弱無用的資訊。影象增強的目的是使處理後的影象更適合人眼的視覺特性或易於機器識別。 在醫學成像、遙感成像、人物攝影等領域,影象增強技術都有著廣泛的應用。影象增強同時可以作為目標識別、目標跟蹤、特徵點匹配、影象融合、超解析度重構等影象處理演算法的預處理演算法。

在這裡插入圖片描述

近些年來,出現了眾多的單幅影象去霧演算法,應用比較廣泛的有:

  • 直方圖均衡化去霧演算法
  • Retinex去霧演算法
  • 暗通道先驗去霧演算法
  • 基於卷積神經網路的DehazeNet去霧演算法

其主要可以分為 3 類:基於影象增強的去霧演算法、基於影象復原的去霧演算法和基於 CNN 的去霧演算法。

(1) 基於影象增強的去霧演算法
通過影象增強技術突出影象細節,提升對比度,使之看起來更加清晰,這類演算法的適用性較廣。具體的演算法有:

  • Retinex 演算法

    根據成像原理,消除了反射分量的影響,達到了影象增強去霧的效果 * 直方圖均衡化演算法

    使影象的畫素分佈更加均勻,放大了影象的細節 * 偏微分方程演算法

    將影象視作一個偏微分方程,通過計算梯度場提高對比度 * 小波變換演算法

    對影象進行分解,放大有用的部分

此外,在這類演算法的基礎上出現了眾多的基於影象增強原理的改進演算法。

在這裡插入圖片描述

(2) 基於影象復原的去霧演算法
主要是基於大氣散射物理學模型,通過對大量有霧影象和無霧影象進行觀察總結,得到其中存在的一些對映關係,然後根據有霧影象的形成過程來進行逆運算,從而恢復清晰影象。其中最經典的要屬何愷明大佬提出的:

  • 暗通道先驗去霧演算法

    通過對大量無霧影象進行特徵分析,找到了無霧影象與大氣散射模型中某些引數的先驗關係。該演算法複雜度低,去霧效果好,因此在其基礎上出現了大量基於暗通道先驗的改進演算法。

在這裡插入圖片描述

(3) 基於CNN的去霧演算法
使用 CNN 建立一個端到端的模型,通過有霧影象恢復出無霧影象,目前使用神經網路進行去霧的演算法主要有兩種思路:

  • 使用 CNN 生成大氣散射模型的某些引數,然後再根據大氣散射模型來恢復無霧影象
  • 使用 CNN (例如 GAN)直接根據模糊影象生成無霧的清晰影象

CNN 因其強大的學習能力在多個領域得到應用,因此也出現了採用 CNN 進行去霧的演算法。2016年CAI等首次提出了一種名為DehazeNet的去霧網路,用於估計有霧影象的透射率。DehazeNet 將有霧的模糊影象作為輸入,輸出其透射率,基於大氣散射模型理論恢復出無霧的清晰影象。

在這裡插入圖片描述

下圖是分別對直方圖均衡化、暗通道先驗去霧、DehazeNet和AOD-Net去霧演算法進行測試,實驗結果如圖所示。由圖可知,基於影象增強的直方圖均衡化演算法的去霧影象對比度明顯增強,由於不考慮降質原因,在增加對比度的同時也對噪聲進行了放大,出現細節丟失與色彩偏差現象。基於物理模型的暗通道去霧演算法、基於神經網路的 DehazeNet 和 AOD-Net 演算法的去霧效果較直方圖均衡化演算法更佳。

在這裡插入圖片描述

其他去霧演算法對比結果如下圖所示,比如城市和道路有無影象去霧效果對比。

在這裡插入圖片描述

在這裡插入圖片描述

最後,正如總結王道累老師總結的一樣,目前針對有霧影象去霧的演算法主要是從基於影象增強、影象復原和 CNN 3 個方向進行的。

  • 基於影象增強的方法不考慮有霧影象的形成過程,而是直接通過突出影象的細節,提高對比度等方式,從而使有霧影象看上去更加清晰。
  • 基於影象復原的方法則是追尋影象降質的物理過程,通過物理模型還原出清晰的影象。
  • 基於 CNN 的方法則是利用神經網路強大的學習能力,尋找有霧影象與影象復原物理模型中某些係數的對映關係或者使用 GAN,根據有霧影象還原出無霧的清晰影象。

上述 3 類去霧演算法對於霧天影象都有著明顯的去霧效果,儘管其在實際生活中已經得到了廣泛的應用,但下述幾點仍有可能是今後影象去霧領域的研究重點和難點:

  • 更加真實的霧天影象資料集
    採用神經網路進行去霧的演算法在效果上好於影象增強和復原的方法,但是由於在自然界中很難拍攝到一組背景相同的有霧影象和無霧影象,因此目前訓練神經網路所採用的資料集均是通過合成得到的,雖然能夠在一定程度上擬合自然環境,但是仍然存在著一些差距。所以目前急需一種由在真實環境中獲取到的具有相同背景的有霧影象和無霧影象構建的資料集,來提高神經網路去霧演算法的魯棒性和穩定性。

  • 更加簡便的去霧演算法
    目前各類演算法能夠有效去除單幅影象上的霧霾,但相對較好的演算法都存在著時間複雜度高的問題,很難應用到視訊去霧或者需求較多的複雜任務中去。

  • 魯棒性更強的去霧演算法
    上述演算法都只對影象上存在的均勻的薄霧有較好的去霧效果,對於濃霧或者分佈不均的團霧則效果較差,因此找到一種適用範圍更廣的去霧方法將會是一個極具挑戰性的課題。

二.ACE去霧演算法

1.演算法原理

該部分主要介紹參考作者書籍以及相關論文進行敘述,簡單介紹ACE演算法的原理知識。如果讀者想詳細瞭解其原理,推薦閱讀英文原文,詳見下面的參考文獻,都是大佬。

引用及參考中文論文:

英文原文:

  • http://www.ipol.im/pub/art/2012/g-ace/?utm_source=doi
    Automatic Color Enhancement (ACE) and its Fast Implementation
  • http://www.sciencedirect.com/science/article/abs/pii/S0167865502003239
    A new algorithm for unsupervised global and local color correction(原作者Rizzi大佬)

在這裡插入圖片描述

影象對比度增強的演算法在很多場合都有用處,特別是在醫學影象中,這是因為在眾多疾病的診斷中,醫學影象的視覺檢查時很有必要的。Retinex演算法是代表性的影象增強演算法,它根據人的視網膜和大腦皮層模擬對物體顏色的波長光線反射能力而形成,對複雜環境下的一維條碼具有一定範圍內的動態壓縮,對影象邊緣有著一定自適應的增強。

自動色彩均衡(Automatic Color Enhancement,ACE) 演算法是Rizzi大神在Retinex演算法的理論上提出的,它通過計算影象目標畫素點和周圍畫素點的明暗程度及其關係來對最終的畫素值進行校正,實現影象的對比度調整,產生類似人體視網膜的色彩恆常性和亮度恆常性的均衡,具有很好的影象增強效果。

ACE演算法包括兩個步驟:

  • 一是對影象進行色彩和空域調整,完成影象的色差校正,得到空域重構影象。

    模仿視覺系統的側抑制性和區域自適應性,進行色彩的空域調整。側抑制性是一個生理學概念,指在某個神經元受到刺激而產生興奮時,再刺激相近的神經元,後者所發生的興奮對前者產生的抑制作用。 * 二是對校正後的影象進行動態擴充套件。

    對影象的動態範圍進行全域性調整,並使影象滿足灰度世界理論和白斑點假設。演算法針對單通道,再延伸應用到RGB彩色空間的3通道影象,即對3個通道分別處理再進行整合完成。

(1) 區域自適應濾波
輸入影象I(灰度圖為例),該步是對單通道影象I中所有點p的區域自適應濾波,得到完成色差校正,空域重構後的中間結果影象,計算公式如下:

在這裡插入圖片描述

式中:Ic§-Ic(j)為p、j兩個畫素點間灰度差值,表達擬生物學上的側抑制性;d(p,j)表示距離度量函式,使用兩點間的歐氏距離,作用上控制點j對p的影響權重,映射出濾波的區域適應性;Sa(x)是亮度表現函式(奇函式),本文演算法選擇經典Saturation函式。

在這裡插入圖片描述

不同亮度函式和引數的選擇控制了對比度增強的程度,經典的Saturation函式在飽和前取越大的斜率,結果的對比度增強越明顯,如圖2所示,極限情況是sign函式形式,而Sign函式由於無差別過度增強放大,導致噪聲同樣得到放大效果不佳,最終選擇Saturation函式作為相對亮度表現函式。公式如下:

在這裡插入圖片描述

(2) 色調重整拉伸,對影象動態擴充套件
將式(1)中得到的中間量拉伸對映到 [0, 255] 中,佔滿動態範圍 [0, 255](8位灰度影象),計算公式如下,式中:[minR,maxR]是中間量L(x)的全部定義域,該項使影象達到全域性白平衡。

在這裡插入圖片描述

下圖是條形碼影象進行ACE影象增強後的效果圖,通過影象增強後的圖(b)對比度更強,改善了原影象的明暗程度,增強的同時保持了影象的真實性。

在這裡插入圖片描述

ACE演算法英文介紹如下:

在這裡插入圖片描述

實驗對比效果如下圖所示,大家在寫該主題論文的時候,注意和傳統方法對比。

在這裡插入圖片描述

在這裡插入圖片描述

2.程式碼實現

由於OpenCV中暫時沒有ACE演算法包,下面的程式碼是借鑑“zmshy2128”老師的文章,修改實現的彩色直方圖均衡化處理。後面有機會作者詳細分析其程式碼實現過程。

  • 自動色彩均衡(ACE)快速演算法 - zmshy2128老師

    -- coding: utf-8 --

    By:Eastmount CSDN 2021-03-12

    慘zmshy2128老師文章並修改成Python3程式碼

    import cv2 import numpy as np import math import matplotlib.pyplot as plt

    線性拉伸處理

    去掉最大最小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=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 = radius2+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(h2+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': img = cv2.imread('car.png') res = zmIceColor(img/255.0)*255 cv2.imwrite('car-Ice.jpg', res)

執行結果如圖所示,ACE演算法能有效進行影象去霧處理,實現影象的細節增強。

在這裡插入圖片描述

在這裡插入圖片描述

最後是目標檢測去霧和女神去霧的效果,哈哈,繼續加油!

在這裡插入圖片描述

在這裡插入圖片描述

三.暗通道先驗去霧演算法

該演算法是計算機視覺領域何愷明大佬於2009年提出的影象去霧經典演算法,並獲取當年CVPR最佳論文。論文題目為《Single Image Haze Removal Using Dark Channel Prior》。下圖是大佬的百科簡介,是真的厲害,值得我們大家學習。

  • 2003年5月,何愷明拿到保送清華的資格,是當年執信中學唯一保送上清華大學的學生;高考結果出爐以後,何愷明獲得滿分900分的成績,成為當年廣東省9位滿分狀元之一。
  • 2009年,何愷明成為首獲計算機視覺領域三大國際會議之一CVPR“最佳論文獎”的中國學者。
  • 在2015年的ImageNet影象識別大賽中,何愷明和他的團隊用“影象識別深度差殘學習”系統,擊敗谷歌、英特爾、高通等業界團隊,榮獲第一。
  • 何愷明作為第一作者獲得了CVPR 2009,CVPR 2016和ICCV 2017(Marr Prize)的最佳論文獎,並獲得了ICCV 2017最佳學生論文獎。
  • 2018年,第31屆計算機視覺和模式識別大會(Conference on Computer Vision and Pattern Recognition, CVPR)在美國鹽湖城召開,何愷明獲得本屆大會的PAMI年輕學者獎。

在這裡插入圖片描述

1.演算法原理

言歸正傳,如果是影象處理或研究影象去霧領域的作者,建議大家認真閱讀這篇英文原文,能在2009年提出該演算法真的很驚豔。

引用及參考中文論文:

英文原文:

在這裡插入圖片描述

暗通道先驗(Dark Channel Prior, DCP)去霧演算法 依賴大氣散射模型進行去霧處理,通過對大量有霧影象和無霧影象進行觀察總結,得到其中存在的一些對映關係,然後根據有霧影象的形成過程來進行逆運算,從而恢復清晰影象。

在這裡插入圖片描述

演算法實現過程及原理如下,參考何愷明老師和何濤老師的論文。

(1) 大氣散射模型
在計算機視覺和計算機圖形學中,方程所描述的大氣散射模型被廣泛使用。引數解釋如下:

  • x是影象的空間座標
  • I(x)代表有霧影象(待去霧影象)
  • J(x)代表無霧影象(待恢復影象)
  • A代表全球大氣光值
  • t(x)代表透射率

方程右邊第一項為場景直接衰減項,第二項為環境光項。

在這裡插入圖片描述

(2) 暗通道定義
在絕大多數非天空的區域性區域中,某些畫素總會至少有一個顏色通道的值很低。對於一幅影象J(x),其暗通道的數學定義表示如下:

在這裡插入圖片描述

其中,Ω(x)表示以x為中心的區域性區域,上標c表示RGB三個通道。該公式的意義用程式碼表達也很簡單,首先求出每個畫素RGB分量中的最小值,存入一副和原始影象大小相同的灰度圖中,然後再對這幅灰度圖進行最小值濾波,濾波的半徑由視窗大小決定。

(3) 暗通道先驗理論
暗通道先驗理論指出:對於非天空區域的無霧影象J(x)的暗通道趨於0,即:

在這裡插入圖片描述

實際生活中造成暗原色中低通道值主要有三個因素:

  • a) 汽車、建築物和城市中玻璃窗戶的陰影,或者是樹葉、樹與岩石等自然景觀的投影;
  • b) 色彩鮮豔的物體或表面,在RGB的三個通道中有些通道的值很低(比如綠色的草地/樹/植物,紅色或黃色的花朵/葉子,或者藍色的水面);
  • c) 顏色較暗的物體或者表面,例如灰暗色的樹幹和石頭。

在這裡插入圖片描述

總之,自然景物中到處都是陰影或者彩色,這些景物的影象的暗原色總是很灰暗的,而有霧的影象較亮。因此,可以明顯的看到暗通道先驗理論的普遍性。

在這裡插入圖片描述

在這裡插入圖片描述

(4) 公式變形
根據大氣散射模型,將第一個公式稍作處理,變形為下式:

在這裡插入圖片描述

假設每一個視窗的透射率t(x)為常數,記為t’(x),並且A值已給定,對式兩邊同時進行兩次最小值運算,可得:

在這裡插入圖片描述

其中,J(x)是要求的無霧影象,根據前述的暗通道先驗理論可知:

在這裡插入圖片描述

因此可推匯出:

在這裡插入圖片描述

(5) 透射率計算
將上式帶入可得到透射率t’(x)的預估值,如下所示:

在這裡插入圖片描述

現實生活中,即便晴空萬里,空氣中也會存在一些顆粒,在眺望遠處的景物時,人們還是能感覺到霧的存在。另外,霧的存在讓人們感受到景深,因此在去霧的同時有必要保留一定程度的霧。可以通過引入一個0到1之 間 的 因 子 w(一 般取0.95)對預估透射率進行修正,如式所示:

在這裡插入圖片描述

以上的推導過程均假設大氣光值A是已知的,在實際中,可以藉助暗通道圖從原始霧圖中求取。具體步驟如下:

  • 先求取暗通道圖,在暗通道圖中按照亮度的大小提取最亮的前0.1%的畫素
  • 在原始霧圖I(x)中找對應位置上具有最高亮度的點的值,作為大氣光值A

此外,由於透射率t偏小時,會造成J偏大,恢復的無霧影象整體向白場過度,因此有必要對透射率設定一個下限值t0(一般取值為0.1),當t值小於t0 時,取t=t0。將以上求得的透射率和大氣光值代入公式,最終整理得到影象的恢復公式如下:

在這裡插入圖片描述

這就是暗通道先驗去霧演算法的原理過程,下面簡單補充論文中的處理效果圖。

在這裡插入圖片描述

再次膜拜偶像,極力推薦大家閱讀論文。

2.演算法實現

實現程式碼引用木老師的,感覺比我寫得好,參考如下:

  • openCV+python實現影象去霧 - 木盞老師

    -- coding: utf-8 --

    """ Created on Sat Sep 11 00:16:07 2021 @author: xiuzhang

    參考資料: http://blog.csdn.net/leviopku/article/details/83898619 """

    import sys import cv2 import math import numpy as np

    def DarkChannel(im,sz): b,g,r = cv2.split(im) dc = cv2.min(cv2.min(r,g),b) kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(sz,sz)) dark = cv2.erode(dc,kernel) return dark

    def AtmLight(im,dark): [h,w] = im.shape[:2] imsz = h*w numpx = int(max(math.floor(imsz/1000),1)) darkvec = dark.reshape(imsz,1) imvec = im.reshape(imsz,3)

    indices = darkvec.argsort()
    indices = indices[imsz-numpx::]
    
    atmsum = np.zeros([1,3])
    for ind in range(1,numpx):
       atmsum = atmsum + imvec[indices[ind]]
    
    A = atmsum / numpx;
    return A
    

    def TransmissionEstimate(im,A,sz): omega = 0.95 im3 = np.empty(im.shape,im.dtype)

    for ind in range(0,3):
        im3[:,:,ind] = im[:,:,ind]/A[0,ind]
    
    transmission = 1 - omega*DarkChannel(im3,sz)
    return transmission
    

    def Guidedfilter(im,p,r,eps): mean_I = cv2.boxFilter(im,cv2.CV_64F,(r,r)) mean_p = cv2.boxFilter(p, cv2.CV_64F,(r,r)) mean_Ip = cv2.boxFilter(imp,cv2.CV_64F,(r,r)) cov_Ip = mean_Ip - mean_Imean_p

    mean_II = cv2.boxFilter(im*im,cv2.CV_64F,(r,r))
    var_I   = mean_II - mean_I*mean_I
    
    a = cov_Ip/(var_I + eps)
    b = mean_p - a*mean_I
    
    mean_a = cv2.boxFilter(a,cv2.CV_64F,(r,r))
    mean_b = cv2.boxFilter(b,cv2.CV_64F,(r,r))
    
    q = mean_a*im + mean_b
    return q
    

    def TransmissionRefine(im,et): gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) gray = np.float64(gray)/255 r = 60 eps = 0.0001 t = Guidedfilter(gray,et,r,eps)

    return t
    

    def Recover(im,t,A,tx = 0.1): res = np.empty(im.shape,im.dtype) t = cv2.max(t,tx)

    for ind in range(0,3):
        res[:,:,ind] = (im[:,:,ind]-A[0,ind])/t + A[0,ind]
    
    return res
    

    if name == 'main':

    fn = 'car-02.png'
    src = cv2.imread(fn)
    I = src.astype('float64')/255
    
    dark = DarkChannel(I,15)
    A = AtmLight(I,dark)
    te = TransmissionEstimate(I,A,15)
    t = TransmissionRefine(src,te)
    J = Recover(I,t,A,0.1)
    
    arr = np.hstack((I, J))
    cv2.imshow("contrast", arr)
    cv2.imwrite("car-02-dehaze.png", J*255 )
    cv2.imwrite("car-02-contrast.png", arr*255)
    cv2.waitKey();
    

實現效果如下圖所示:

在這裡插入圖片描述

如果想和後續目標汽車檢測結合,同樣可以先去霧再進行檢測,如下圖所示:

在這裡插入圖片描述

四.影象噪聲和霧生成

影象處理總少不了噪聲新增或生成,下面補充兩個簡單的椒鹽噪聲和霧氣模擬生成的程式碼。這與本文的實驗緊密相關,能為我們提供更多的GAN生成樣本。後面人工智慧系列文章,GAN我們看看能不能學習真實霧化場景的影象,值得期待,哈哈!

1.加鹽噪聲

原圖是一張風景影象:

請新增圖片描述

程式碼如下:

# -*- coding:utf-8 -*-
import cv2
import numpy as np

#讀取圖片
img = cv2.imread("fj.png", cv2.IMREAD_UNCHANGED)
rows, cols, chn = img.shape

#加噪聲
for i in range(50000):    
    x = np.random.randint(0, rows) 
    y = np.random.randint(0, cols)    
    img[x,y,:] = 210

cv2.imshow("noise", img)

#等待顯示
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('fj-res.png',img)

輸出結果如下圖所示:

在這裡插入圖片描述

2.霧的模擬生成

程式碼如下:

import numpy as np
import cv2 as cv
import os
import random

file = ['fj.png']
output = 'fj-wu.png'

for file_img in file:
    #開啟影象
    img = cv.imread(file_img)
    mask_img = cv.imread(file_img)

    #霧的顏色
    mask_img[:, :] = (166, 178, 180)

    #裡面引數可調,主要調整霧的濃度
    image = cv.addWeighted(img,
                           round(random.uniform(0.03, 0.28), 2),
                           mask_img, 1, 0)

    #儲存的資料夾
    cv.imwrite(output, image)

輸出結果如下圖所示,效果還不錯。

在這裡插入圖片描述

請新增圖片描述

點選關注,第一時間瞭解華為雲新鮮技術~