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实现】

选做题

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

 实现的简易的 Canny 边缘检测算法 

Holistically-Nested 边缘检测 

RCF边缘检测模型

 Crisp Edge Detection(CED)模型

心得体会


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

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

5.1 卷积

考虑到使用全连接前馈网络来处理图像时,会出现如下问题:

  1. 模型参数过多,容易发生过拟合。 在全连接前馈网络中,隐藏层的每个神经元都要跟该层所有输入的神经元相连接。随着隐藏层神经元数量的增多,参数的规模也会急剧增加,导致整个神经网络的训练效率非常低,也很容易发生过拟合。

  2. 难以提取图像中的局部不变性特征。 自然图像中的物体都具有局部不变性特征,比如尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变性特征。

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

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

5.1.1 二维卷积运算

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

对于一个输入矩阵X\in\mathbb{R}^{M*N}和一个滤波器W\in\mathbb{R}^{U*V},它们的卷积为

y_{i,j}=\sum_{u=0}^{U-1}\sum_{v=0}^{V-1}w_{uv}x_{i+u,j+v}

 经过卷积运算后,最终输出矩阵大小则为

M′=M−U+1

N′=N−V+1

可以发现,使用卷积处理图像,会有以下两个特性:

   1.在卷积层(假设是第ll层)中的每一个神经元都只和前一层(第l-1层)中某个局部窗口内的神经          元相连,构成一个局部连接网络,这也就是卷积神经网络的局部连接特性。
   2.由于卷积的主要功能是在一个图像(或特征图)上滑动一个卷积核,所以作为参数的卷积核        W\in\mathbb{R}^{U*V}对于第ll层的所有的神经元都是相同的,这也就是卷积神经网络的权重共享特性。

5.1.2 二维卷积算子

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))

运行结果:

input: tensor([[[1., 2., 3.],
         [4., 5., 6.],
         [7., 8., 9.]]]), 
output: tensor([[[25., 31.],
         [43., 49.]]], grad_fn=<CopySlices>)

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

参数量

由于二维卷积的运算方式为在一个图像(或特征图)上滑动一个卷积核,通过卷积操作得到一组新的特征。所以参数量仅仅与卷积核的尺寸有关,对于一个输入矩阵X\in\mathbb{R}^{M*N}和一个滤波器W\in\mathbb{R}^{U*V},卷积核的参数量为U*V。

假设有一幅大小为32×32的图像,如果使用全连接前馈网络进行处理,即便第一个隐藏层神经元个数为1,此时该层的参数量也高达1025个,此时该层的计算过程如图所示

 

可以想像,随着隐藏层神经元数量的变多以及层数的加深,使用全连接前馈网络处理图像数据时,参数量会急剧增加。如果使用卷积进行图像处理,当卷积核为×3时,参数量仅为9,相较于全连接前馈网络,参数量少了非常多。

计算量

在卷积神经网络中运算时,通常会统计网络总的乘加运算次数作为计算量,来衡量整个网络的运算速度。对于单个二维卷积,计算量的统计方式为:

FLOPs=M{}'*N{}'*U*V

其中M′×N′表示输出特征图的尺寸,即输出特征图上每个点都要与卷积核W\in\mathbb{R}^{U*V}进行U×V次乘加运算。对于一幅大小为32×32的图像,使用3×3的卷积核进行运算可以得到以下的输出特征图尺寸:

M{}'=M-U+1=30

N{}'=N-V+1=30

此时,计算量为:

FLOPs=M{}'*N{}'*U*V=30*30*3*3=8100

5.1.4 感受野

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

  而当通过两层3×3的卷积之后,感受野的大小将会增加到5×5

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

5.1.5 卷积的变种

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

5.1.5.1 步长(Stride)

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

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

对于一个输入矩阵X\in\mathbb{R}^{M*N}和一个滤波器W\in\mathbb{R}^{U*V},它们的卷积为

y_{i,j}=\sum_{u=0}^{U-1}\sum_{v=0}^{V-1}w_{uv}x_{i*S+u,j*S+v}

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

5.1.5.2 零填充(Zero Padding)

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

在二维卷积运算中,零填充(Zero Padding)是指在输入矩阵周围对称地补上P个0。

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

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))

运行结果: 

When kernel_size=3, padding=1 stride=1, input's shape: torch.Size([2, 8, 8]), output's shape: torch.Size([2, 8, 8])
When kernel_size=3, padding=1 stride=2, input's shape: torch.Size([2, 8, 8]), output's shape: torch.Size([2, 4, 4])

从输出结果看出,使用3×3大小卷积,padding为1;当stride=1时,模型的输出特征图与输入特征图保持一致;当stride=2时,模型的输出特征图的宽和高都缩小一倍。

5.1.7 使用卷积运算完成图像边缘检测任务【使用pytorch实现】

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

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

# 读取图片
img = Image.open('touxiang.jpg').convert('L')#转成灰度图
img.resize((256,256))


# 设置卷积核参数
w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')
# 创建卷积算子,卷积核大小为3x3,并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(kernel_size=3, stride=1, padding=0, weight_attr=torch.tensor(w))

# 将读入的图片转化为float32类型的numpy.ndarray
inputs = np.array(img).astype('float32')
print("bf to_tensor, inputs:",inputs)
# 将图片转为Tensor
inputs = torch.tensor(inputs)
print("bf unsqueeze, inputs:",inputs)
inputs = torch.unsqueeze(inputs, axis=0)
print("af unsqueeze, inputs:",inputs)
outputs = conv(inputs)
outputs.detach().numpy()
# 可视化结果
plt.figure(figsize=(8, 4))
f = plt.subplot(121)
f.set_title('input image', fontsize=15)
plt.imshow(img)
f = plt.subplot(122)
f.set_title('output feature map', fontsize=15)
plt.imshow(outputs.detach().numpy().squeeze(), cmap='gray')
plt.savefig('conv-vis.pdf')
plt.show()

运行结果:

bf to_tensor, inputs: [[241. 245. 244. ... 238. 239. 243.]
 [238. 242. 243. ... 239. 238. 240.]
 [239. 243. 242. ... 243. 240. 240.]
 ...
 [245. 242. 242. ... 238. 236. 240.]
 [240. 239. 241. ... 238. 235. 240.]
 [235. 236. 240. ... 239. 235. 240.]]
bf unsqueeze, inputs: tensor([[241., 245., 244.,  ..., 238., 239., 243.],
        [238., 242., 243.,  ..., 239., 238., 240.],
        [239., 243., 242.,  ..., 243., 240., 240.],
        ...,
        [245., 242., 242.,  ..., 238., 236., 240.],
        [240., 239., 241.,  ..., 238., 235., 240.],
        [235., 236., 240.,  ..., 239., 235., 240.]])
af unsqueeze, inputs: tensor([[[241., 245., 244.,  ..., 238., 239., 243.],
         [238., 242., 243.,  ..., 239., 238., 240.],
         [239., 243., 242.,  ..., 243., 240., 240.],
         ...,
         [245., 242., 242.,  ..., 238., 236., 240.],
         [240., 239., 241.,  ..., 238., 235., 240.],
         [235., 236., 240.,  ..., 239., 235., 240.]]])

 从输出结果看,使用拉普拉斯算子,目标的边缘可以成功被检测出来。

选做题

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

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)

 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()

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]
    ]]
])

 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)

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)

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)

 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)

 实现的简易的 Canny 边缘检测算法 

  • Canny 是一个经典的图像边缘检测算法,一般包含如下几个步骤:

    • 使用高斯模糊对图像进行模糊降噪处理

    • 基于图像梯度幅值进行图像边缘增强

    • 非极大值抑制处理进行图像边缘细化

    • 图像二值化和边缘连接得到最终的结果

 

基于Numpy 模块实现简单的 Canny 检测器

导入需要的包:

import cv2
import math
import numpy as np

 1. 高斯模糊

 

