線性濾波可以說是圖像處理最基本的方法,它可以允許我們對圖像進行處理,產生很多不同的效果。做法很簡單。首先,我們有一個二維的濾波器矩陣(有個高大上的名字叫卷積核)和一個要處理的二維圖像。然后,對於圖像的每一個像素點,計算它的鄰域像素和濾波器矩陣的對應元素的乘積,然后加起來,作為該像素位置的值。這樣就完成了濾波過程。
對圖像和濾波矩陣進行逐個元素相乘再求和的操作就相當於將一個二維的函數移動到另一個二維函數的所有位置,這個操作就叫卷積或者協相關。卷積和協相關的差別是,卷積需要先對濾波矩陣進行180的翻轉,但如果矩陣是對稱的,那么兩者就沒有什么差別了。
Correlation 和 Convolution可以說是圖像處理最基本的操作,但卻非常有用。這兩個操作有兩個非常關鍵的特點:它們是線性的,而且具有平移不變性shift-invariant。平移不變性指我們在圖像的每個位置都執行相同的操作。線性指這個操作是線性的,也就是我們用每個像素的鄰域的線性組合來代替這個像素。這兩個屬性使得這個操作非常簡單,因為線性操作是最簡單的,然后在所有地方都做同樣的操作就更簡單了。
實際上,在信號處理領域,卷積有廣泛的意義,而且有其嚴格的數學定義,但在這里不關注這個。
2D卷積需要4個嵌套循環4-double loop,所以它並不快,除非我們使用很小的卷積核。這里一般使用3x3或者5x5。而且,對於濾波器,也有一定的規則要求:
1)濾波器的大小應該是奇數,這樣它才有一個中心,例如3x3,5x5或者7x7。有中心了,也有了半徑的稱呼,例如5x5大小的核的半徑就是2。
2)濾波器矩陣所有的元素之和應該要等於1,這是為了保證濾波前后圖像的亮度保持不變。當然了,這不是硬性要求了。
3)如果濾波器矩陣所有元素之和大於1,那么濾波后的圖像就會比原圖像更亮,反之,如果小於1,那么得到的圖像就會變暗。如果和為0,圖像不會變黑,但也會非常暗。
4)對於濾波后的結構,可能會出現負數或者大於255的數值。對這種情況,我們將他們直接截斷到0和255之間即可。對於負數,也可以取絕對值。
常見的卷積核:
soble_x = np.array(([-1, 0, 1], [-2, 0, 2], [-1, 0, 1])) soble_y = np.array(([-1, -2, -1], [0, 0, 0], [1, 2, 1])) soble = np.array(([-1, -1, 0], [-1, 0, 1], [0, 1, 1])) prewitt_x = np.array(([-1, 0, 1], [-1, 0, 1], [-1, 0, 1])) prewitt_y = np.array(([-1, -1,-1], [0, 0, 0], [1, 1, 1])) prewitt = np.array(([-2, -1, 0], [-1, 0, 1], [0, 1, 2])) laplacian = np.array(([0, -1, 0], [-1, 4, -1], [0, -1, 0])) laplacian2 = np.array(([-1, -1, -1], [-1, 8, -1], [-1, -1, -1]))
不同的卷積核對圖像進行濾波得到的效果是不同的,我們可以根據濾波器的特點分析出濾波器的功能,下面我們使用python代碼對卷積操作進行實踐:
import numpy as np from PIL import Image import matplotlib.pyplot as plt import matplotlib as mpl def convolve(image, filt): height, width = image.shape h, w = filt.shape height_new = height - h + 1 width_new = width - w + 1 image_new = np.zeros((height_new, width_new), dtype=np.float) for i in range(height_new): for j in range(width_new): image_new[i,j] = np.sum(image[i:i+h, j:j+w] * filt) image_new = image_new.clip(0, 255) image_new = np.rint(image_new).astype('uint8') return image_new if __name__ == "__main__": mpl.rcParams['font.sans-serif'] = ['SimHei'] mpl.rcParams['axes.unicode_minus'] = False path = './simplepython/convolve/lena.png' A = Image.open(path, 'r') a = np.array(A) soble_x = np.array(([-1, 0, 1], [-2, 0, 2], [-1, 0, 1])) soble_y = np.array(([-1, -2, -1], [0, 0, 0], [1, 2, 1])) soble = np.array(([-1, -1, 0], [-1, 0, 1], [0, 1, 1])) prewitt_x = np.array(([-1, 0, 1], [-1, 0, 1], [-1, 0, 1])) prewitt_y = np.array(([-1, -1,-1], [0, 0, 0], [1, 1, 1])) prewitt = np.array(([-2, -1, 0], [-1, 0, 1], [0, 1, 2])) laplacian = np.array(([0, -1, 0], [-1, 4, -1], [0, -1, 0])) laplacian2 = np.array(([-1, -1, -1], [-1, 8, -1], [-1, -1, -1])) weight_list = ('soble_x', 'soble_y', 'soble', 'prewitt_x', 'prewitt_y', 'prewitt', 'laplacian', 'laplacian2') plt.figure(figsize=(10,4)) i = 1 for weight in weight_list: R = convolve(a[:, :, 0], eval(weight)) G = convolve(a[:, :, 1], eval(weight)) B = convolve(a[:, :, 2], eval(weight)) I = 255 - np.stack((R, G, B), 2) plt.subplot(2, 4, i) i += 1 plt.title("濾波器: %s"%(weight)) plt.axis('off') plt.imshow(I) plt.tight_layout(2) plt.subplots_adjust(top=0.92) plt.suptitle('不同的圖像卷積操作') plt.show()
上述代碼中,image_new.clip(0, 255)函數的作用是將image_new中的值進行截斷,小於等於0的置為0,大於等於255的置為255。np.rint(image_new).astype('uint8')的含義是將得到的圖像矩陣轉換為int型,在轉換為uint8類型。eval(weight)函數的作用是將字符串值轉換為對應的變量值。我們對lena圖像進行操作,下面是得到的結果: