圖片雜訊處理(一)


有時候會想為圖片加上雜訊,例如增加圖片的粗糙感,以製造相片的懷舊風格,或者是後製出物體表面的磨砂材質感覺,一些影像處理軟體,就會提供增加雜訊之類的濾鏡。

雜訊的製造方式很多,認識一下雜訊的原理,會有助於選擇合適的雜訊處理方式,而在需要去除雜訊的場合,也可以觀察雜訊是否為某種形成方式,對於雜訊去除的效率也會有幫助。

以灰階圖片為例,如果要在圖片增加雜訊,方式之一是圖片撒上一些黑點與白點,例如:

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.pyplothist,它接受一組資料,計算清單中各值出現的次數,上面的範例透過 NumPy 陣列的 ravel 方法,取得圖片攤平後的資料(只是個 NumPy 視圖),hist 的第二個參數指定要切出幾個直條,第三個參數指定要計算的值範圍,log 指定了是否 y 軸是否使用對數結果顯示。

以上的範例,依灰階值畫出來的直方圖會是:

雜訊處理(一)

也就是能用來表示具有某灰階值的像素個數有幾個。

OpenCV 本身也有計算直方圖資料的函式 cv2.calcHist,而且是專門針對圖片進行計算,它的參數有:

  • images:一組要分析的圖片。
  • channels:要分析的頻道,若是灰階圖片就指定 [0],若是彩色圖片,可分別使用 [0][1][2] 指定 BGR 頻道。
  • mask:圖片遮罩,預設為 None
  • histSize:各頻道要切分出幾個直條。
  • ranges:要計算的像素值範圍,通常都是設為 [0, 256]

計算出來的資料,可以直接透過 matplotlib.pyplotplot 繪製折線圖,或者是透過 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.pyplothist 就可以了。

回到方才的椒鹽雜訊,如果只看撒下椒鹽的點,由於那些點要嘛設定為黑,要嘛設定為白,畫出來的直方圖就會只在 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),就圖像而言,人眼很容易會察覺到雜訊的存在。

在後續的文件中,將進一步探討白雜訊與高斯雜訊…