NNDL 实验六 卷积神经网络(1)卷积

目录

5.1 卷积

5.1.1 二维卷积运算

5.1.2 二维卷积算子

5.1.3 二维卷积的参数量和计算量

5.1.4 感受野

5.1.5 卷积的变种

5.1.5.1 步长(Stride)

5.1.5.2 零填充(Zero Padding)

5.1.6 带步长和零填充的二维卷积算子

5.1.7 使用卷积运算完成图像边缘检测任务

选做题: Pytorch实现1、2;阅读3、4、5写体会。

边缘检测系列1:传统边缘检测算子

1.1构建通用的边缘检测算子

1.2图像边缘检测测试函数

1.3 Roberts 算子

1.4 Prewitt 算子

1.5 Sobel 算子

1.6 Scharr 算子

1.7 Krisch 算子

1.8 Robinson算子

1.9 Laplacian 算子

边缘检测系列2:  简易的 Canny 边缘检测器

边缘检测系列3:【HED】 Holistically-Nested 边缘检测

边缘检测系列4:【RCF】基于更丰富的卷积特征的边缘检测

边缘检测系列5:【CED】添加了反向细化路径的 HED 模型

总结:


卷积神经网络(Convolutional Neural Network,CNN)

  • 受生物学上感受野机制的启发而提出。
  • 一般是由卷积层、汇聚层和全连接层交叉堆叠而成的前馈神经网络
  • 有三个结构上的特性:局部连接、权重共享、汇聚。
  • 具有一定程度上的平移、缩放和旋转不变性。
  • 和前馈神经网络相比,卷积神经网络的参数更少。
  • 主要应用在图像和视频分析的任务上,其准确率一般也远远超出了其他的神经网络模型。
  • 近年来卷积神经网络也广泛地应用到自然语言处理、推荐系统等领域。

5.1 卷积

卷积神经网络有三个结构上的特性:局部连接、权重共享和汇聚。这些特性使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。和前馈神经网络相比,卷积神经网络的参数也更少。因此,通常会使用卷积神经网络来处理图像信息。

卷积是分析数学中的一种重要运算,常用于信号处理或图像处理任务。本节以二维卷积为例来进行实践。

5.1.1 二维卷积运算

在机器学习和图像处理领域,卷积的主要功能是在一个图像(或特征图)上滑动一个卷积核,通过卷积操作得到一组新的特征。在计算卷积的过程中,需要进行卷积核的翻转,而这也会带来一些不必要的操作和开销。因此,在具体实现上,一般会以数学中的互相关(Cross-Correlatio)运算来代替卷积。
在神经网络中,卷积运算的主要作用是抽取特征,卷积核是否进行翻转并不会影响其特征抽取的能力。特别是当卷积核是可学习的参数时,卷积和互相关在能力上是等价的。因此,很多时候,为方便起见,会直接用互相关来代替卷积。

5.1.2 二维卷积算子

【使用pytorch实现】

首先实现一个简单的二维卷积算子,代码实现如下:

import torch
import torch.nn as nn
 
class Conv2D(nn.Module):
    def __init__(self, weight_attr=torch.tensor([[0., 1.], [2., 3.]])):
        super(Conv2D, self).__init__()
        # 使用'torch.Parameter'进行参数初始化
        self.weight = torch.nn.Parameter(weight_attr)
 
    def forward(self, X):
        """
        输入:
            - X:输入矩阵,shape=[B, M, N],B为样本数量
        输出:
            - output:输出矩阵
        """
        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, dim=[1, 2])
        return output
 
# 随机构造一个二维输入矩阵
torch.random.manual_seed(100)
inputs = torch.tensor([[[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]])
 
conv2d = Conv2D()
outputs = conv2d(inputs)
print("input: {}, \noutput: {}".format(inputs, outputs))

5.1.3 二维卷积的参数量和计算量

 

5.1.4 感受野

输出特征图上每个点的数值,是由输入图片上大小为U×VU×V的区域的元素与卷积核每个元素相乘再相加得到的,所以输入图像上U×VU×V区域内每个元素数值的改变,都会影响输出点的像素值。我们将这个区域叫做输出特征图上对应点的感受野。感受野内每个元素数值的变动,都会影响输出点的数值变化。

当增加卷积网络深度的同时,感受野将会增大,输出特征图中的一个像素点将会包含更多的图像语义信息。 

5.1.5 卷积的变种

在卷积的标准定义基础上,还可以引入卷积核的滑动步长和零填充来增加卷积的多样性,从而更灵活地进行特征抽取。

5.1.5.1 步长(Stride)

在卷积运算的过程中,有时会希望跳过一些位置来降低计算的开销,也可以把这一过程看作是对标准卷积运算输出的下采样

在计算卷积时,可以在所有维度上每间隔S个元素计算一次,SS称为卷积运算的步长(Stride),也就是卷积核在滑动时的间隔。

在二维卷积运算中,当步长S=2时,计算过程如图5.6 所示。

图5.6:步长为2的二维卷积计算过程

5.1.5.2 零填充(Zero Padding)

在卷积运算中,还可以对输入用零进行填充使得其尺寸变大。根据卷积的定义,如果不进行填充,当卷积核尺寸大于1时,输出特征会缩减。对输入进行零填充则可以对卷积核的宽度和输出的大小进行独立的控制。

在二维卷积运算中,零填充(Zero Padding)是指在输入矩阵周围对称地补上PP个00。图5.7 为使用零填充的示例。

图5.7:padding=1的零填充

 

5.1.6 带步长和零填充的二维卷积算子

【使用pytorch实现】

引入步长和零填充后,二维卷积算子代码实现如下:

class Conv2D(nn.Module):
    def __init__(self, kernel_size, stride=1, padding=0, weight_attr=False):
        super(Conv2D, self).__init__()
        if type(weight_attr) == bool:
            weight_attr = torch.ones(size=(kernel_size, kernel_size))
        self.weight = torch.nn.Parameter(weight_attr)
        # 步长
        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,
                    dim=[1, 2])
        return output


inputs = torch.randn(size=[2, 8, 8])
conv2d_padding = Conv2D(kernel_size=3, padding=1, weight_attr=torch.zeros((3, 3)))
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时,模型的输出特征图与输入特征图保持一致;

当stride=2时,模型的输出特征图的宽和高都缩小一倍。

5.1.7 使用卷积运算完成图像边缘检测任务

【使用pytorch实现】

在图像处理任务中,常用拉普拉斯算子对物体边缘进行提取,拉普拉斯算子为一个大小为3×3的卷积核,中心元素值是8,其余元素值是−1。

构造一个简单的拉普拉斯算子,并对一张输入的灰度图片进行边缘检测,提取出目标的外形轮廓。

import matplotlib.pyplot as plt
from PIL import Image
import numpy as np

# 读取图片
img = Image.open(r'img-3.jpg').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)
# 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:传统边缘检测算子

边缘检测系列1:传统边缘检测算子 - 飞桨AI Studio

实现一些传统边缘检测算子,如:Roberts、Prewitt、Sobel、Scharr、Kirsch、Robinson、Laplacian

1.1构建通用的边缘检测算子

  • 因为上述的这些算子在本质上都是通过卷积计算实现的,只是所使用到的卷积核参数有所不同
  • 所以可以构建一个通用的计算算子,只需要传入对应的卷积核参数即可实现不同的边缘检测
  • 并且在后处理时集成了上述的四种计算最终边缘强度的方式
import numpy as np
 
import torch
import torch.nn as nn
 
import os
import cv2
from PIL import Image

