1. 程式人生 > >Python 影象處理 OpenCV (7):影象平滑(濾波)處理

Python 影象處理 OpenCV (7):影象平滑(濾波)處理

![](https://cdn.geekdigging.com/opencv/opencv_header.png) 前文傳送門: [「Python 影象處理 OpenCV (1):入門」](https://www.geekdigging.com/2020/05/17/5513454552/) [「Python 影象處理 OpenCV (2):畫素處理與 Numpy 操作以及 Matplotlib 顯示影象」](https://www.geekdigging.com/2020/05/18/4936041986/) [「Python 影象處理 OpenCV (3):影象屬性、影象感興趣 ROI 區域及通道處理」](https://www.geekdigging.com/2020/05/19/1227329671/) [「Python 影象處理 OpenCV (4):影象算數運算以及修改顏色空間」](https://www.geekdigging.com/2020/05/21/1757913240/) [「Python 影象處理 OpenCV (5):影象的幾何變換」](https://www.geekdigging.com/2020/05/23/4331122737/) [「Python 影象處理 OpenCV (6):影象的閾值處理」](https://www.geekdigging.com/2020/06/03/6651375581/) ## 1. 引言 第一件事情還是先做名詞解釋,影象平滑到底是個啥? 從字面意思理解貌似影象平滑好像是在說影象滑動。 emmmmmmmmmmmmmmm。。。。 其實半毛錢關係也沒有,影象平滑技術通常也被成為影象濾波技術(這個名字看到可能大家會有點感覺)。 每一幅影象都包含某種程度的噪聲,噪聲可以理解為由一種或者多種原因造成的灰度值的隨機變化,如由光子通量的隨機性造成的噪聲等等。 而影象平滑技術或者是影象濾波技術就是用來處理影象上的噪聲,其中,能夠具備邊緣保持作用的影象平滑處理,成為了大家關注的重點。 這不廢話,處理個圖片降噪,結果把整個影象搞的跟玻璃上糊上了一層水霧一樣,這種降噪有啥意義。 本文會介紹 OpenCV 中提供的影象平滑的 4 個演算法: * 均值濾波 * 方框濾波 * 高斯濾波 * 中值濾波 下面開始一個一個看吧:) 先給出一個給馬里奧加噪聲的程式,程式來源於楊老師的部落格:https://blog.csdn.net/Eastmount/article/details/82216380 ,完整程式碼如下: ```python import cv2 as cv import numpy as np # 讀取圖片 img = cv.imread("maliao.jpg", cv.IMREAD_UNCHANGED) rows, cols, chn = img.shape # 加噪聲 for i in range(5000): x = np.random.randint(0, rows) y = np.random.randint(0, cols) img[x, y, :] = 255 cv.imshow("noise", img) # 影象儲存 cv.imwrite("maliao_noise.jpg", img) # 等待顯示 cv.waitKey() cv.destroyAllWindows() ``` ![](https://cdn.geekdigging.com/opencv/07/maliao_noise.jpg) 上面這段程式實際上是在圖片上隨機加了 5000 個白點,這個噪聲真的是夠大的了。 ## 2. 2D 影象卷積 在介紹濾波之前先簡單介紹下 2D 影象卷積,影象卷積其實就是影象過濾。 影象過濾的時候可以使用各種低通濾波器( LPF ),高通濾波器( HPF )等對影象進行過濾。 低通濾波器( LPF )有助於消除噪聲,但是會使影象模糊。 高通濾波器( HPF )有助於在影象中找到邊緣。 OpenCV 為我們提供了一個函式 `filter2D()` 來將核心與影象進行卷積。 我們嘗試對影象進行平均濾波, 5 x 5 平均濾波器核心如下: $$ K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix} $$ 具體操作如下: 我們保持這個核心在一個畫素上,將所有低於這個核心的 25 個畫素相加,取其平均值,然後用新的平均值替換中心畫素。它將對影象中的所有畫素繼續此操作,完整的示例程式碼如下: ```python import numpy as np import cv2 as cv from matplotlib import pyplot as plt # 讀取圖片 img = cv.imread("maliao_noise.jpg", cv.IMREAD_UNCHANGED) rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB) kernel = np.ones((5,5),np.float32)/25 dst = cv.filter2D(rgb_img, -1, kernel) titles = ['Source Image', 'filter2D Image'] images = [rgb_img, dst] for i in range(2): plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://cdn.geekdigging.com/opencv/07/filter2D_result.png) 可以看到,噪點確實去除掉了,就是圖片變得模糊起來。 ## 3. 均值濾波 均值濾波是指任意一點的畫素值,都是周圍 N * M 個畫素值的均值。 其實均值濾波和上面的那個影象卷積的示例,做了同樣的事情,我只是用 `filter2D()` 這個方法手動完成了均值濾波,實際上 OpenCV 為我們提供了專門的均值濾波的方法,前面影象卷積沒有看明白的同學,可以再一遍均值濾波,我儘量把這個事情整的明白的。 還是來畫個圖吧: ![](https://cdn.geekdigging.com/opencv/07/junzhilb_1.png) 中間那個紅色的方框裡面的值,是周圍 25 個格子區域中的畫素的和去除以 25 ,這個公式是下面這樣的: $$ K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix} $$ 我為了偷懶,所有的格子裡面的畫素值都寫成 1 ,畢竟 n / n 永遠都等於 1 ,快誇我機智。 上面這個 5 * 5 的矩陣稱為核,針對原始影象內的畫素點,採用核進行處理,得到結果影象。 這個核我們可以自定義大小,比如 5 * 5 ,3 * 3 , 10 * 10 等等,具體定義多大完全看療效。 OpenCV 為我提供了 `blur()` 方法用作實現均值濾波,原函式如下: ```python def blur(src, ksize, dst=None, anchor=None, borderType=None) ``` * kSize: 核心引數,其實就是圖片進行卷積的時候相乘的那個矩陣,具體的卷積是如何算的,網上有很多,我這裡就不介紹了,所得到的影象是模糊的,而且影象其實是按照原來的比例缺少了(原影象-核心引數+1)^2 個單元格。 * anchor: Point 型別,即錨點,有預設值 Point(-1, -1) ,當座標為負值,就表示取核的中心。 * borderType: Int 型別,用於推斷影象外部畫素的某種邊界模式,有預設值 BORDER_DEFAULT 。 接下來是均值濾波的示例程式碼: ```python import cv2 as cv import matplotlib.pyplot as plt # 讀取圖片 img = cv.imread("maliao_noise.jpg", cv.IMREAD_UNCHANGED) rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB) # 均值濾波 blur_img = cv.blur(rgb_img, (3, 3)) # blur_img = cv.blur(img, (5, 5)) # blur_img = cv.blur(img, (10, 10)) # blur_img = cv.blur(img, (20, 20)) titles = ['Source Image', 'Blur Image'] images = [rgb_img, blur_img] for i in range(2): plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://cdn.geekdigging.com/opencv/07/blur_result.png) 這個降噪的效果好像沒有前面 2D 卷積的那個降噪效果好,但是影象更為清晰,因為我在這個示例中使用了更小的核 3 * 3 的核,順便我也試了下大核,比如程式碼中註釋掉的 10 * 10 的核或者 20 * 20 的核,實時證明,核越大降噪效果越好,但是相反的是影象會越模糊。 ## 4. 方框濾波 方框濾波和均值濾波核基本一致,其中的區別是需不需要進行歸一化處理。 什麼是歸一化處理等下再說,我們先看方框濾波的原函式: ```python def boxFilter(src, ddepth, ksize, dst=None, anchor=None, normalize=None, borderType=None) ``` * src: 原始影象。 * ddepth: Int 型別,目標影象深度,通常用 -1 表示與原始影象一致。 * kSize: 核心引數。 * dst: 輸出與 src 大小和型別相同的影象。 * anchor: Point 型別,即錨點,有預設值 Point(-1, -1) 。 * normalize: Int 型別,表示是否對目標影象進行歸一化處理。 當 normalize 為 true 時,需要執行均值化處理。 當 normalize 為 false 時,不進行均值化處理,實際上是求周圍各畫素的和,很容易發生溢位,溢位時均為白色,對應畫素值為 255 。 完整示例程式碼如下: ```python import cv2 as cv import matplotlib.pyplot as plt # 讀取圖片 img = cv.imread('maliao_noise.jpg') source = cv.cvtColor(img, cv.COLOR_BGR2RGB) # 方框濾波 result = cv.boxFilter(source, -1, (5, 5), normalize = 1) # 顯示圖形 titles = ['Source Image', 'BoxFilter Image'] images = [source, result] for i in range(2): plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://cdn.geekdigging.com/opencv/07/boxfilter_result1.png) 當我們把 normalize 的屬性設為 0 時,不進行歸一化處理,結果就變成了下面這個樣子: ![](https://cdn.geekdigging.com/opencv/07/boxfilter_result2.png) ## 5. 高斯濾波 為了克服簡單區域性平均法的弊端(影象模糊),目前已提出許多保持邊緣、細節的區域性平滑演算法。它們的出發點都集中在如何選擇鄰域的大小、形狀和方向、引數加平均及鄰域各店的權重係數等。 在高斯濾波的方法中,實際上是把卷積核換成了高斯核,那麼什麼是高斯核呢? 簡單來講就是方框還是那個方框,原來每個方框裡面的權是相等的,大家最後取平均,現在變成了高斯分佈的,方框中心的那個權值最大,其餘方框根據距離中心元素的距離遞減,構成一個高斯小山包,這樣取到的值就變成了加權平均。 下圖是所示的是 3 * 3 和 5 * 5 領域的高斯核。 ![](https://cdn.geekdigging.com/opencv/07/gaosihe.jpg) 高斯濾波是在 OpenCV 中是由 `GaussianBlur()` 方法進行實現的,它的原函式如下: ```python def GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None) ``` * sigmaX: 表示 X 方向方差。 這裡需要注意的是 ksize 核大小,在高斯核當中,核 (N, N) 必須是奇數, X 方向方差主要控制權重。 完整的示例程式碼如下: ```python import cv2 as cv import matplotlib.pyplot as plt # 讀取圖片 img = cv.imread('maliao_noise.jpg') source = cv.cvtColor(img, cv.COLOR_BGR2RGB) # 方框濾波 result = cv.GaussianBlur(source, (3, 3), 0) # 顯示圖形 titles = ['Source Image', 'GaussianBlur Image'] images = [source, result] for i in range(2): plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://cdn.geekdigging.com/opencv/07/gaussianblur_result.png) ## 6. 中值濾波 在使用鄰域平均法去噪的同時也使得邊界變得模糊。 而中值濾波是非線性的影象處理方法,在去噪的同時可以兼顧到邊界資訊的保留。 中值濾波具體的做法是選一個含有奇數點的視窗 W ,將這個視窗在影象上掃描,把視窗中所含的畫素點按灰度級的升或降序排列,取位於中間的灰度值來代替該點的灰度值。 下圖是一個一維的視窗的濾波過程: ![](https://cdn.geekdigging.com/opencv/07/zhongzhilvbo_yiwei.jpg) 在 OpenCV 中,主要是通過呼叫 `medianBlur()` 來實現中值濾波,它的原函式如下: ```python def medianBlur(src, ksize, dst=None) ``` 中值濾波的核心數和高斯濾波的核心數一樣,必須要是大於 1 的奇數。 示例程式碼如下: ```python import cv2 as cv import matplotlib.pyplot as plt # 讀取圖片 img = cv.imread('maliao_noise.jpg') source = cv.cvtColor(img, cv.COLOR_BGR2RGB) # 方框濾波 result = cv.medianBlur(source, 3) # 顯示圖形 titles = ['Source Image', 'medianBlur Image'] images = [source, result] for i in range(2): plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://cdn.geekdigging.com/opencv/07/medianblur_result.png) 可以明顯看到,目前中值濾波是對原影象降噪後還原度最高的,常用的中值濾波的圖形除了可以使用方框,還有十字形、圓形和環形,不同形狀的視窗產生不同的濾波效果。 ![](https://cdn.geekdigging.com/opencv/07/zhongzhilvbo_yiwei_others.jpg) 方形和圓形視窗適合外輪廓線較長的物體影象,而十字形視窗對有尖頂角狀的影象效果好。 對於一些細節較多的複雜影象,可以多次使用不同的中值濾波。 ## 7. 示例程式碼 如果有需要獲取原始碼的同學可以在公眾號回覆「OpenCV」進行獲取。 ## 8. 參考 https://blog.csdn.net/Eastmount/article/details/82216380 http://www.woshic