卷积神经网络基础概念理解(一)

卷积神经网络(CNN)概念理解

  • 一 图片知识
    • 1.通道
    • 2.位深
    • 3.图像深度
    • 4.像素深度
    • 5.举例说明
  • 二 卷积概念
    • 1.卷积核(Kerner)
    • 2.过滤器(Filter)
    • 3.通道(Channels)
    • 4.感受野
    • 5.填充(Padding)
    • 6.步长(Stride)
    • 7.空洞卷积
    • 8.卷积计算(Convolution)
    • 9.池化
    • 10.全连接(FC)
    • 11.隐藏层
    • 12.梯度
    • 13.激活函数(activation function)
  • 三 Python Demo 演示
    • 1.图片通道观察
    • 2.卷积计算
  • 四 Java 卷积形式遍历二维数组Demo

一 图片知识

一般,卷积处理都是针对图片来使用,那我们就需要先了解一下图片的数据概念;
图片分解最小属性是像素,每个像素点应包含颜色、亮度、位置等信息

1.通道

我们看到的图像都是由三原色混合而成的,其中颜料三原色为红、黄、蓝;光学三原色为红、绿、蓝;
通常电子图片的每个像素颜色由 R、G、B 三个维度描述,则我们可以称这样的图片有三通道

2.位深

图像每个通道颜色数用多少位二进制数表示,就是位深

3.图像深度

图像每个像素颜色数用多少位二进制数表示,就是图像深度

4.像素深度

图像每个像素全部信息用多少位二进制数表示,就是像素深度,值大于等于图像深度

5.举例说明

图片格式 RGBA 不考虑其他信息
通道数 4 R、G、B、A(透明度/灰度/亮度)
位深 8 2^8 0-255 共256种度量级
图像位深 24 2^8 * 2^8 * 2^8 = 2^24 = 16,777,216 (颜色量级)
像素深度 32 2^24 * 2^8 = 16,777,216 * 256 (颜色量级 + 透明度)

二 卷积概念

1.卷积核(Kerner)

一个[m,n]维度的矩阵,用于对输入图片进行卷积计算;卷积核初始值随机生成,并在学习过程中调整,
直至当前卷积核参数,使指定输入经过计算后能输出预期值

卷积神经网络基础概念理解(一)_第1张图片

2.过滤器(Filter)

多个Kerner叠加即为过滤器,一说卷积核就是过滤器,这个看个人理解即可。

卷积神经网络基础概念理解(一)_第2张图片

3.通道(Channels)

即体现一个事物特征所涉及的数据维度
单通道:比如黑白图片,可以通过一位数字来表示,0 或 1(0-255 可以调整深浅)
三通道:比如普通彩色图片,每个像素需要三个值来描述,	R(0-255)、G(0-255)、B(0-255),
       分别提取R、G、B数据,则可以获取图片在这三个维度的一个输入数据
四通道:R、G、B、A;A 即 Alpha , 透明度
其他通道:比如描述声音信息,我们可以采集响度、频率、音色等,作为不同维度的输入;如果我们使用大量猫、狗、海豚、大象的声音,
         仅采集频率信息(即单通道)进行卷积计算并修正参数,最终就可以得到一个利用声音频率值分辨上述四种动物的网络

4.感受野

即卷积计算过程中,每一层卷积计算所输出的特征图(feature map)映射回输入图,对应的输入图大小;
卷积层数越多,最终输出的特征图所能映射的最初输入,差距越大,包含信息越多,
下面介绍到卷积计算时,继续深入理解这个概念。
遗留问题:感受野有什么作用?

5.填充(Padding)

填充,即在卷积计算时将原图按圈数补充像素点,一般值为 0 ,每填充 1 圈,输入图尺寸长、宽分别增加 2
遗留问题:为什么要填充原图

6.步长(Stride)

步长即在卷积计算时,卷积核每计算一次,向右或向下移动的距离(假设从输入图左上角开始做卷积)