class EdgeOP(nn.Module):
    def __init__(self, kernel):
        '''
        kernel: shape(out_channels, in_channels, h, w)
        '''
        super(EdgeOP, self).__init__()
        out_channels, in_channels, h, w = kernel.shape
        self.filter = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=(h, w), padding='same',
                                bias=False)
        self.filter.weight.data=torch.tensor(kernel,dtype=torch.float32)
 
    @staticmethod
    def postprocess(outputs, mode=0, weight=None):
        '''
        Input: NCHW
        Output: NHW(mode==1-3) or NCHW(mode==4)
 
        Params:
            mode: switch output mode(0-4)
            weight: weight when mode==3
        '''
        if mode == 0:
            results = torch.sum(torch.abs(outputs), dim=1)
        elif mode == 1:
            results = torch.sqrt(torch.sum(torch.pow(outputs, 2), dim=1))
        elif mode == 2:
            results = torch.max(torch.abs(outputs), dim=1).values
        elif mode == 3:
            if weight is None:
                C = outputs.shape[1]
                weight = torch.tensor([1 / C] * C, dtype=torch.float32)
            else:
                weight = torch.tensor(weight, dtype=torch.float32)
            results = torch.einsum('nchw, c -> nhw', torch.abs(outputs), weight)
        elif mode == 4:
            results = torch.abs(outputs)
        return torch.clip(results, 0, 255).to(torch.uint8)
 
    @torch.no_grad()
    def forward(self, images, mode=0, weight=None):
        outputs = self.filter(images)
        return self.postprocess(outputs, mode, weight)

1.2图像边缘检测测试函数

  • 为了方便测试就构建了如下的测试函数,测试同一张图片不同算子/不同边缘强度计算方法的边缘检测效果
import os
import cv2
 
from PIL import Image
 
def test_edge_det(kernel, img_path='img-3.jpg'):
    img = cv2.imread(img_path, 0)
    print(img)
    img_tensor = torch.tensor(img, dtype=torch.float32)[None, None, ...]
    op = EdgeOP(kernel)
    all_results = []
    for mode in range(4):
        results = op(img_tensor, mode=mode)
        all_results.append(results.numpy()[0])
 
    results = op(img_tensor, mode=4)
    for result in results.numpy()[0]:
        all_results.append(result)
    return all_results, np.concatenate(all_results, 1)

1.3 Roberts 算子

Roberts算法优点:局部差分算子寻找边缘,边缘定位精度较高,对陡峭边缘且含有噪声少的图像效果较好。

Roberts算法缺点:容易丢失一部分边缘,没经过平滑处理,不具备抑制噪声能力。

roberts_kernel = np.array([
    [[
        [1,  0],
        [0, -1]
    ]],
    [[
        [0, -1],
        [1,  0]
    ]]
])
 
_, concat_res = test_edge_det(roberts_kernel)
Image.fromarray(concat_res).show()

1.4 Prewitt 算子

Prewitt算法优点:先做加权平滑,后微分,有移植噪声能力,边缘定位精度校准。

Prewitt算法缺点:边缘容易丢失多像素宽度,出现虚假边缘。

prewitt_kernel = np.array([
    [[
        [-1, -1, -1],
        [ 0,  0,  0],
        [ 1,  1,  1]
    ]],
    [[
        [-1,  0,  1],
        [-1,  0,  1],
        [-1,  0,  1]
    ]],
    [[
        [ 0,  1,  1],
        [-1,  0,  1],
        [-1, -1,  0]
    ]],
    [[
        [ -1, -1,  0],
        [ -1,  0,  1],
        [  0,  1,  1]
    ]]
])
 
_, concat_res = test_edge_det(prewitt_kernel)
Image.fromarray(concat_res).show()

1.5 Sobel 算子

Sobel算法优点:先做加权平滑,后微分,有移植噪声能力,边缘定位精度校准。

Sobel算法缺点:边缘容易丢失多像素宽度,出现虚假边缘。

sobel_kernel = np.array([
    [[
        [-1, -2, -1],
        [ 0,  0,  0],
        [ 1,  2,  1]
    ]],
    [[
        [-1,  0,  1],
        [-2,  0,  2],
        [-1,  0,  1]
    ]],
    [[
        [ 0,  1,  2],
        [-1,  0,  1],
        [-2, -1,  0]
    ]],
    [[
        [ -2, -1,  0],
        [ -1,  0,  1],
        [  0,  1,  2]
    ]]
])
 
_, concat_res = test_edge_det(sobel_kernel)
Image.fromarray(concat_res).show()

1.6 Scharr 算子

scharr_kernel = np.array([
    [[
        [-3, -10, -3],
        [ 0,   0,  0],
        [ 3,  10,  3]
    ]],
    [[
        [-3,  0,   3],
        [-10, 0,  10],
        [-3,  0,   3]
    ]],
    [[
        [ 0,  3,  10],
        [-3,  0,  3],
        [-10, -3,  0]
    ]],
    [[
        [ -10, -3, 0],
        [ -3,  0, 3],
        [ 0,  3,  10]
    ]]
])
 
_, concat_res = test_edge_det(scharr_kernel)
Image.fromarray(concat_res).show()

1.7 Krisch 算子

Krisch_kernel = np.array([
    [[
        [5, 5, 5],
        [-3,0,-3],
        [-3,-3,-3]
    ]],
    [[
        [-3, 5,5],
        [-3,0,5],
        [-3,-3,-3]
    ]],
    [[
        [-3,-3,5],
        [-3,0,5],
        [-3,-3,5]
    ]],
    [[
        [-3,-3,-3],
        [-3,0,5],
        [-3,5,5]
    ]],
    [[
        [-3, -3, -3],
        [-3,0,-3],
        [5,5,5]
    ]],
    [[
        [-3, -3, -3],
        [5,0,-3],
        [5,5,-3]
    ]],
    [[
        [5, -3, -3],
        [5,0,-3],
        [5,-3,-3]
    ]],
    [[
        [5, 5, -3],
        [5,0,-3],
        [-3,-3,-3]
    ]],
])
 
_, concat_res = test_edge_det(Krisch_kernel)
Image.fromarray(concat_res).show()

1.8 Robinson算子

robinson_kernel = np.array([
    [[
        [1, 2, 1],
        [0, 0, 0],
        [-1, -2, -1]
    ]],
    [[
        [0, 1, 2],
        [-1, 0, 1],
        [-2, -1, 0]
    ]],
    [[
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ]],
    [[
        [-2, -1, 0],
        [-1, 0, 1],
        [0, 1, 2]
    ]],
    [[
        [-1, -2, -1],
        [0, 0, 0],
        [1, 2, 1]
    ]],
    [[
        [0, -1, -2],
        [1, 0, -1],
        [2, 1, 0]
    ]],
    [[
        [1, 0, -1],
        [2, 0, -2],
        [1, 0, -1]
    ]],
    [[
        [2, 1, 0],
        [1, 0, -1],
        [0, -1, -2]
    ]],
])
 
_, concat_res = test_edge_det(robinson_kernel)
Image.fromarray(concat_res).show()

1.9 Laplacian 算子

Laplacian算法优点:不依赖边缘方向的二阶微分算子,对阶跃型边缘点定位准确。

Laplacian算法缺点:对噪声敏感,噪声成分加强,抗噪声能力差,易丢失一部分边缘的方向信息。

laplacian_kernel = np.array([
    [[
        [1, 1, 1],
        [1, -8, 1],
        [1, 1, 1]
    ]],
    [[
        [0, 1, 0],
        [1, -4, 1],
        [0, 1, 0]
    ]]
])
 
_, concat_res = test_edge_det(laplacian_kernel)
Image.fromarray(concat_res).show()

总结:边缘检测,实质上还是通过模板来求目标灰度值。只是模板的区别,以及通过求多个模板对应的灰度值后的处理方式不同而已。 

边缘检测系列2:简易的 Canny 边缘检测器

边缘检测系列2:简易的 Canny 边缘检测器 - 飞桨AI Studio

基于 OpenCV 实现快速的 Canny 边缘检测

  • 在 OpenCV 中只需要使用 cv2.Canny 函数即可实现 Canny 边缘检测
import cv2
import numpy as np
 
from PIL import Image
 
lower = 30  # 最小阈值
upper = 70  # 最大阈值
 
img_path = 'img-3.jpg'  # 指定测试图像路径
 
gray = cv2.imread(img_path, 0)  # 读取灰度图像
edge = cv2.Canny(gray, lower, upper) # Canny 图像边缘检测
 
contrast = np.concatenate([edge, gray], 1) # 图像拼接
Image.fromarray(contrast).show()# 显示图像

边缘检测系列3:【HED】 Holistically-Nested 边缘检测

边缘检测系列3:【HED】 Holistically-Nested 边缘检测 - 飞桨AI Studio

复现论文 Holistically-Nested Edge Detection,发表于 CVPR 2015 

一个基于深度学习的端到端边缘检测模型

模型结构

  • HED 模型包含五个层级的特征提取架构,每个层级中:

    • 使用 VGG Block 提取层级特征图

    • 使用层级特征图计算层级输出

    • 层级输出上采样

  • 最后融合五个层级输出作为模型的最终输出:

    • 通道维度拼接五个层级的输出

    • 1x1 卷积对层级输出进行融合

  • 模型总体架构图如下:

效果演示

  • 论文中的效果对比图:

Holistically表示该算法试图训练一个image-to-image的网络;Nested则强调在生成的输出过程中通过不断的集成和学习得到更精确的边缘预测图的过程。它解决了两个重要的问题,1)整体图片的训练和预测,2)多尺度多层级的特征学习。HED能实现端到端的训练,输入一个图片,输出对应的边缘图片。

从上图中HED和传统Canny算法进行边缘检测的效果对比图我们可以看到HED的效果要明显优于Canny算子的。

边缘检测系列4:【RCF】基于更丰富的卷积特征的边缘检测

边缘检测系列4:【RCF】基于更丰富的卷积特征的边缘检测 - 飞桨AI Studio

复现论文 Richer Convolutional Features for Edge Detection,CVPR 2017 发表

一个基于更丰富的卷积特征的边缘检测模型 【RCF】。

模型架构(RCF 与 HED 比较)

  • RCF 与 HED 模型一样,包含五个层级的特征提取架构,同样也是基于 VGG 16 Backbone

  • 相比 HED,RCF 模型更加充分利用对象的多尺度和多级信息来全面地执行图像到图像的预测

  • RCF 不只是使用了每个层级的输出,而是使用了每个层级中所有卷积层的输出进行融合(Conv + sum)后,作为边缘检测的输入

  • 模型结构图如下:

效果演示

  • RCF 边缘检测

RCF将所有卷积特性封装成更具判别性的表示形式,这很好地利用了丰富的特性层次结构,并且可以通过反向传播进行训练。RCF充分利用了目标的多尺度、多层次信息,全面地进行图像对图像的预测。使用VGG16网络,可以在几个可用的数据集上实现了最先进的性能。

边缘检测系列5:【CED】添加了反向细化路径的 HED 模型

边缘检测系列5:【CED】添加了反向细化路径的 HED 模型 - 飞桨AI Studio

Crisp Edge Detection(CED)模型是前面介绍过的 HED 模型的另一种改进模型

模型架构

  • CED 模型总体基于 HED 模型改造而来,其中做了如下几个改进:

    • 将模型中的上采样操作从转置卷积插值更换为 PixelShuffle

    • 添加了反向细化路径,即一个反向的从高层级特征逐步往低层级特征的边缘细化路径

    • 没有多层级输出,最终的输出为融合了各层级的特征的边缘检测结果

  • 架构图如下

效果参考

  • 论文效果图:

CED 模型利用自上而下的反向细化路径,并逐渐增加特征图的分辨率以生成清晰的边缘。

CED提出了细化的方案,即1)反向细化路径模块,以及2)采用亚像素卷积替代线性插值的全卷积,对于边缘定位效果更好。

总结:

本次实验的内容还是很丰富的,主要是学习卷积的基础知识,在上次的作业中也有涉及,然后就是动手实践了各种边缘检测的模型,学习到了很多边缘检测的卷积核,在实践中体会它们的不同。通过老师的引导,认识到了HED、RCF、CED等边缘检测模型。

参考:

图像处理 —— 边缘检测 - 知乎

边缘检测之HED - 知乎

http://t.csdn.cn/7ORrV

http://t.csdn.cn/fjpR5

http://t.csdn.cn/bAbui

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值