def smooth(img_gray, kernel_size=5):
    # 生成高斯滤波器
    """
    要生成一个 (2k+1)x(2k+1) 的高斯滤波器,滤波器的各个元素计算公式如下:
    H[i, j] = (1/(2*pi*sigma**2))*exp(-1/2*sigma**2((i-k-1)**2 + (j-k-1)**2))
    """
    sigma1 = sigma2 = 1.4
    gau_sum = 0
    gaussian = np.zeros([kernel_size, kernel_size])
    for i in range(kernel_size):
        for j in range(kernel_size):
            gaussian[i, j] = math.exp(
                (-1 / (2 * sigma1 * sigma2)) *
                (np.square(i - 3) + np.square(j-3))
            ) / (2 * math.pi * sigma1 * sigma2)
            gau_sum = gau_sum + gaussian[i, j]
 
    # 归一化处理
    gaussian = gaussian / gau_sum
 
    # 高斯滤波
 
    img_gray = np.pad(img_gray, ((kernel_size//2, kernel_size//2), (kernel_size//2, kernel_size//2)), mode='constant')
    W, H = img_gray.shape
    new_gray = np.zeros([W - kernel_size, H - kernel_size])
 
    for i in range(W-kernel_size):
        for j in range(H-kernel_size):
            new_gray[i, j] = np.sum(
                img_gray[i: i + kernel_size, j: j + kernel_size] * gaussian
            )
 
    return new_gray

  2. 计算图像的梯度幅值

def gradients(new_gray):
    """
    :type: image which after smooth
    :rtype:
        dx: gradient in the x direction
        dy: gradient in the y direction
        M: gradient magnitude
        theta: gradient direction
    """
 
    W, H = new_gray.shape
    dx = np.zeros([W-1, H-1])
    dy = np.zeros([W-1, H-1])
    M = np.zeros([W-1, H-1])
    theta = np.zeros([W-1, H-1])
 
    for i in range(W-1):
        for j in range(H-1):
            dx[i, j] = new_gray[i+1, j] - new_gray[i, j]
            dy[i, j] = new_gray[i, j+1] - new_gray[i, j]
            # 图像梯度幅值作为图像强度值
            M[i, j] = np.sqrt(np.square(dx[i, j]) + np.square(dy[i, j]))
            # 计算  θ - artan(dx/dy)
            theta[i, j] = math.atan(dx[i, j] / (dy[i, j] + 0.000000001))
 
    return dx, dy, M, theta

 3. 非极大值抑制

def NMS(M, dx, dy):
 
    d = np.copy(M)
    W, H = M.shape
    NMS = np.copy(d)
    NMS[0, :] = NMS[W-1, :] = NMS[:, 0] = NMS[:, H-1] = 0
 
    for i in range(1, W-1):
        for j in range(1, H-1):
 
            # 如果当前梯度为0,该点就不是边缘点
            if M[i, j] == 0:
                NMS[i, j] = 0
 
            else:
                gradX = dx[i, j]  # 当前点 x 方向导数
                gradY = dy[i, j]  # 当前点 y 方向导数
                gradTemp = d[i, j]  # 当前梯度点
 
                # 如果 y 方向梯度值比较大,说明导数方向趋向于 y 分量
                if np.abs(gradY) > np.abs(gradX):
                    weight = np.abs(gradX) / np.abs(gradY)  # 权重
                    grad2 = d[i-1, j]
                    grad4 = d[i+1, j]
 
                    # 如果 x, y 方向导数符号一致
                    # 像素点位置关系
                    # g1 g2
                    #    c
                    #    g4 g3
                    if gradX * gradY > 0:
                        grad1 = d[i-1, j-1]
                        grad3 = d[i+1, j+1]
 
                    # 如果 x,y 方向导数符号相反
                    # 像素点位置关系
                    #    g2 g1
                    #    c
                    # g3 g4
                    else:
                        grad1 = d[i-1, j+1]
                        grad3 = d[i+1, j-1]
 
                # 如果 x 方向梯度值比较大
                else:
                    weight = np.abs(gradY) / np.abs(gradX)
                    grad2 = d[i, j-1]
                    grad4 = d[i, j+1]
 
                    # 如果 x, y 方向导数符号一致
                    # 像素点位置关系
                    #      g3
                    # g2 c g4
                    # g1
                    if gradX * gradY > 0:
 
                        grad1 = d[i+1, j-1]
                        grad3 = d[i-1, j+1]
 
                    # 如果 x,y 方向导数符号相反
                    # 像素点位置关系
                    # g1
                    # g2 c g4
                    #      g3
                    else:
                        grad1 = d[i-1, j-1]
                        grad3 = d[i+1, j+1]
 
                # 利用 grad1-grad4 对梯度进行插值
                gradTemp1 = weight * grad1 + (1 - weight) * grad2
                gradTemp2 = weight * grad3 + (1 - weight) * grad4
 
                # 当前像素的梯度是局部的最大值,可能是边缘点
                if gradTemp >= gradTemp1 and gradTemp >= gradTemp2:
                    NMS[i, j] = gradTemp
 
                else:
                    # 不可能是边缘点
                    NMS[i, j] = 0
 
    return NMS

 4. 图像二值化和边缘连接

def double_threshold(NMS, threshold1, threshold2):
    NMS = np.pad(NMS, ((1, 1), (1, 1)), mode='constant')
    W, H = NMS.shape
    DT = np.zeros([W, H])
 
    # 定义高低阈值
    TL = threshold1  * np.max(NMS)
    TH = threshold2  * np.max(NMS)
 
    for i in range(1, W-1):
        for j in range(1, H-1):
           # 双阈值选取
            if (NMS[i, j] < TL):
                DT[i, j] = 0
 
            elif (NMS[i, j] > TH):
                DT[i, j] = 1
 
           # 连接
            elif ((NMS[i-1, j-1:j+1] < TH).any() or
                    (NMS[i+1, j-1:j+1].any() or
                     (NMS[i, [j-1, j+1]] < TH).any())):
                DT[i, j] = 1
 
    return DT

 5. Canny 边缘检测

def canny(gray, threshold1, threshold2, kernel_size=5):
    norm_gray = gray
    gray_smooth = smooth(norm_gray, kernel_size)
    dx, dy, M, theta = gradients(gray_smooth)
    nms = NMS(M, dx, dy)
    DT = double_threshold(nms, threshold1, threshold2)
    return DT

 6. 代码测试

import cv2
import numpy as np
 
from PIL import Image
 
lower = 0.1 # 最小阈值
upper = 0.3 # 最大阈值
 
img_path = '1-2.jpg' # 指定测试图像路径
 
gray = cv2.imread(img_path, 0) # 读取灰度图像
edge = canny(gray, lower, upper) # Canny 图像边缘检测
edge = (edge * 255).astype(np.uint8) # 反归一化
 
contrast = np.concatenate([edge, gray], 1) # 图像拼接
Image.fromarray(contrast) # 显示图像

Holistically-Nested 边缘检测 

效果对比

  

 Holistically-Nested 表示此模型是一个多尺度的端到端边缘检测模型

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

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

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

    • 层级输出上采样

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

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

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

  • 模型总体架构图如下:

    RCF边缘检测模型

 效果展示

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

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

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

  • 模型结构图如下

 

 Crisp Edge Detection(CED)模型

 效果图

 

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

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

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

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

  • 架构图如下

 

心得体会

这次实验了解了有关卷积的基础知识,对二维卷积的算子、计算量、参数量等进行了学习,并使用pytorch代码复现了二维卷积算子和使用卷积完成图像的边缘检测的任务。在选做题中实现了一些传统边缘检测算子,其中Roberts算子对具有陡峭低噪声的图像处理效果较好,Prewitt算子、Sobel算子和Kirsch算子对灰度渐变和噪声较多的图像处理效果较好。除此之外还了解了Holistically-Nested 边缘检测模型、RCF边缘检测模型与Crisp Edge Detection(CED)模型,Holistically-Nested 模型是一个多尺度的端到端边缘检测模型, RCF基于更丰富的卷积特征来进行边缘检测,效果相比 HED 有所提升,而CED 模型总体基于 HED 模型改造而来,添加了反向细化路径,没有多层级输出,最终的输出为融合了各层级的特征的边缘检测结果。

参考

NNDL 实验5(上)

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

6. 卷积神经网络 — 动手学深度学习 2.0.0-beta1 documentation (d2l.ai)

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

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

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

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

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

神经网络的实验步骤详细分析具体-神经网络大作业(一).doc 本人做的神经网络的实验,步骤详细,分析具体,适合做入门学习用-I do neural network experiments, the steps detailed analysis of specific, suitable for entry to study 截取某些内容,方便参考: 用BP网络识别雷达测速的三类信号 一.数据来源      此信号来自一部测速雷达获得的三种目标的回波信号,三种目标分别是行人W、自行车B和卡车T,信号中包含目标的速度信息。 二.信号的分析与处理      根据所给的三类信号的样本,每一个样本中均包含1024个数据,由于每一个样本的数据量较大,不可能将所有1024个数据全都作为神经元的输入,计算量太大,所以必须首先对信号进行分析,提取最有价值的特征信息。      首先可以看看每一个样本中的数据图,以各类信号中的第一个样本为例,如图1所示。 (1)                                       (2)                                        (3) 图1 (1)行人数据图  (2)自行车数据图  (3)卡车数据图              从上图的时域数据基本上观察不出规律,因此我们要对数据进行傅立叶变换,从频域分析数据的特征,如下图2所示。 图2 行人数据频谱图 从上图中看到行人的数据的频谱的幅度很小,原因是因为信号在零点处的值特别大,所以要将在零点处的值去掉,得到如图3所示。 图3 行人数据去掉零点后的频谱图 这时可以观察到信号的一些特征,从图中发现信号的频谱图是基本对称分布的,而且信号的峰值也很大,可以对它首先进行归一化,如下图4所示。 图4 (1)行人数据归一化后的频谱图 (2)取绝对值后的频谱图 同时将自行车和卡车的频谱图来做比较如图5,6所示 图5 (1)自行车数据归一化后的频谱图        (2)取绝对值后的频谱图 图6 (1)卡车数据归一化后的频谱图              (2)取绝对值后的频谱图 从上面三幅图中,可以观察到信号都有明显的峰值,但是出现的位置不同,另外,信号的均值和方差明显不同。但是考虑到雷达所测数据中,会有一些速度反常规的游离数据,所以考虑采用受游离数据影响小的平均绝对值偏差来代替样本方差作为输入特征。同时,以数据的样本中位数来作为输入特征来减少游离数据的影响。根据这些特征进行提取来作为输入。 三.特征提取 1.取信号归一化后的均值作为一个特征量。 2.取信号归一化后的平均绝对值偏差作为一个特征量。 3.取信号归一化后的样本中位数作为一个特征量。 4.由三幅图的比较可以发现,信号的每两点之间的起伏程度也不尽相同,所以可以设定一个特征量,来纪录信号两点间的起伏程度的大小。 5.信号在经过归一化后,可以将信号全部的值加起来,用这个总的值来作为一个特征量。 除了上述的特征,还有很多特征可以提取,但是特征越多,需要的输入神经元越多,依照隐层神经元约为输入神经元的两倍的原则,隐层的神经元也将越多。则网络训练的时间将花费很大。所以,本实验只提取了上述特征中的1,2,3。 四.算法与实现 根据提取的特征的维数,来决定输入神经元的个数。因为提取的三个特征的维数分别为8,1和1,所以输入神经元的个数为10。输出神经元的个数定为3个,考虑到被识别的三种信号分别对应三个输出,虽然用两个神经元就可以表示三种输出状态,但是用三个神经元能更好地分辨,减少出错的概率。至于隐层的神经元个数则按照约为输入神经元个数的两倍的原则,设为20个。当然还可以在调试过程中根据输出的识别率来找到一个一个较为合适的个数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值