7.空洞卷积

即将卷积核做膨胀处理,在卷积核各个相邻元素点之间填充 (dilation_rate - 1)个 0
此处仅做简单概念说明,详细原理和作用请查阅资料

卷积神经网络基础概念理解(一)_第3张图片

8.卷积计算(Convolution)

卷积神经网络基础概念理解(一)_第4张图片

如图,当输入尺寸为 5x5 ,卷积核尺寸为 3x3 时,从输入图左上角开始卷积计算:
1.取绿色区域各个对应位置值相乘,
2.再将所得积相加得最终和,即输出值,如橙色图左上角
=> 0x0 + 2x0 + 1x1 + 3x1 + 1x1 + 2x0 + 3x0 + 0x0 + 0x0 = 5 
3.输出特征图尺寸计算公式理解:以横向计算为例,则设图长为 length,填充 padd 圈,则填充后长为
  (length + 2 * padd) 卷积核横向尺寸为 f,则横向前进距离为 length + 2 * padd - f
  如果步长为 s 则所能走过的步数为 (length + 2 * padd - f)/2,每走一步能输出一个点,再加上自身原始的 1 即为输出特征尺寸

演示视频1:

卷积计算过程 5x5 <-> 3x3

演示视频2:

卷积计算 size=5 p=2 f=3 s=2

 由卷积计算过程可以发现,边缘的点仅参与了一次计算,非边缘点可能参与多次计算,如果平衡这种现象让
 所有点均衡计算,就是通过填充 Padding 实现的
 同时每经过一个完整卷积计算,输出特征图尺寸比输入图尺寸有所缩减(不考虑填充),会最终归一为一个
 值,这样相对来说,卷积核越小,对于同一张输入图(感受野一致),归一需要的全卷积计算次数越多,即
 网络越深,网络深度越深感受野越大性能越好

9.池化

上面我们了解了基本的卷积计算过程,将各个点对应乘积再相加,池化就是
选取上面例子中 9 个乘积结果最大值作为输出点的值,又称为:最大池化

10.全连接(FC)

全连接层(fully connected layers)一般位于神经网络的末尾,用于将池化结果归一化,即对不同维度的特征值
再加权融合,使之包含一个事物的全部特征,这样就能避免出现"管中窥豹"的问题,因此全连接层在神经网络
内起着"分类器"的所用

11.隐藏层

在神经网络中,除了输入层、输出层,输入到数输出中间,这些用户看不到的处理层,即为隐藏层

12.梯度

对于神经网络来说,经过一次卷积计算,并不能产生什么实际效果,要经过多层处理后,才能输出有效的特
征值,但是因为卷积核值固定,多次计算相当于线性处理,这样就可能产生层与层计算结果相近的情况,也
即梯度下降缓慢,甚至消失,则此时模型训练效率低、结果不可靠

13.激活函数(activation function)

首先,我们要处理的数据绝大部分都是非线性的,而在一般的神经网络中,通过卷积计算,即对每个像素点
进行加权处理,这个操作显然是线性的;这样为了避免梯度下降缓慢甚至消失的问题,我们引入了非线性因
素解决线性模型所不能解决的问题,也即激活函数,用于隐藏层的分层、提高训练效率和可靠性

三 Python Demo 演示

1.图片通道观察

opencv 通道顺序为 BGR 此为历史遗留问题,曾经 BGR 为普遍说法,如今 RGB 更流行,使用中注意即可
import cv2
import numpy as np
from PIL import Image

## 1读取图片
original_img=cv2.imdecode(np.fromfile('1.png',dtype=np.uint8), 1)

## 取出原图的宽高
width=original_img.shape[1]
height=original_img.shape[0]
# print(width)
# print(height)

## 定义画布
out_img=np.zeros((height * 2,width * 4,3), np.uint8)
# print(out_img.shape[1])
# print(out_img.shape[0])

