Python仙术 第5期 卷积、池化、特征向量与文字识别?

卷积神经网络概述

卷积神经网络(CNN, Convolutional Neural Networks),名称来源于其特殊的卷积层结构,可以有效地提取图像的局部特征,进而捕捉到复杂的模式和特征,因此奠定了其在计算机视觉领域的重要地位。

相比于传统的全连接神经网络,卷积神经网络能够非常好地利用图像的特征,并以低效能消耗完成复杂的识别工作。

卷积神经网络一般分为三层结构:

卷积层:卷积神经网络最具有特色的部分,使用不同的卷积核,提取图像的多方特征。
池化层:池化操作的目的是降维,减少参数量,增加性能的同时可以降低模型对局部变化的灵敏度,提升其泛化能力。
全连接层:将提取的特征映射到样本的标签空间,以进行分类或者回归任务。这是模型处理的最后阶段,负责整合和预测最终结果。

由于全连接层的实现较为复杂,本文着重于直观介绍卷积神经网络的特色算法而非训练模型,因此在经历单一样本的卷积和池化操作之后,我们直接通过展平操作获得特征向量。绝对不是懒得写

提取图片的特征向量

数据准备

我准备了三张16*16的图片,分别是字母F和两个字母X
image image image

由于图片经过压缩,这里展示的可能有点像灰度图像,实际上我直接使用了二值图像,没有灰度。如果是灰度图像或者彩色图像,在导入时应当对其进行二值处理。

from PIL import Image
import numpy as np

# 通过Pillow直接导入图片,图片已经是16*16的黑白二值图像了
image = Image.open('datas/X.png')
# convert('1')可将图像转化为二值图像
image = image.convert('1')
# 转为numpy array
array = np.array(image)

这时候打印这个array,你会看到它是一个16*16的二维数组,元素是True和False。不过没有关系,这不影响后续操作。

卷积处理

卷积,是两个函数(信号)合并的一种方式,它可以通过一个函数对另一个函数进行“滤波”,以达到提取特征的目的。

对于此算法而言,我们要操作的信号是二维数组,其中一个信号是我们给定的固定数组,叫做卷积核(滤波器),而另一个信号就是我们从待处理的数组中抽取出的一部分。例如,这是右侧3×3滤波器对原数组的(2,2)坐标为中心的滤波操作。
滤波的基本方法是滤波器(卷积核)与图像局部区域的元素相乘后求和。


使用3×3的滤波器对16×16的数组进行步长为1的滤波操作,滤波器会经历以(2,2),(2,3),…(2,15),(3,2),…(15,15)为中心的样本,并将结果映射到一个新的二维数组中。显然,这个二维数组的尺寸是14×14。

实际上,卷积操作通常会使用多组正交的卷积核,以提高信号捕捉效率。例如,我使用了一组正交的卷积核分别对样本进行卷积处理,生成两张特征图。

from scipy.signal import convolve2d
import numpy as np


# 左下-右上卷积核
# 采用3*3卷积核,卷积核采用(-1, 2)二值
# 如果采用(0,1)那么对比度边缘(快速变化的区域)特征会被忽视
kernel_vy = np.array([[-1, -1, 2],
                      [-1, 2, -1],
                      [2, -1, -1]])
# 左上-右下卷积核
kernel_vx = np.array([[2, -1, -1],
                      [-1, 2, -1],
                      [-1, -1, 2]])
convolved_vx = convolve2d(array, kernel_vx, mode='valid')
convolved_vy = convolve2d(array, kernel_vy, mode='valid')

# 卷积结果
# 由于边缘覆盖问题,16*16的图经过3*3卷积核的采样之后,特征图的大小是14*14
print("左上-右下卷积结果:")
print(convolved_vx)
print("左下-右上卷积结果:")
print(convolved_vy)

很多教学视频喜欢采用(0, 1)二值构造卷积核,但是这样做相当于无视了0值的滤波。我这里采用的是(-1, 2)二值进行滤波。

我们可以获得如下的结果

池化

池化的过程其实相对简单,它的目的是降低维度,同时增加稳定性,它的操作大致是将数组拆分成多个n×n的小块,然后把每个小块分别处理得当新的值。

例如,我们的14×14特征图,以2×2的池化窗口,可以拆出7×7个数组。

一般的操作有最大池化和平均池化。最大池化就是取整个池化窗口里的最大值,平均池化就是取整个池化窗口的平均值。(对,就是这么简单)

from skimage.measure import block_reduce
import numpy as np

# 池化操作
# 在这里我们选择最大池化(在一个池化窗口中选取最大值作为池化结果)
# 14*14大小图片,以(2,2)池化的结果,得到的特征图大小是7*7
pool_size = (2, 2)
pooled_vx = block_reduce(convolved_vx, pool_size, np.max)
pooled_vy = block_reduce(convolved_vy, pool_size, np.max)

print("池化结果")
print(pooled_vx)
print(pooled_vy)

池化操作的结果就是,我们获得了更小的一组特征图

至此,卷积神经网络的特色操作已经完成,我们获得了一组拥有优良泛化能力的向量组。

提取特征向量

