有時候會想為圖片加上雜訊,例如增加圖片的粗糙感,以製造相片的懷舊風格,或者是後製出物體表面的磨砂材質感覺,一些影像處理軟體,就會提供增加雜訊之類的濾鏡。
雜訊的製造方式很多,認識一下雜訊的原理,會有助於選擇合適的雜訊處理方式,而在需要去除雜訊的場合,也可以觀察雜訊是否為某種形成方式,對於雜訊去除的效率也會有幫助。
以灰階圖片為例,如果要在圖片增加雜訊,方式之一是圖片撒上一些黑點與白點,例如:
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
fraction = 0.1 # 雜訊佔圖的比例
salt_vs_pepper = 0.5 # 鹽與胡椒的比例
img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)
noisy = salt_pepper_noise(img, fraction, salt_vs_pepper)
cv2.imshow('Salt & Pepper Noise', noisy)
cv2.waitKey(0)
cv2.destroyAllWindows()
黑點就好比胡椒,白點就像是鹽,這種加上雜訊的方式,就稱為椒鹽雜訊(Salt & Pepper Noise),完成的效果圖如下:
椒鹽雜訊是認識雜訊處理時一個很好的起點,在更進一步之前,先來看看分析圖片像素值時一個常見的視覺化工具「直方圖(Histogram)」,例如,來分析一下灰階圖片的灰階值分佈:
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)
plt.hist(img.ravel(), 256, [0, 256], log = True)
plt.show()
這邊使用到 matplotlib.pyplot
的 hist
,它接受一組資料,計算清單中各值出現的次數,上面的範例透過 NumPy 陣列的 ravel
方法,取得圖片攤平後的資料(只是個 NumPy 視圖),hist
的第二個參數指定要切出幾個直條,第三個參數指定要計算的值範圍,log
指定了是否 y 軸是否使用對數結果顯示。
以上的範例,依灰階值畫出來的直方圖會是:
也就是能用來表示具有某灰階值的像素個數有幾個。
OpenCV 本身也有計算直方圖資料的函式 cv2.calcHist
,而且是專門針對圖片進行計算,它的參數有:
images
:一組要分析的圖片。channels
:要分析的頻道,若是灰階圖片就指定[0]
,若是彩色圖片,可分別使用[0]
、[1]
、[2]
指定 BGR 頻道。mask
:圖片遮罩,預設為None
。histSize
:各頻道要切分出幾個直條。ranges
:要計算的像素值範圍,通常都是設為[0, 256]
。
計算出來的資料,可以直接透過 matplotlib.pyplot
的 plot
繪製折線圖,或者是透過 bar
繪製直條圖。例如:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
plt.bar(np.arange(0, 256), np.log(hist.ravel()))
plt.show()
以上的範例,依灰階值畫出來的直條圖會是:
因為這邊要處理的是灰階圖,接下來使用 matplotlib.pyplot
的 hist
就可以了。
回到方才的椒鹽雜訊,如果只看撒下椒鹽的點,由於那些點要嘛設定為黑,要嘛設定為白,畫出來的直方圖就會只在 0 與 255 處有直條。
這就好比如果將椒鹽雜訊套用在一張圖的每個像素點,也就是每個像素點要嘛設定為黑,要嘛設定為白,由於每個像素點就只有 0 或 255 的值:
import cv2
import numpy as np
import matplotlib.pyplot as plt
width = 250
height = 250
img = np.random.choice([0, 255], size = width * height).reshape(height, width).astype(np.uint8)
cv2.imshow('Salt & Pepper Noise', img)
plt.hist(img.ravel(), 256, [0, 256])
plt.show()
依灰階值畫出來的直方圖就會是:
因為原圖像的像素灰階值,多半不會極為接近 0 或 255,這種雜訊會直接令雜訊點與周圍的像素值的變化極大,也就是在圖中會很突兀,就像訊號中的突波,例如正常的音樂中突然爆音,環境中燈光突然閃爍之類的,因而又稱為脈衝雜訊(Impluse noise),就圖像而言,人眼很容易會察覺到雜訊的存在。
在後續的文件中,將進一步探討白雜訊與高斯雜訊…