本文的目的:说明在卷积操作中常用的卷积核及其作用原理,并基于Pytorch框架通过实例使用这些卷积核。
看本文前要掌握的基础知识:需要了解卷积神经元网络CNN中卷积的运算原理,CSDN上此类文章很多,不再赘述。推荐一篇:RGB彩色图像的卷积过程(gif动图演示)
卷积核(kernel)是卷积神经元网络CNN中最重要的权重参数,在卷积神经元网络学习过程中,主要也是为了学习到合适的卷积核。下面将通过对图像的处理方式将常用的卷积核分为3类。
这类卷积核是研究最多的,卷积核多种多样。这类卷积核的共同特征是:卷积核内所有的值求和为0
,这是因为边缘的区域,图像的像素值会发生突变,与这样的卷积核做卷积会得到一个不为0的值。而非边缘的区域,像素值很接近,与这样的卷积核做卷积会得到一个约等于0的值。
常用的边缘检测卷积核有:
①Robert算子
{ − 1 0 0 1 } \left\{ \begin{matrix} -1 &0 \\ 0 & 1 \\ \end{matrix} \right\} {−1001}
或
{ 0 − 1 1 0 } \left\{ \begin{matrix} 0& -1 \\ 1 &0 \\ \end{matrix} \right\} {01−10}
②Prewitt算子
{ − 1 − 1 − 1 0 0 0 1 1 1 } \left\{ \begin{matrix} -1&-1 & -1 \\ 0 & 0&0\\ 1 & 1&1 \\ \end{matrix} \right\} ⎩ ⎨ ⎧−101−101−101⎭ ⎬ ⎫
或
{ − 1 0 1 − 1 0 1 − 1 0 1 } \left\{ \begin{matrix} -1&0 & 1 \\ -1 & 0&1\\ -1 & 0&1 \\ \end{matrix} \right\} ⎩ ⎨ ⎧−1−1−1000111⎭ ⎬ ⎫
③Sobel算子
{ − 1 − 2 − 1 0 0 0 1 2 1 } \left\{ \begin{matrix} -1&-2 & -1 \\ 0 & 0&0\\ 1 &2&1 \\ \end{matrix} \right\} ⎩ ⎨ ⎧−101−202−101⎭ ⎬ ⎫
或
{ − 1 0 1 − 2 0 2 − 1 0 1 } \left\{ \begin{matrix} -1&0 & 1 \\ -2 & 0&2\\ -1 & 0&1 \\ \end{matrix} \right\} ⎩ ⎨ ⎧−1−2−1000121⎭ ⎬ ⎫
④Laplace算子
{ 0 1 0 1 − 4 1 0 1 0 } \left\{ \begin{matrix} 0&1 &0 \\ 1 & -4&1\\ 0& 1&0\\ \end{matrix} \right\} ⎩ ⎨ ⎧0101−41010⎭ ⎬ ⎫
这类卷积核的作用原理是对一片区域内的像素值求平均值,使得像素变化更加平缓
,达到模糊化的目的,例如:
{ 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 } \left\{ \begin{matrix} 1/9&1/9 &1/9 \\ 1/9 & 1/9&1/9 \\ 1/9&1/9&1/9 \\ \end{matrix} \right\} ⎩ ⎨ ⎧1/91/91/91/91/91/91/91/91/9⎭ ⎬ ⎫
这类卷积核的作用是凸显像素值有变化的区域
,使得本来像素值梯度就比较大的区域(边缘区域)变得像素值梯度更大。在边缘检测中,卷积核的设计要求卷积核内的所有值求和为0,这里的要求刚好相反,要求卷积核内的所有值应该不为0,凸显出像素值梯度较大的区域,例如以下卷积核:
{ − 1 − 1 − 1 − 1 9 − 1 − 1 − 1 − 1 } \left\{ \begin{matrix} -1&-1 &-1 \\ -1 & 9&-1 \\ -1&-1&-1 \\ \end{matrix} \right\} ⎩ ⎨ ⎧−1−1−1−19−1−1−1−1⎭ ⎬ ⎫
只要把握以上卷积核的原理,就可以自己设计卷积核。比如模糊化卷积核,这样也是可以的
{ 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 1 / 16 } \left\{ \begin{matrix} 1/16&1/16 &1/16&1/16 \\ 1/16&1/16 &1/16&1/16 \\ 1/16&1/16 &1/16&1/16 \\ 1/16&1/16 &1/16&1/16 \\ \end{matrix} \right\} ⎩ ⎨ ⎧1/161/161/161/161/161/161/161/161/161/161/161/161/161/161/161/16⎭ ⎬ ⎫
①image转tensor:使用PIL中Image.open()打开图像,然后使用torchvision.transforms.ToTensor()转为tensor:
image = Image.open('image_path').convert('RGB') #导入图片
image_to_tensor = torchvision.transforms.ToTensor() #实例化ToTensor
original_image_tensor = image_to_tensor(image).unsqueeze(0) #把图片转换成tensor
这里使用.unsqueeze(0)升维的目的是为了后面进行卷积操作准备,因为Conv2d要求输入的tensor维度为4维,即[batch, channel, H, W]。这里对应要增加batch这个维度。
②tensor转image:使用torchvision.utils.save_image():
torchvision.utils.save_image(tensor, 'save_image_path')
因为nn.Conv2d()方法中,卷积核(权重)是默认随机的,所以需要先指定好卷积核:
#卷积核:laplace
conv_laplace = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,padding=0,bias=False)
conv_laplace.weight.data = torch.tensor([[[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]]]], dtype=torch.float32)
注意卷积核的维度,也是上面说的要有4个维度。
①原图像:
②边缘检测卷积核(prewitt横向算子)卷积后:
③边缘检测卷积核(prewitt纵向算子)卷积后:
体验一下同样是边缘检测,以上3个算子的输出差异。尤其是横向和纵向上的差别。
这里把背景的噪声点都凸显出来了。
import torch
from PIL import Image
import torchvision
image = Image.open('girl.png').convert('RGB') #导入图片
image_to_tensor = torchvision.transforms.ToTensor() #实例化ToTensor
original_image_tensor = image_to_tensor(image).unsqueeze(0) #把图片转换成tensor
#卷积核:prewitt横向
conv_prewitt_h = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,padding=0,bias=False) #bias要设定成False,要不然会随机生成bias,每次结果都不一样
conv_prewitt_h.weight.data = torch.tensor([[[[-1,-1,-1],[0,0,0],[1,1,1]],
[[-1,-1,-1],[0,0,0],[1,1,1]],
[[-1,-1,-1],[0,0,0],[1,1,1]]]], dtype=torch.float32)
#卷积核:prewitt纵向
conv_prewitt_l = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,padding=0,bias=False)
conv_prewitt_l.weight.data = torch.tensor([[[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]]]], dtype=torch.float32)
#卷积核:laplace
conv_laplace = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,padding=0,bias=False)
conv_laplace.weight.data = torch.tensor([[[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]],
[[-1,0,1],[-1,0,1],[-1,0,1]]]], dtype=torch.float32)
#卷积核:模糊化
conv_blur = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=5,padding=0,bias=False)
conv_blur.weight.data = torch.full((1,3,5,5),0.04)
#卷积核:锐利化
conv_sharp = torch.nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,padding=0,bias=False)
conv_sharp.weight.data = torch.tensor([[[[-1,-1,1],[-1,-1,-1],[-1,-1,-1]],
[[-1,-1,1],[-1,22,-1],[-1,-1,-1]],
[[-1,-1,1],[-1,-1,-1],[-1,-1,-1]]]], dtype=torch.float32)
#生成并保存图片
tensor_prewitt_h = conv_prewitt_h(original_image_tensor)
torchvision.utils.save_image(tensor_prewitt_h, 'prewitt_h.png')
tensor_prewitt_l = conv_prewitt_l(original_image_tensor)
torchvision.utils.save_image(tensor_prewitt_l, 'prewitt_l.png')
tensor_laplace = conv_laplace(original_image_tensor)
torchvision.utils.save_image(tensor_laplace, 'laplace.png')
tensor_blur = conv_blur(original_image_tensor)
torchvision.utils.save_image(tensor_blur, 'blur.png')
tensor_sharp = conv_sharp(original_image_tensor)
torchvision.utils.save_image(tensor_sharp, 'sharp.png')