提取特征向量的一大方法就是展平和拼接。顾名思义,我们可以把二维的特征图,展开成一维的数组,然后把每个特征图展开得到的数组有序拼接,最后得到一个长数组,作为该图片的特征向量。

具体来说,我这里得到了两个7×7的池化后特征图,因此最后能够获得长度为2×7×7=98的特征向量。

# 将池化后的特征图展平成特征向量,这一操作可以直接使用numpy提供的flatten()方法完成
# 实际上转换为全连接层的输入还有其他方法,这里不训练模型,因此直接连接成一个向量
flattened_vx = pooled_vx.flatten()
flattened_vy = pooled_vy.flatten()
# 将两个方向的向量拼合,可以得到一个长向量,作为该图片的特征向量
# 特征向量的长度应当是2*7*7=98
feature_vector = np.concatenate((flattened_vx, flattened_vy))
print("特征向量:")
print(feature_vector)

效果展示

程序源码

from PIL import Image
from scipy.signal import convolve2d
from skimage.measure import block_reduce
import numpy as np


# 计算特征向量
def f_vector(image_path: str) -> np.ndarray:
    # 通过Pillow直接导入图片,图片已经是16*16的黑白二值图像了
    image = Image.open(image_path)
    # convert('1')可将图像转化为二值图像(仅黑白,舍弃灰度)
    image = image.convert('1')
    # 转为numpy array
    array = np.array(image)

    # 左下-右上卷积核
    # 采用3*3对角卷积核,卷积核采用(-1, 2)二值
    # 如果采用(0,1)那么对比度边缘(快速变化的区域)特征会被忽视
    kernel_vy = np.array([[-1, -1, 2],
                          [-1, 2, -1],
                          [2, -1, -1]])
    # 左上-右下卷积核
    kernel_vx = np.array([[2, -1, -1],
                          [-1, 2, -1],
                          [-1, -1, 2]])
    # 进行卷积操作
    convolved_vx = convolve2d(array, kernel_vx, mode='valid')
    convolved_vy = convolve2d(array, kernel_vy, mode='valid')

    # 池化操作
    # 在这里我们选择最大池化(在一个池化窗口中选取最大值作为池化结果)
    # 14*14大小图片,以(2,2)池化的结果,得到的特征图大小是7*7
    pool_size = (2, 2)
    pooled_vx = block_reduce(convolved_vx, pool_size, np.max)
    pooled_vy = block_reduce(convolved_vy, pool_size, np.max)

    # 将池化后的特征图展平成特征向量,这一操作可以直接使用numpy提供的flatten()方法完成
    # 实际上转换为全连接层的输入还有其他方法,这里不训练模型,因此直接连接成一个向量
    flattened_vx = pooled_vx.flatten()
    flattened_vy = pooled_vy.flatten()
    # 将两个方向的向量拼合,可以得到一个长向量,作为该图片的特征向量
    # 特征向量的长度应当是2*7*7=98
    feature_vector = np.concatenate((flattened_vx, flattened_vy))
    return feature_vector


# 比较相似度,使用余弦值
def cos(x: np.ndarray, y: np.ndarray) -> float:
    dot_product = np.dot(x, y)
    norm_x = np.linalg.norm(x)
    norm_y = np.linalg.norm(y)
    return dot_product / (norm_x * norm_y)


if __name__ == "__main__":
    sample_image = 'datas/X.png'
    sample_f_vector = f_vector(sample_image)
    x_image = 'datas/X_2.png'
    x_f_vector = f_vector(x_image)
    f_image = 'datas/F.png'
    f_f_vector = f_vector(f_image)

    print(f"相似度:X字符模板和X字符测试样本:{cos(sample_f_vector, x_f_vector):.4f}")
    print(f"相似度:X字符模板和F字符测试样本:{cos(sample_f_vector, f_f_vector):.4f}")

直接应用的结果已经很不错了

30 Likes

你到底有没有在学化学啊

9 Likes

开始上强度了

24 Likes

研究方向:计算机视觉点云
前来报到! :face_with_hand_over_mouth:

3 Likes


可能有吧…?

4 Likes

这个是真能学到东西

3 Likes

我擦,开始看不懂了

2 Likes

主要是这东西太抽象了一点,文字不是特别好描述,尤其是滤波操作那块,有动画演示好一些

3 Likes

学到了,催更下一期 :partying_face:

3 Likes

歇菜了,最近要搞毕业论文

还有就是那个

您好,看过你的简历,暂不符合岗位要求,感谢关注。

4 Likes

hhhhh,那还是论文优先

2 Likes

最近正好在学习,感谢

1 Like

已会

1 Like

炼丹也是化学

6 Likes

图解动画推荐三蓝一棕:
https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://m.youtube.com/watch%3Fv%3DKuXjwB4LzSA&ved=2ahUKEwicza2gybWFAxVmklYBHa8EB3gQwqsBegQIEhAG&usg=AOvVaw0-__O2L06fggum_BpfCicI

5 Likes

咦怎么就第5期了,前几期呢

6 Likes

卷王啊

1 Like

确定是第5期,而不是第50期吗?

错字纠正:得当->得到

不禁令我再次翻开很久之前的笔记



1 Like