## 定义水印样式
font=cv2.FONT_HERSHEY_COMPLEX
font_col=(255,255,255)
font_position=(10,40)
font_size=1.0
font_weight=2

## 填充原图
out_img[0:height,0:width]=original_img

## 灰度化:归一到 R、G、B 的某个通道, 或者按权重取值归一, 权重和为1,如(0.298839R' + 0.586811G'+ 0.114350B')
gray_img=cv2.cvtColor(original_img,cv2.COLOR_RGB2GRAY)

## 填充灰度图,需要融合一下, gray_img 输出为单通道
grey_tmp_img=cv2.merge([gray_img,gray_img,gray_img])
cv2.putText(grey_tmp_img,'Grey',font_position,font,font_size,font_col,font_weight)
out_img[0:height,width:width * 2]=grey_tmp_img



## 取出通道
channel_arr=cv2.split(original_img)
b_img=channel_arr[0]
g_img=channel_arr[1]
r_img=channel_arr[2]

## 定义空集合,用于通道合并
zero_img=np.zeros(original_img.shape[:2],dtype="uint8")

## 取单通道数据 B
b_tmp_img=cv2.merge([b_img,zero_img,zero_img])
cv2.putText(b_tmp_img,'B',font_position,font,font_size,font_col,font_weight)
out_img[0:height,width * 2:width * 3]=b_tmp_img

## 取单通道数据 G
g_tmp_img=cv2.merge([zero_img,g_img,zero_img])
cv2.putText(g_tmp_img,'G',font_position,font,font_size,font_col,font_weight)
out_img[0:height,width * 3:width * 4]=g_tmp_img

## 取单通道数据 R
r_tmp_img=cv2.merge([zero_img,zero_img,r_img])
cv2.putText(r_tmp_img,'R',font_position,font,font_size,font_col,font_weight)
out_img[height:height * 2,width * 0:width * 1]=r_tmp_img

## 取双通道数据 B + G
b_g_tmp_img=cv2.merge([b_img,g_img,zero_img])
cv2.putText(b_g_tmp_img,'B + G',font_position,font,font_size,font_col,font_weight)
out_img[height:height * 2,width * 1:width * 2]=b_g_tmp_img

## 取双通道数据 B + R
b_r_tmp_img=cv2.merge([b_img,zero_img,r_img])
cv2.putText(b_r_tmp_img,'B + R',font_position,font,font_size,font_col,font_weight)
out_img[height:height * 2,width * 2:width * 3]=b_r_tmp_img

## 取双通道数据 G + R
g_r_tmp_img=cv2.merge([zero_img,g_img,r_img])
cv2.putText(g_r_tmp_img,'G + R',font_position,font,font_size,font_col,font_weight)
out_img[height:height * 2,width * 3:width * 4]=g_r_tmp_img

## 为原图添加水印
cv2.putText(out_img,'Original',font_position,font,font_size,font_col,font_weight)

# cv2.imshow('Show Image',out_img)
# cv2.waitKey(0)

cv2.imwrite('save/save.png',out_img)

效果图,分别为原图、灰度图、归一为通道B、归一为通道G、归一为通道R、归一为通道BG、归一为通道BR、归一为通道GR

2.卷积计算

import cv2
import numpy as np

## 加载图片
original_img=cv2.imdecode(np.fromfile('1.png',dtype=np.uint8), 1)

## 卷积核定义
kernel=np.array([[0,-1,0],[-1,5,-1],[0,-1,0]])

## 通道分离
channel_arr=cv2.split(original_img)
b_img=channel_arr[0]
g_img=channel_arr[1]
r_img=channel_arr[2]

## 打印结构
print(original_img.shape)
print(kernel.shape)
print(b_img.shape)
print(g_img.shape)
print(r_img.shape)

## 取尺寸
img_size = original_img.shape[0]
img_padding = 0
kernel_size = kernel.shape[0]
kernel_strid = 1
out_size = int((img_size + 2*img_padding - kernel_size)/kernel_strid + 1)

print("img_size:",img_size)
print("kernel_size:",kernel_size)
print("out_size:",out_size)

## 定义输出结果
b_out=np.zeros((out_size,out_size),dtype="uint8")
g_out=np.zeros((out_size,out_size),dtype="uint8")
r_out=np.zeros((out_size,out_size),dtype="uint8")

## 分别计算卷积
data_length=img_size - kernel_size + 1
for x in range(data_length):
    for y in range(data_length):
        # temp = ""
        m=0
        b_sum=0
        g_sum=0
        r_sum=0
        for i in range(x,kernel_size+x):
            n=0
            for j in range(y,kernel_size+y):
                b_sum += kernel[m][n] * b_img[i][j]
                g_sum += kernel[m][n] * g_img[i][j]
                r_sum += kernel[m][n] * r_img[i][j]
                n=n+1
                # temp += str(b_img[i][j]) + " "
                # print("m:",m," n:",n," kernel:",kernel[m][n]," b_img:",b_img[i][j])
            m=m+1
            # temp += "\n"
        b_out[x][y] = b_sum
        g_out[x][y] = g_sum
        r_out[x][y] = r_sum
        # print(temp)
        # print("------------------")
        
## 合并通道:正常卷积一次会将b,g,r三通道对应位置数据求和,输出单通道结果
out_img=cv2.merge([b_out,g_out,r_out])
# cv2.imshow('Out Image',out_img)

## 卷积输出
kernel_out=np.zeros((out_size,out_size),dtype="uint8")
for x in range(out_size):
    for y in range(out_size):
        kernel_out[x][y] = b_out[x][y] + g_out[x][y] + r_out[x][y]
        
out_img_kernel=cv2.merge([kernel_out,kernel_out,kernel_out])
# cv2.imshow('Out Kernel Image',out_img_kernel)

## CV2 卷积结果
result_img=cv2.filter2D(original_img,-1,kernel)

cv2.imshow('Orignal Image',np.hstack((original_img,result_img)))
cv2.imshow('Result Image',np.hstack((out_img,out_img_kernel)))
cv2.waitKey(0)

# cv2.imwrite('save/result_img.png',result_img)
# cv2.imwrite('save/out_img.png',out_img)
# cv2.imwrite('save/out_img_kernel.png',out_img_kernel)

原图(左)和 Opencv filter2D(右)

卷积核:[[0,-1,0],[-1,5,-1],[0,-1,0]]

卷积神经网络基础概念理解(一)_第5张图片

手写卷积通道合并(左)和手写卷积 R、G、B 融合归一


原图尺寸为 400x400 卷积核为 3x3 手写卷积计算输出图为 398x398 Opencv.filter2D 输出为 400x400

四 Java 卷积形式遍历二维数组Demo

package convolution;

import java.util.Random;

/**
 * @author 
 * @date 2022-10-24 11:01
 * @since 1.8
 */
public class Test {

    private static int[][] arr = new int[5][5];

    public static void main(String[] args) {

        Random random = new Random();

        int pic_size = 5;

        for (int i = 0;i < pic_size;i++){
            for (int j = 0;j < pic_size;j++){
                arr[i][j] = random.nextInt(pic_size);
            }
        }

        int con_size = 3;

        StringBuilder builder = new StringBuilder();

        for (int i = 0;i < (pic_size - con_size) + 1 ; i++){
            for (int j = 0;j< (pic_size - con_size) + 1 ;j++){
                builder.setLength(0);
                for (int m = i; m < con_size + i;m++){
                    for (int n = j ; n < con_size + j;n++){
                        builder.append(arr[m][n]).append(" ");
                    }
                    builder.append("\n");
                }
                System.out.println(builder.append("----------------------------"));
            }
        }
    }
}

你可能感兴趣的:(机器学习与图像识别,cnn,深度学习,卷积,感受野,激活函数)