圖片雜訊處理(三)


在〈圖片雜訊處理(一)〉與〈圖片雜訊處理(二)〉中談過了建立圖片雜訊的基本方式,那麼若圖片本身就有雜訊,該如何去除呢?

首先,「去除」雜訊這件事,本身描述上就不是很正確,對於圖片中的雜訊處,像素資訊已經被雜訊掩蓋而失真,你只能試著在該處填個資訊,令其與周圍像素看來協調一些,或說看來不明顯、不突兀,也就是模糊處理,希望讓肉眼看不出來,或者令後續程式處理時能忽略該處資訊,就這點來看,「抑制」雜訊應該是比較正確的說法。

試著讓雜訊處與周圍像素看來協調一些的方式之一,是取周圍像素值加總後平均,將得到的值取代雜訊處的像素,這種方式稱為均值濾波(Mean filter),OpenCV 提供了 blur 函式,可以指定周圍像素大小的長寬來進行模糊處理,例如指定 3x3:

import cv2
import numpy as np

def salt_pepper_noise(image, fraction, salt_vs_pepper):
    img = np.copy(image)
    size = img.size
    num_salt = np.ceil(fraction * size * salt_vs_pepper).astype('int')
    num_pepper = np.ceil(fraction * size * (1 - salt_vs_pepper)).astype('int')
    row, column = img.shape

    x = np.random.randint(0, column - 1, num_pepper)
    y = np.random.randint(0, row - 1, num_pepper)
    img[y, x] = 0  

    x = np.random.randint(0, column - 1, num_salt)
    y = np.random.randint(0, row - 1, num_salt)
    img[y, x] = 255 
    return img

def white_noise(image, min_noise, max_noise):
    img = np.copy(image)
    noise = np.random.randint(min_noise, max_noise, img.shape)
    return np.clip(img + noise, 0, 255).astype('uint8')

def gaussian_noise(image, mean = 0, sigma = 1):
    img = np.copy(image)
    noise = np.random.normal(mean, sigma, img.shape)
    return np.clip(img + noise, 0, 255).astype('uint8')

img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

s_p_noisy = salt_pepper_noise(img, 0.1, 0.5)
white_noisy = white_noise(img, -5, 5)
gaussian_noisy = gaussian_noise(img, 0, 15)

cv2.imshow('s_p_noisy', cv2.blur(s_p_noisy, (3, 3)))
cv2.imshow('white_noisy', cv2.blur(white_noisy, (3, 3)))
cv2.imshow('gaussian_noisy', cv2.blur(gaussian_noisy, (3, 3)))

cv2.waitKey(0)
cv2.destroyAllWindows()

來看看三種雜訊處理過後的結果:

圖片雜訊處理(三)

由於是取周圍像素平均值,對於選定的像素範圍,均值濾波本身的作用在於抑制範圍內較為高頻訊號,如果範圍內灰階值變化不大,也就是範圍內的訊號屬於低頻,均值濾波會有比較好的效果。

另一方面也就是說,均值濾波處理對於椒鹽雜訊或高頻雜訊的效果不大,畢竟椒鹽雜訊處是 0 或 255,取周圍像素平均後的值會受到這兩個極端值的影響,如果雜訊只出現在圖像的某些部份,擴大像素範圍雖然在某些程度上,可以對高頻雜訊有更大的振制,然而取平均的結果就是,邊緣細節容易失去,圖像會更為模糊。

如果取鄰近像素值排序後取中間數,稱為中值濾波(Median filter),OpenCV 的 medianBlur 函式提供了實作。例如:

... 同前 ... 略
img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

s_p_noisy = salt_pepper_noise(img, 0.1, 0.5)
white_noisy = white_noise(img, -5, 5)
gaussian_noisy = gaussian_noise(img, 0, 15)

cv2.imshow('s_p_noisy', cv2.medianBlur(s_p_noisy, 3))
cv2.imshow('white_noisy', cv2.medianBlur(white_noisy, 3))
cv2.imshow('gaussian_noisy', cv2.medianBlur(gaussian_noisy, 3))

cv2.waitKey(0)
cv2.destroyAllWindows()

來看看效果:

圖片雜訊處理(三)

因為是取排序後的中值,像素選擇範圍內差異極大的像素比較不會被選中,若鄰近像素範圍選擇適當,中值濾波可以有效地抑制高頻雜訊,畢竟排序後,因雜訊而差異大的像素值會是兩側,比較不會被選中,例如椒鹽雜訊這邊看來抑制的效果不錯。

中值濾波也能夠保留較多邊緣的細節,因為圖像邊緣處往往是灰階變化大的地方,若選定的範圍是在圖像邊緣處,排序後大致上是一邊很高,一邊偏很低,選擇的中值基本上會落在其中一邊,較容易維持邊緣的細節,只不過處理後的邊緣不免會產生鋸齒狀。

方才談到均值濾波的原理,是單純取鄰近像素的平均值,高斯濾波的原理也是取鄰近像素平均值,然而計算平均值時會考慮鄰近像素的權重,權重是透過二維的高斯函式來計算,也就是考量了一定範圍內的雜訊會具有高斯分佈。

實際環境中不少雜訊分佈就具有此特性,因此高斯濾波常用來抑制照片中的雜訊,缺點就是計算上比較耗時,OpenCV 的 GaussianBlur 函式提供了實現,例如:

... 同前 ... 略
img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

s_p_noisy = salt_pepper_noise(img, 0.1, 0.5)
white_noisy = white_noise(img, -5, 5)
gaussian_noisy = gaussian_noise(img, 0, 15)

cv2.imshow('s_p_noisy', cv2.GaussianBlur(s_p_noisy, (3, 3), 15))
cv2.imshow('white_noisy', cv2.GaussianBlur(white_noisy, (3, 3), 15))
cv2.imshow('gaussian_noisy', cv2.GaussianBlur(gaussian_noisy, (3, 3), 15))

cv2.waitKey(0)
cv2.destroyAllWindows()

來看看效果,在保留細節部份,高斯濾波有不錯的表現,對於低頻雜訊也有不錯的效果:

圖片雜訊處理(三)

均值濾波、中值濾波、高斯濾波等,是認識雜訊抑制(也是模糊)時很好的一個起點,它們都利用像素的局部資訊,來達到抑制雜訊之目的;然而,雜訊抑制還有許多其他方式,例如,透過分析整個圖片的資訊,來達到抑制雜訊的目的,在 OpenCV 的官方文件〈Image Denoising〉,就有談到這類雜訊抑制的相關函式,有進一步雜訊處理的需求時可以參考。