Programming Assignment 3—卷積( Convolution)
Author:Tian YJ
編寫卷積函數function Convolve(I, F, iw, ih, fw, fh),以備後面練習使用:
a. I是一幅灰度圖像,其分辨率是iw× ih.
b. F 是一個濾波器(由浮點數構成的二維陣列),其大小是 fw× fh,通常 ( fh, fw ) << ( ih, iw).
c. 輸出 O(x, y) 是一幅與輸入圖像I大小相同的圖像,O的每一個像素點的值計算如下:將濾波器F的右上角與I(x,y)相重合,然後將I和 F重疊的所有像素對應相乘(如果I沒有與F對應的像素,則該值爲0),然後求和.
d. 採用如下兩種濾波器來測試你的代碼.
**注意:**這裏涉及圖像邊緣像素的處理問題,當對圖像四個邊的像素進行處理時,必然會出現沒有圖像的像素與濾波器模板值對應的情況發生,本作業採用三種處理方法:第一種置0,這種方法的缺點是四個邊會有失真;第二種處理方法是將圖像沿邊沿作鏡像對稱;第三種方法是調整濾波器模板的值,具體方法是:將圖像外部濾波器的權重設置爲零,同時,爲了保留圖像的能量,重新按比例調整圖像內部的濾波器的權重,使其總和爲1,如下圖所示。
**提交報告內容:**包括實現原理,程序輸入與輸出圖像對比,結果分析,及代碼。編程語言不限。
實現原理
雖然卷積層得名於卷積(convolution)運算,但我們通常在卷積層中使用更加直觀的互相關運算。在二維卷積層中,一個二維輸入數組和一個二維核(kernel)數組通過互相關運算輸出一個二維數組。
我們用一個具體例子來解釋二維互相關運算的含義。如下圖所示,輸入是一個高和寬均爲3的二維數組。我們將該數組的形狀記爲或(3,3)。核數組的高和寬分別爲2。該數組在卷積計算中又稱卷積核或過濾器(filter)。卷積核窗口(又稱卷積窗口)的形狀取決於卷積核的高和寬,即。圖中的陰影部分爲第一個輸出元素及其計算所使用的輸入和核數組元素:。
在二維互相關運算中,卷積窗口從輸入數組的最左上方開始,按從左往右、從上往下的順序,依次在輸入數組上滑動。當卷積窗口滑動到某一位置時,窗口中的輸入子數組與核數組按元素相乘並求和,得到輸出數組中相應位置的元素。圖中的輸出數組高和寬分別爲2,其中的4個元素由二維互相關運算得出:
填充(padding)是指在輸入高和寬的兩側填充元素(通常是0元素)。下圖裏我們在原輸入高和寬的兩側分別添加了值爲0的元素,使得輸入高和寬從3變成了5,並導致輸出高和寬由2增加到4。圖中的陰影部分爲第一個輸出元素及其計算所使用的輸入和核數組元素:。
也就是說,輸出的高和寬會分別增加和。
代碼實現
import cv2 # 我只用它來做圖像讀寫和繪圖,沒調用它的其它函數哦
import numpy as np # 進行數值計算
導入所需要的庫文件
def Convolve(I, F, iw, ih, fw, fh):
'''
I:輸入的灰度圖
F:卷積核即濾波器
iw:輸入圖片寬度
ih:輸入圖片高度
fw:卷積核寬度
fh:卷積核高度
'''
Y = np.zeros((ih-fh+1, iw-fw+1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (I[i : i+fh, j : j+fw] * F).sum()
Y = Y.astype(np.uint8)
return Y
定義的卷積函數
##### 簡單測試
I = np.array([[4, 4, 4], [4, 4, 4], [4, 4, 4]])
F = np.array([[1/4, 1/4], [1/4, 1/4]])
ih, iw = I.shape
fh, fw = F.shape
print('原始矩陣:\n', I)
print('卷積核:\n', F)
print('卷積結果:\n', Convolve(I, F, iw, ih, fw, fh))
結果如下:(是正確的)
邊緣處理
# (1)置零
def padding(img):
I = np.zeros((img.shape[0] + 2, img.shape[1] + 2)).astype(np.float32)
for i in range(img.shape[0]):
for j in range(img.shape[1]):
I[i + 1, j + 1] = img[i, j]
return I
img = np.ones((4,4))
print(padding(img))
結果如圖:
# (2)沿邊作鏡像對稱
def mirror(img):
I = np.zeros((img.shape[0] + 2, img.shape[1] + 2)).astype(np.float32)
for i in range(img.shape[0]):
for j in range(img.shape[1]):
I[i + 1, j + 1] = img[i, j]
# 第一步,上下邊框對稱取值
I[0, :] = I[2, :]
I[-1, :] = I[-3, :]
# 第二步,左右邊框對稱取值
I[:, 0] = I[:, 2]
I[:, -1] = I[:, -3]
# 第三步,四個頂點對稱取值
I[0, 0] = I[0, 2]
I[-1, 0] = I[-1, 2]
I[0, -1] = I[0, -3]
I[-1, -1] = I[-1, -3]
return I
img = np.array([[213, 166, 237, 240, 196, 243], [166, 81, 213, 181, 34, 197],
[237, 217, 247, 240, 196, 243], [245, 200, 241, 241, 199, 240],
[200, 38, 190, 189, 35, 197], [241, 185, 237, 240, 189, 241]])
print(mirror(img))
結果如圖:
def Fliter_new(F, row, col):
# F是卷積核即濾波器
# row爲零行
# col爲零列
if row!=None and col!=None:
index = (F == 0)
F[index] = 999
F[row, :] = 0
F[:, col] = 0
index = (F == 999)
F[index] = 0
Sum = F.sum()
for i in range(0, F.shape[0]):
for j in range(0, F.shape[1]):
if F[i, j] != 0:
F[i, j] = F[i, j] / Sum
elif col==None:
index = (F == 0)
F[index] = 999
F[row, :] = 0
index = (F == 999)
F[index] = 0
Sum = F.sum()
for i in range(0, F.shape[0]):
for j in range(0, F.shape[1]):
if F[i, j] != 0:
F[i, j] = F[i, j] / Sum
elif row==None:
index = (F == 0)
F[index] = 999
F[:, col] = 0
index = (F == 999)
F[index] = 0
Sum = F.sum()
for i in range(0, F.shape[0]):
for j in range(0, F.shape[1]):
if F[i, j] != 0:
F[i, j] = F[i, j] / Sum
return F
## (3)調整濾波器模板的值
######這裏需要修改卷積函數
def Convolve_weight(img, F, iw, ih, fw, fh):
# 第一步,先在邊緣填充零
# 調用之前寫好padding零的函數
I = padding(img)
# 第二步,進行卷積
Y = Convolve(I, F, iw, ih, fw, fh)
# 第三步,邊緣值修改
### 四個端點
Y[0,0] = (I[0 : 0+fh, 0 : 0+fw] * Fliter_new(F, 0, 0)).sum()
Y[-1,0] = (I[I.shape[0]-fh :, 0 : fw] * Fliter_new(F, -1, 0)).sum()
Y[0,-1] = (I[0 : 0+fh, I.shape[1]-fw :] * Fliter_new(F, 0, -1)).sum()
Y[-1,-1] = (I[I.shape[0]-fh :, I.shape[1]-fw :] * Fliter_new(F, -1, -1)).sum()
### 四個邊緣
for i in range(1,Y.shape[1]-1):
Y[0,i] = (I[0 : 0+fh, i : i+fw] * Fliter_new(F, 0, None)).sum()
Y[-1,i] = (I[I.shape[0]-fh :, i : i+fw] * Fliter_new(F, -1, None)).sum()
for j in range(1,Y.shape[0]-1):
Y[j,0] = (I[j : j+fh, 0 : 0+fw] * Fliter_new(F, None, 0)).sum()
Y[j,-1] = (I[j : j+fh, I.shape[1]-fw :] * Fliter_new(F, None, -1)).sum()
return Y
這是第三種方法
# 主函數
if __name__ == '__main__':
# 設置路徑
path_work = 'C:/Users/86187/Desktop/'
img_name = 'flowergray'
file_in = path_work + img_name + '.jpg'
file_out = path_work + img_name + '_Conv' + '.jpg'
# 讀取圖片
img = cv2.imread(file_in, 0)
# 定義卷積核
F = np.ones((2,2))*(1/4)
# 獲取圖片尺寸
iw, ih = img.shape
fw, fh = F.shape
#調用函數
I = padding(img)
out = Convolve(I, F, iw, ih, fw, fh)
# 圖片展示
cv2.imshow("result",out)
# 固定繪圖窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存結果
cv2.imwrite(file_out, out)
- 輸出結果1
置零 | 鏡像對稱 | 調整濾波器模板 |
---|---|---|
最下圖是原圖圖像,可以明顯看出,經過卷積後,圖像變模糊了。
濾波器爲:
- 輸出結果2
置零 | 鏡像對稱 | 調整濾波器模板 |
---|---|---|
濾波器爲:
分析:上述老師給的兩種濾波器都是取(四)九個值的平均值代替中間像素值,所以起到的平滑的效果。平滑,即試圖向邊模糊,有減少噪聲的功用。至於邊界處理,三種方法得出的效果大同小異,補零的方法會出現一點點邊界失真。