5.1 卷积
卷积神经网络(Convolutional Neural Network,CNN)
-
受生物学上感受野机制的启发而提出。
-
一般是由卷积层、汇聚层和全连接层交叉堆叠而成的前馈神经网络
-
有三个结构上的特性:局部连接、权重共享、汇聚。
-
具有一定程度上的平移、缩放和旋转不变性。
-
和前馈神经网络相比,卷积神经网络的参数更少。
-
主要应用在图像和视频分析的任务上,其准确率一般也远远超出了其他的神经网络模型。
-
近年来卷积神经网络也广泛地应用到自然语言处理、推荐系统等领域。
5.1.1 二维卷积运算
5.1.2 二维卷积算子
在本书后面的实现中,算子都继承paddle.nn.Layer,并使用支持反向传播的飞桨API进行实现,这样我们就可以不用手工写backword()的代码实现。
【使用pytorch实现】代码如下:
import torch
import torch.nn as nn
import torch.nn
import numpy as np
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
w = torch.tensor(np.array([[0., 1.], [2., 3.]], dtype='float32').reshape([kernel_size, kernel_size]))
self.weight = torch.nn.Parameter(w, requires_grad=True)
def forward(self, X):
u, v = self.weight.shape
output = torch.zeros([X.shape[0], X.shape[1] - u + 1, X.shape[2] - v + 1])
for i in range(output.shape[1]):
for j in range(output.shape[2]):
output[:, i, j] = torch.sum(X[:, i:i + u, j:j + v] * self.weight, axis=[1, 2])
return output
# 随机构造一个二维输入矩阵
inputs = torch.as_tensor([[[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]])
conv2d = Conv2D(kernel_size=2)
outputs = conv2d(inputs)
print("input: {}, \noutput: {}".format(inputs, outputs))
运行结果:
5.1.3 二维卷积的参数量和计算量
随着隐藏层神经元数量的变多以及层数的加深,
使用全连接前馈网络处理图像数据时,参数量会急剧增加。
如果使用卷积进行图像处理,相较于全连接前馈网络,参数量少了非常多。
5.1.4 感受野
输出特征图上每个点的数值,是由输入图片上大小为U×V的区域的元素与卷积核每个元素相乘再相加得到的,所以输入图像上U×V区域内每个元素数值的改变,都会影响输出点的像素值。我们将这个区域叫做输出特征图上对应点的感受野。感受野内每个元素数值的变动,都会影响输出点的数值变化。比如3×3卷积对应的感受野大小就是3×3,如图。
而当通过两层3×3的卷积之后,感受野的大小将会增加到5×5,如图所示。
因此,当增加卷积网络深度的同时,感受野将会增大,输出特征图中的一个像素点将会包含更多的图像语义信息。
5.1.5 卷积的变种
在卷积的标准定义基础上,还可以引入卷积核的滑动步长和零填充来增加卷积的多样性,从而更灵活地进行特征抽取。
5.1.5.1 步长(Stride)
在卷积运算的过程中,有时会希望跳过一些位置来降低计算的开销,也可以把这一过程看作是对标准卷积运算输出的下采样。
在计算卷积时,可以在所有维度上每间隔S个元素计算一次,S称为卷积运算的步长(Stride),也就是卷积核在滑动时的间隔。
在二维卷积运算中,当步长S=2时,计算过程如图所示。
卷积核的步长度代表提取的精度,步长小,提取的特征会更全面,不会遗漏太多信息。但同时可能造成计算量增大,甚至过拟合等问题。步长大,计算量会下降,但很有可能错失一些有用的特征。步长选多少没有一套明确的标准,还是要看你输入图像的大小和卷积核的大小。总之在计算资源够用的前提下,最好不要让你卷积核错失太多特征。
5.1.5.2 零填充(Zero Padding)
在卷积运算中,还可以对输入用零进行填充使得其尺寸变大。根据卷积的定义,如果不进行填充,当卷积核尺寸大于1时,输出特征会缩减。对输入进行零填充则可以对卷积核的宽度和输出的大小进行独立的控制。
在二维卷积运算中,零填充(Zero Padding)是指在输入矩阵周围对称地补上P个0。图 为使用零填充的示例。
- 对于奇数卷积核,通过让padding=(k-1)/2,即可实现same的效果。即输出尺寸保持不变或以步长倍数缩小(对于奇数输入尺寸则向上取整)
- 对于偶数卷积核,若是偶数输入尺寸,则padding=floor((k-1)/2)可实现same的效果
- 对于偶数卷积核,若是奇数输入尺寸,则padding=ceil((k-1)/2)可实现same的效果
- 若涉及到空洞卷积,则公式变为:d*(k-1)/2
5.1.6 带步长和零填充的二维卷积算子
从输出结果看出,使用3×3大小卷积,
padding为1,
当stride=1时,模型的输出特征图与输入特征图保持一致;
当stride=2时,模型的输出特征图的宽和高都缩小一倍。
【使用pytorch实现】代码如下:
import torch
import torch.nn as nn
import torch.nn
import numpy as np
class Conv2D(nn.Module):
def __init__(self, kernel_size,stride=1, padding=0):
super(Conv2D, self).__init__()
w = torch.tensor(np.array([[0., 1., 2.], [3., 4. ,5.],[6.,7.,8.]], dtype='float32').reshape([kernel_size, kernel_size]))
self.weight = torch.nn.Parameter(w, requires_grad=True)
self.stride = stride
self.padding = padding
def forward(self, X):
# 零填充
new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])
new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
u, v = self.weight.shape
output_w = (new_X.shape[1] - u) // self.stride + 1
output_h = (new_X.shape[2] - v) // self.stride + 1
output = torch.zeros([X.shape[0], output_w, output_h])
for i in range(0, output.shape[1]):
for j in range(0, output.shape[2]):
output[:, i, j] = torch.sum(
new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * self.weight,
axis=[1, 2])
return output
inputs = torch.randn(size=[2, 8, 8])
conv2d_padding = Conv2D(kernel_size=3, padding=1)
outputs = conv2d_padding(inputs)
print("When kernel_size=3, padding=1 stride=1, input's shape: {}, output's shape: {}".format(inputs.shape, outputs.shape))
conv2d_stride = Conv2D(kernel_size=3, stride=2, padding=1)
outputs = conv2d_stride(inputs)
print("When kernel_size=3, padding=1 stride=2, input's shape: {}, output's shape: {}".format(inputs.shape, outputs.shape))
运行结果:
从输出结果看出,使用3×3大小卷积:
padding为1,当stride=1时,模型的输出特征图与输入特征图保持一致。
padding为1,当stride=2时,模型的输出特征图的宽和高都缩小了一倍。
5.1.7 使用卷积运算完成图像边缘检测任务
在图像处理任务中,常用拉普拉斯算子对物体边缘进行提取,拉普拉斯算子为一个大小为3×3的卷积核,中心元素值是8,其余元素值是−1。
考虑到边缘其实就是图像上像素值变化很大的点的集合,因此可以通过计算二阶微分得到,当二阶微分为0时,像素值的变化最大。此时,对x方向和y方向分别求取二阶导数:
完整的二阶微分公式为:
上述公式也被称为拉普拉斯算子,对应的二阶微分卷积核为:
对上述算子全部求反也可以起到相同的作用,此时,该算子可以表示为:
也就是一个点的四邻域拉普拉斯的算子计算结果是自己像素值的四倍减去上下左右的像素的和,将这个算子旋转45°后与原算子相加,就变成八邻域的拉普拉斯算子,也就是一个像素自己值的八倍减去周围一圈八个像素值的和,做为拉普拉斯计算结果,此时,该算子可以表示为:
下面我们利用上面定义的Conv2D算子,构造一个简单的拉普拉斯算子,并对一张输入的灰度图片进行边缘检测,提取出目标的外形轮廓。
代码如下:
import torch
import torch.nn as nn
import torch.nn
import numpy as np
class Conv2D(nn.Module):
def __init__(self, kernel_size,stride=1, padding=0):
super(Conv2D, self).__init__()
# 设置卷积核参数
w = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], dtype='float32').reshape((3,3))
w=torch.from_numpy(w)
self.weight = torch.nn.Parameter(w, requires_grad=True)
self.stride = stride
self.padding = padding
def forward(self, X):
# 零填充
new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])
new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
u, v = self.weight.shape
output_w = (new_X.shape[1] - u) // self.stride + 1
output_h = (new_X.shape[2] - v) // self.stride + 1
output = torch.zeros([X.shape[0], output_w, output_h])
for i in range(0, output.shape[1]):
for j in range(0, output.shape[2]):
output[:, i, j] = torch.sum(
new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * self.weight,
axis=[1, 2])
return output
import matplotlib.pyplot as plt
from PIL import Image
# 读取图片
img = Image.open('cat.png').convert('L')
img = np.array(img, dtype='float32')
im = torch.from_numpy(img.reshape((img.shape[0],img.shape[1])))
# 创建卷积算子,卷积核大小为3x3,并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(kernel_size=3, stride=1, padding=0)
# 将读入的图片转化为float32类型的numpy.ndarray
inputs = np.array(im).astype('float32')
print("bf as_tensor, inputs:",inputs)
# 将图片转为Tensor
inputs = torch.as_tensor(inputs)
print("bf unsqueeze, inputs:",inputs)
inputs = torch.unsqueeze(inputs, axis=0)
print("af unsqueeze, inputs:",inputs)
outputs = conv(inputs)
print(outputs)
# outputs = outputs.data.squeeze().numpy()
# # 可视化结果
plt.subplot(121).set_title('input image', fontsize=15)
plt.imshow(img.astype('uint8'),cmap='gray')
plt.subplot(122).set_title('output feature map', fontsize=15)
plt.imshow(outputs.squeeze().detach().numpy(), cmap='gray')
plt.savefig('conv-vis.pdf')
plt.show()
运行结果:
选做题
Pytorch实现1、2;阅读3、4、5写体会。
1.实现一些传统边缘检测算子,如:Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Laplacian。
代码如下:
import cv2
import numpy as np
# 加载图像
image = cv2.imread('cat.png', 0)
image = cv2.resize(image, (800, 800))
# 自定义卷积核
# Roberts边缘算子
kernel_Roberts_x = np.array([
[1, 0],
[0, -1]
])
kernel_Roberts_y = np.array([
[0, -1],
[1, 0]
])
# Sobel边缘算子
kernel_Sobel_x = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
kernel_Sobel_y = np.array([
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])
# Prewitt边缘算子
kernel_Prewitt_x = np.array([
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]])
kernel_Prewitt_y = np.array([
[1, 1, 1],
[0, 0, 0],
[-1, -1, -1]])
# Kirsch 边缘检测算子
def kirsch(image):
m, n = image.shape
list = []
kirsch = np.zeros((m, n))
for i in range(2, m - 1):
for j in range(2, n - 1):
d1 = np.square(5 * image[i - 1, j - 1] + 5 * image[i - 1, j] + 5 * image[i - 1, j + 1] -
3 * image[i, j - 1] - 3 * image[i, j + 1] - 3 * image[i + 1, j - 1] -
3 * image[i + 1, j] - 3 * image[i + 1, j + 1])
d2 = np.square((-3) * image[i - 1, j - 1] + 5 * image[i - 1, j] + 5 * image[i - 1, j + 1] -
3 * image[i, j - 1] + 5 * image[i, j + 1] - 3 * image[i + 1, j - 1] -
3 * image[i + 1, j] - 3 * image[i + 1, j + 1])
d3 = np.square((-3) * image[i - 1, j - 1] - 3 * image[i - 1, j] + 5 * image[i - 1, j + 1] -
3 * image[i, j - 1] + 5 * image[i, j + 1] - 3 * image[i + 1, j - 1] -
3 * image[i + 1, j] + 5 * image[i + 1, j + 1])
d4 = np.square((-3) * image[i - 1, j - 1] - 3 * image[i - 1, j] - 3 * image[i - 1, j + 1] -
3 * image[i, j - 1] + 5 * image[i, j + 1] - 3 * image[i + 1, j - 1] +
5 * image[i + 1, j] + 5 * image[i + 1, j + 1])
d5 = np.square((-3) * image[i - 1, j - 1] - 3 * image[i - 1, j] - 3 * image[i - 1, j + 1] - 3
* image[i, j - 1] - 3 * image[i, j + 1] + 5 * image[i + 1, j - 1] +
5 * image[i + 1, j] + 5 * image[i + 1, j + 1])
d6 = np.square((-3) * image[i - 1, j - 1] - 3 * image[i - 1, j] - 3 * image[i - 1, j + 1] +
5 * image[i, j - 1] - 3 * image[i, j + 1] + 5 * image[i + 1, j - 1] +
5 * image[i + 1, j] - 3 * image[i + 1, j + 1])
d7 = np.square(5 * image[i - 1, j - 1] - 3 * image[i - 1, j] - 3 * image[i - 1, j + 1] +
5 * image[i, j - 1] - 3 * image[i, j + 1] + 5 * image[i + 1, j - 1] -
3 * image[i + 1, j] - 3 * image[i + 1, j + 1])
d8 = np.square(5 * image[i - 1, j - 1] + 5 * image[i - 1, j] - 3 * image[i - 1, j + 1] +
5 * image[i, j - 1] - 3 * image[i, j + 1] - 3 * image[i + 1, j - 1] -
3 * image[i + 1, j] - 3 * image[i + 1, j + 1])
# 第一种方法:取各个方向的最大值,效果并不好,采用另一种方法
list = [d1, d2, d3, d4, d5, d6, d7, d8]
kirsch[i, j] = int(np.sqrt(max(list)))
for i in range(m):
for j in range(n):
if kirsch[i, j] > 127:
kirsch[i, j] = 255
else:
kirsch[i, j] = 0
return kirsch
# 拉普拉斯卷积核
kernel_Laplacian_1 = np.array([
[0, 1, 0],
[1, -4, 1],
[0, 1, 0]])
kernel_Laplacian_2 = np.array([
[1, 1, 1],
[1, -8, 1],
[1, 1, 1]])
# 下面两个卷积核不具有旋转不变性
kernel_Laplacian_3 = np.array([
[2, -1, 2],
[-1, -4, -1],
[2, 1, 2]])
kernel_Laplacian_4 = np.array([
[-1, 2, -1],
[2, -4, 2],
[-1, 2, -1]])
# 5*5 LoG卷积模板
kernel_LoG = np.array([
[0, 0, -1, 0, 0],
[0, -1, -2, -1, 0],
[-1, -2, 16, -2, -1],
[0, -1, -2, -1, 0],
[0, 0, -1, 0, 0]])
# 卷积
output_1 = cv2.filter2D(image, -1, kernel_Prewitt_x)
output_2 = cv2.filter2D(image, -1, kernel_Sobel_x)
output_3 = cv2.filter2D(image, -1, kernel_Prewitt_x)
output_4 = cv2.filter2D(image, -1, kernel_Laplacian_1)
output_5 = kirsch(image)
# 显示锐化效果
image = cv2.resize(image, (800, 600))
output_1 = cv2.resize(output_1, (800, 600))
output_2 = cv2.resize(output_2, (800, 600))
output_3 = cv2.resize(output_3, (800, 600))
output_4 = cv2.resize(output_4, (800, 600))
output_5 = cv2.resize(output_5, (800, 600))
cv2.imshow('Original Image', image)
cv2.imshow('Prewitt Image', output_1)
cv2.imshow('Sobel Image', output_2)
cv2.imshow('Prewitt Image', output_3)
cv2.imshow('Laplacian Image', output_4)
cv2.imshow('kirsch Image', output_5)
# 停顿
if cv2.waitKey(0) & 0xFF == 27:
cv2.destroyAllWindows()
运行结果:
各个算子优缺点比较
Roberts算子:Roberts算子是2×2算子,对具有陡峭的低噪声图像响应最好,并且检测垂直边缘的效果好于斜向边缘,定位精度高。然而,它对噪声敏感,无法抑制噪声的影响。因此,它适用于边缘明显且噪声较少的图像分割。
Prewitt算子:Prewitt算子将两个点的各自一定领域内的灰度值求和,并根据两个灰度值和的差来计算x,y的偏导数。它是平均滤波,对噪声有抑制作用,对于灰度渐变的低噪声图像有较好的检测效果,但是像素平均相当于对图像的低通滤波,所以它对边缘的定位不如Roberts算子。对于混合多复杂噪声的图像,效果不太理想。
Sobel算子:Sobel算子是滤波算子的形式,用于提取边缘,可以利用快速卷积函数, 简单有效,因此应用广泛。美中不足的是,Sobel算子并没有将图像的主体与背景严格地区分开来,即Sobel算子没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。
Scharr算子:Scharr算子是对Sobel算子差异性的增强,因此两者之间的在检测图像边缘的原理和使用方式上相同。Scharr算子的边缘检测滤波的尺寸为3x3,因此也有称其为Scharr滤波器。可以通过将滤波器中的权重系数放大来增大像素值间的差异,弥补Sobel算子对图像中较弱的边缘提取效果较差的缺点。
Kirsch算子:Kirsch算子是一种非线性边缘检测器,可在几个预定方向上找到最大边缘强度。它以计算机科学家Russell A. Kirsch命名。它采用8个模板对图像上的每一个像素点进行卷积求导数,这8个模板代表8个方向,对图像上的8个特定边缘方向作出最大响应,运算中取最大值作为图像的边缘输出。对灰度渐变和噪声较多的图像处理效果较好。
Laplacian算子:对图像中的越阶性边缘点定位准确,对噪声非常敏感,丢失一部分边缘的方向信息,造成一些不连续的检测边缘。
Canny算子:是一个具有滤波,增强和检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以消除噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度的幅值和方向。在处理过程中,Canny算法还将经过一个非极大值抑制的过程。最后Canny算法将采用两个阈值来连接边缘。高定位精度、低误判率、抑制虚假边缘,适用于高噪声图像。
2.实现的简易的 Canny 边缘检测算法。
算法原理
Canny 是一个经典的图像边缘检测算法,一般包含如下几个步骤:
- 使用高斯模糊对图像进行模糊降噪处理
- 基于图像梯度幅值进行图像边缘增强
- 非极大值抑制处理进行图像边缘细化
- 图像二值化和边缘连接得到最终的结果
实现步骤:
- 用高斯滤波器平滑图像
- 计算图像中每个像素点的梯度强度和方向
- 对梯度幅值进行非极大值抑制
- 用双阈值算法检测来确定真实和潜在的边缘
优点:
-
对噪声不敏感,不容易受到噪声干扰
-
能够检测到真正的弱边缘
-
使用两种不同的阈值分别检测强边缘和弱边缘
-
仅当强边缘与弱边缘相连时才将弱边缘包含在输出图像中
缺点:
- 易使高频边缘被平滑掉,从而造成边缘丢失
代码如下:
import cv2
# 加载图像
image = cv2.imread(r'cat.png',0)
image = cv2.resize(image,(800,800))
def Canny(image,k,t1,t2):
img = cv2.GaussianBlur(image, (k, k), 0)
canny = cv2.Canny(img, t1, t2)
return canny
image = cv2.resize(image, (800, 600))
cv2.imshow('Original Image', image)
output =cv2.resize(Canny(image,3,50,150),(800,600))
cv2.imshow('Canny Image', output)
# 停顿
if cv2.waitKey(0) & 0xFF == 27:
cv2.destroyAllWindows()
运行结果:
3.复现论文 Holistically-Nested Edge Detection,发表于 CVPR 2015
一个基于深度学习的端到端边缘检测模型。
边缘检测系列3:【HED】 Holistically-Nested 边缘检测
HED 模型包含五个层级的特征提取架构,每个层级中:
- 使用 VGG Block 提取层级特征图
- 使用层级特征图计算层级输出
- 层级输出上采样
最后融合五个层级输出作为模型的最终输出:
- 通道维度拼接五个层级的输出
- 1x1 卷积对层级输出进行融合
模型总体架构图如下:
HED和Canny的处理结果进行比较:
从处理结果的展示中可以看到HED的精度高于Canny。Canny的精度主要依赖于阈值的设置,通过人为的阈值设置可以检测到细粒度的边缘,很依赖图片像素值。但是相比于神经网络,Canny缺失语义方面的理解,神经网络对边缘的理解是更多层次的。HED属于深度学习网络的一种,而且加入了Deep supervision,每个Side output继承上一层的特征,最后对多层特征融合,进一步取得了精度的提升。
4.复现论文 Richer Convolutional Features for Edge Detection,CVPR 2017 发表
一个基于更丰富的卷积特征的边缘检测模型 【RCF】。
边缘检测系列4:【RCF】基于更丰富的卷积特征的边缘检测
RCF 基于 HED 网络,与 VGG16 相比,RCF 主要做了如下修改:
- 与 HED 相同,RCF 去掉了最后一个池化层和之后的全连接层,形成了全卷积网络。
- 在 VGG16 的每个卷积层之后连接一个 1×1 大小深度为 21 的卷积层,并将每一阶段的特征累积得到混合特征。
- 在每个阶段得到混合特征之后添加反卷积层进行上采样(upsample)。
- 在上采样层之后添加 loss/sigmoid 层计算损失。
- 将所有上采用层连接,对每个阶段的特征进行融合,最后再通过 loss/sigmoid 层计算损失。
模型结构图如下:
论文每个阶段的输出如下:
RCF与HED之间最明显区别之处在以下三个方面:
- HED对于边缘检测来说,丢失了许多非常有用的信息,因为它仅使用到VGG16中每个阶段的最后一层卷积特征。RCF与之相反,它使用了所有卷积层的特征,使得在更大的范围内捕获更多的对象或对象局部边界成为可能。
- RCF创造性的提出了一个对训练样本非常合适的损失函数。而且,本文首先将标注人数大于A的边缘像素作为正样例,为0的作为负样例。除此之外,还忽略了标注者人数小于A的边缘像素,既不作为正样例,也不作为负样例。
- 与之相反的是,HED将标注人数小于总人数一半的边缘像素作为负样例。上述做法会迷惑网络的训练,因为这些点并不是真实的分边缘像素点。
5.【CED】添加了反向细化路径的 HED 模型 - 飞桨AI Studio (baidu.com)
Crisp Edge Detection(CED)模型是前面介绍过的 HED 模型的另一种改进模型。、
边缘检测系列5:【CED】添加了反向细化路径的 HED 模型
CED 模型总体基于 HED 模型改造而来,其中做了如下几个改进:
- 将模型中的上采样操作从转置卷积插值更换为 PixelShuffle
- 添加了反向细化路径,即一个反向的从高层级特征逐步往低层级特征的边缘细化路径
- 没有多层级输出,最终的输出为融合了各层级的特征的边缘检测结果
架构图如下:
PixelShuffle: 正常情况下,卷积操作会使特征图的高和宽缩小。但当我们的步长即 stride = 1/r < 1时,可以让卷积后的特征图的高和宽扩大——即分辨率增大或者叫上采样,这个新的操作叫做 PixelShuffle.
更多算法细节可以参考论文:Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network
原理图如下:
一方面是前馈传播,获取丰富的网络特征图,而一大特点是,用了一个反向细化路径(backward-refining path,也就是图中的橙色框,扩展开就是第二个虚线框中的结构)来补充HED网络,它使用有效的亚像素卷积逐步提高样本特征, 反向细化路径将特征映射与沿前向传播路径的中间特征融合,这个细化方式由多个细化模块一起组成,最终达到原图的分辨率。
细化模块融合了一个自顶向下的特征映射和前传上的特征映射,并使用亚像素卷积向上采样。在进行融合的时候,通过减少两个特征映射的维数来实现融合(即降维,由于通道数不同,不得不这样做)。
将输入前向路径特征映射的通道数表示为kh。 经过卷积和RELU运算后,信道被简化为k‘h,远小于kh。同样的操作从先前的细化模块对特征映射进行校正,从ku生成k‘u。将上述特征映射连接到一个新的具有k‘u+k’h通道的特征映射中,并将其减少,用k‘d通道通过3×3卷积层进行特征映射。从而降低了总体计算成本,平衡了两个输入特征映射。
对于上采样,采用亚像素卷积操作,而非流行的反卷积操作,这样对于边缘的定位表现似乎更好。亚像素卷积不是通过单个反卷积层直接输出放大特征映射,而是由一个卷积层和一个跟随相移层组成的一种网络层。总体上看,参数量也比HED少了许多。
CED的两个主要组成部分:前向传播路径和反向细化路径。 前向传播路径类似于HED。 它生成具有丰富语义信息的高维低分辨率特征图。 反向细化路径将沿着向前传播路径的特征图与中间特征进行融合。 这个细化是通过细化模块多次完成的。 每次我们使用子像素卷积将特征分辨率提高一个小的因子(2x),最终达到输入分辨率。
总结
本次实验主要是学习卷积层的一些知识,学习了常见的传统边缘检测的卷积核,并实现了实例化;学习了Canny边缘检测方法,了解了其步骤和原理,并实现了实例化;对于经典的HED、RCF、CED边缘检测模型有了浅显的认识;实现了二维卷积,利用自定义的二维卷积算子完成了一个简单的图片边缘检测,并且阅读了几篇边缘检测的论文。
参考
边缘检测系列5:【CED】添加了反向细化路径的 HED 模型
边缘检测-HED-RCF
边缘检测系列5:【CED】添加了反向细化路径的 HED 模型
边缘检测系列4:【RCF】基于更丰富的卷积特征的边缘检测
算子优缺点