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个元素由二维互相关运算得出:
(1)填充零
填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。下图里我们在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4。图中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:。
一般来说,如果在高的两侧一共填充行,在宽的两侧一共填充列,那么输出形状将会是
也就是说,输出的高和宽会分别增加和。
(2)沿边缘作镜像对称
第一步:上下边框填充
第二步:左右边框填充
第三步:四个顶点填充
(3)调整滤波器模板的值
代码实现
import cv2 # 我只用它来做图像读写和绘图,没调用它的其它函数哦
import numpy as np # 进行数值计算
导入所需要的库文件
def Convolve(I, F, iw, ih, fw, fh):
'''
I:输入的灰度图
F:卷积核即滤波器
iw:输入图片宽度
ih:输入图片高度
fw:卷积核宽度
fh:卷积核高度
padding:边界填充(这里padding默认为一层)
'''
ih = I.shape[0]
iw = I.shape[1]
Y = np.zeros((ih-fh+1, iw-fw+1))
print(Y.shape)
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('原始矩阵:
', I)
print('卷积核:
', F)
print('卷积结果:
', 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]
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:
F[row, :] = 0
F[:, col] = 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:
F[row, :] = 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:
F[:, col] = 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()
F = np.ones((2,2))*1/4
Y[-1,0] = (I[-fh :, 0 : fw] * Fliter_new(F, -1, 0)).sum()
F = np.ones((2,2))*1/4
Y[0,-1] = (I[0 : 0+fh, -fw :] * Fliter_new(F, 0, -1)).sum()
F = np.ones((2,2))*1/4
Y[-1,-1] = (I[-fh :, -fw :] * Fliter_new(F, -1, -1)).sum()
### 四个边缘
for i in range(1,Y.shape[1]-1):
F = np.ones((2,2))*1/4
Y[0,i] = (I[0 : fh, i : i+fw] * Fliter_new(F, 0, None)).sum()
F = np.ones((2,2))*1/4
Y[-1,i] = (I[-fh :, i : i+fw] * Fliter_new(F, -1, None)).sum()
for j in range(1,Y.shape[0]-1):
F = np.ones((2,2))*1/4
Y[j,0] = (I[j : j+fh, 0 : fw] * Fliter_new(F, None, 0)).sum()
F = np.ones((2,2))*1/4
Y[j,-1] = (I[j : j+fh, -fw :] * Fliter_new(F, None, -1)).sum()
return Y
#img = np.array([[4,8,16,32],
# [8,4,16,32],
# [16,4,8,32],
# [32,8,4,16]])
#ih, iw = img.shape
#F = np.ones((2,2))*1/4
#fh, fw = F.shape
#out = Convolve_weight(img, F, iw, ih, fw, fh)
#print(out)
对比手动计算值,结果正确。
# 主函数
if __name__ == '__main__':
# 设置路径
path_work = 'C:/Users/86187/Desktop/image/'
img_name = 'flowergray'
file_in = path_work + img_name + '.jpg'
file_out = path_work + img_name + '_z' + '.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_weight(img, F, iw, ih, fw, fh)
# 图片展示
cv2.imshow("result",out)
# 固定绘图窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite(file_out, out)
- 输出结果1
置零 | 镜像对称 | 调整滤波器模板 |
---|---|---|
最下图是原图图像,可以明显看出,经过卷积后,图像变模糊了。
滤波器为:
- 输出结果2
置零 | 镜像对称 | 调整滤波器模板 |
---|---|---|
滤波器为:
分析:上述老师给的两种滤波器都是取(四)九个值的平均值代替中间像素值,所以起到的平滑的效果。平滑,即试图向边模糊,有减少噪声的功用。至于边界处理,三种方法得出的效果大同小异,补零的方法会出现一点点边界失真。