滤波与卷积
一、滤波与卷积的区别
图像处理中滤波和卷积原理上相似,但是在实现的细节上存在一些区别。
滤波操作就是图像对应像素与掩膜(mask)的对应元素相乘相加。而卷积操作是图像对应像素与旋转180度的卷积核对应元素相乘相加。
下面是一个卷积示意图(卷积核已经旋转180°)
二、卷积
卷积操作也是卷积核与图像对应位置的乘积和。但是卷积操作在做乘积之前,需要先
将卷积核翻转180度,之后再做乘积。其数学定义为:
一般称g为作用在f上的filter或kernel。f是图像,g叫滤波器或卷积核
三、卷积核的要求
对于滤波器,也有一定的规则要求:
1)滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有
了半径的称呼,例如5x5大小的核的半径就是2。
2)滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。但
这不是硬性要求。
3)如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小
于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
4)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截 断到0和255之间即可。对于负数,也可以取绝对值。
四、卷积的意义
在具体应用中,往往有多个卷积核,可以认为,每个卷积核代表了一种图像模式。
如果某个图像块与此卷积核卷积出的值大,则认为此图像块十分接近于此卷积核。
例如,如果我们设计了6个卷积核,可以理解:我们认为这个图像上有6种底层纹理模式,
也就是我们用6种基础模式就能描绘出一副图像。
例如:
用Gx来卷积下面这张图的话,就会在中间黑白边界获得比较大的值。
五、常用的卷积核
1、一个没有任何效果的卷积核:
将原像素中间像素值乘1,其余全部乘0。
显然像素值不会发生任何变化。
2、平滑均值滤波
取九个值的平均值代替中间像素值,用于过滤高频噪声点
— 起到平滑的效果。
3、高斯平滑
高斯平滑水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相比于均值滤波而言,有着更好的平滑效果。
4、图像锐化
图像锐化使用的是拉普拉斯变换核函数:
5、Soble边缘检测
Soble更强调了和边缘相邻的像素点对边缘的影响。
六、卷积解决的问题
卷积负责提取图像中的局部特征
七、滤波or卷积的填充
如果用 (f, f) 的过滤器来卷积一张 (h, w) 大小的图片,每次移动一个像素的话,那么得出的结果就是 (h-f+1, w-f+1) 的输出结果。
f是过滤器大小,
h和w分别是图片的高宽。
如果每次不止移动一个像素,而是s个像素,那么结果就会变为:
其中这个s就叫做步长。此时的卷积存在的问题:
• 只要是f或s的值比1要大的话,那么每次卷积之后结果的长宽,要比卷积前小一些。
• 丢失信息(图片边缘的像素卷积卷不到)
解决问题的方法–在卷积之前在原图周围进行补0填充
1、Same填充
在没有填充时,卷积输出的图像大小为:
假设,我们在原图像四周填充了宽度为P的0(也可以根据需要填充其他值)进行填充,这样原图像的大小就从 (h,w) 变成 (h+2p,w+2p), 有了填充之后,每次卷积之后的图像大小::
此时我们让输图像的宽高等于输入图像的宽高,即可求出p,以高为例:
即计算出了四周填充宽度p。
特别的 当步长 s=1:
当p是分数怎么办?
一般的处理是,只取整数部分(向上取整)
假设 p求出的是高度h上需要填充的总宽度,
p = s(h-1)-h+f
则图像高方向的上半部分需要填充的宽度为
p_top =int(p/2)
p_bottom = p-p_top
图片宽度方向处理方法相同。
以上的填充过程就是same填充
2、Valid(有效)填充
不进行任何填充,直接进行卷积,即p=0,叫做"Valid(有效)填充"
由此可见 卷积时,padding设置为valid会使得图片变小,设置为same不会改变图片大小
vaild输出图片大小:
same输出图片大小:
八、多通道卷积(重要)
理解图:
对多通道图像进行卷积需要使用与图像图像相同的卷积核进行卷积,卷积核的每个通道对应着图像中相应的通道图像,一对一卷积,最后将每个通道的卷积后图像对应位置的卷积值相加,得到一个1通道的特征图。
多通道卷积结果的通道数==卷积核数
'''
滤波与卷积
'''
import cv2
import numpy as np
import matplotlib.pyplot as plt
def Padding(image,kernels_size,stride = [1,1],padding = "same"):
'''
对图像进行padding
:param image: 要padding的图像矩阵
:param kernels_size: list 卷积核大小[h,w]
:param stride: 卷积步长 [左右步长,上下步长]
:param padding: padding方式
:return: padding后的图像
'''
if padding == "same":
h,w = image.shape
p_h =max((stride[0]*(h-1)-h+kernels_size[0]),0) # 高度方向要补的0
p_w =max((stride[1]*(w-1)-w+kernels_size[1]),0) # 宽度方向要补的0
p_h_top = p_h//2 # 上边要补的0
p_h_bottom = p_h-p_h_top # 下边要补的0
p_w_left = p_w//2 # 左边要补的0
p_w_right = p_w-p_w_left # 右边要补的0
# print(p_h_top,p_h_bottom,p_w_left,p_w_right) # 输出padding方式
padding_image = np.zeros((h+p_h, w+p_w), dtype=np.uint8)
for i in range(h):
for j in range(w):
padding_image[i+p_h_top][j+p_w_left] = image[i][j] # 将原来的图像放入新图中做padding
return padding_image
else:
return image
def filtering_and_convolution(image,kernels,stride,padding = "same"):
'''
:param image: 要卷积的图像
:param kernels: 卷积核 列表
:param stride: 卷积步长 [左右步长,上下步长]
:param padding: padding方式 “same”or“valid”
:return:
'''
image_h,image_w = image.shape
kernels_h,kernels_w = np.array(kernels).shape
# 获取卷积核的中心点
kernels_h_core = int(kernels_h/2+0.5)-1
kernels_w_core = int(kernels_w/2+0.5)-1
if padding == "valid":
# 计算卷积后的图像大小
h = int((image_h-kernels_h)/stride[0]+1)
w = int((image_w-kernels_w)/stride[1]+1)
# 生成卷积后的图像
conv_image = np.zeros((h,w),dtype=np.uint8)
# 计算遍历起始点
h1_start = kernels_h//2
w1_start = kernels_w//2
ii=-1
for i in range(h1_start,image_h - h1_start,stride[0]):
ii += 1
jj = 0
for j in range(w1_start,image_w - w1_start,stride[1]):
sum = 0
for x in range(kernels_h):
for y in range(kernels_w):
# print(i,j,int((i/image_h)*h),int((j/image_w)*w), i-kernels_h_core + x, j-kernels_w_core+y,x,y)
sum += int(image[i-kernels_h_core+x][j-kernels_w_core+y]*kernels[x][y])
conv_image[ii][jj] = sum
jj += 1
return conv_image
if padding == "same":
# 对原图进行padding
kernels_size = [kernels_h, kernels_w]
pad_image = Padding(image,kernels_size,stride,padding="same")
# 计算卷积后的图像大小
h = image_h
w = image_w
# 生成卷积后的图像
conv_image = np.zeros((h,w),dtype=np.uint8)
# # 计算遍历起始点
h1_start = kernels_h//2
w1_start = kernels_w//2
ii=-1
for i in range(h1_start,image_h - h1_start,stride[0]):
ii +=1
jj = 0
for j in range(w1_start,image_w - w1_start,stride[1]):
sum = 0
for x in range(kernels_h):
for y in range(kernels_w):
sum += int(image[i-kernels_h_core+x][j-kernels_w_core+y]*kernels[x][y])
conv_image[ii][jj] = sum
jj += 1
return conv_image
def sobel_filter(image):
h = image.shape[0]
w = image.shape[1]
image_new = np.zeros(image.shape, np.uint8)
for i in range(1, h - 1):
for j in range(1, w - 1):
sx = (image[i + 1][j - 1] + 2 * image[i + 1][j] + image[i + 1][j + 1]) - \
(image[i - 1][j - 1] + 2 * image[i - 1][j] + image[i - 1][j + 1])
sy = (image[i - 1][j + 1] + 2 * image[i][j + 1] + image[i + 1][j + 1]) - \
(image[i - 1][j - 1] + 2 * image[i][j - 1] + image[i + 1][j - 1])
image_new[i][j] = np.sqrt(np.square(sx) + np.square(sy))
# image_new[i][j] = sy
return image_new
# 设置matplotlib正常显示中文和负号
plt.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False # 正常显示负号
img = cv2.imread('lenna.png',1)
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
plt.subplot(331)
plt.imshow(img_gray,cmap="gray")
plt.title("原图")
sobel_Gy = [[-1,0,1],[-2,0,2],[-1,0,1]]
Average = [[1/9,1/9,1/9],[1/9,1/9,1/9],[1/9,1/9,1/9]]
Gaussian = [[1/16,2/16,1/16],[2/16,4/16,2/16],[1/16,2/16,1/16]]
Laplace = [[-1,-1,-1],[-1,9,-1],[-1,-1,-1]]
stride=[1,1]
img_sobel_Gy = filtering_and_convolution(img_gray,sobel_Gy,stride,padding="same")
img_Average = filtering_and_convolution(img_gray,Average,stride,padding="same")
img_Gaussian = filtering_and_convolution(img_gray,Gaussian,stride,padding="same")
img_Laplace = filtering_and_convolution(img_gray,Laplace,stride,padding="same")
plt.subplot(332)
plt.imshow(img_sobel_Gy,cmap = "gray")
plt.title("sobel_Gy")
plt.subplot(333)
plt.imshow(img_Average,cmap = "gray")
plt.title("Average")
plt.subplot(334)
plt.imshow(img_Gaussian,cmap = "gray")
plt.title("Gaussian")
plt.subplot(335)
plt.imshow(img_Laplace,cmap = "gray")
plt.title("Laplace")
plt.show()