一般,卷积处理都是针对图片来使用,那我们就需要先了解一下图片的数据概念;
图片分解最小属性是像素,每个像素点应包含颜色、亮度、位置等信息
我们看到的图像都是由三原色混合而成的,其中颜料三原色为红、黄、蓝;光学三原色为红、绿、蓝;
通常电子图片的每个像素颜色由 R、G、B 三个维度描述,则我们可以称这样的图片有三通道
图像每个通道颜色数用多少位二进制数表示,就是位深
图像每个像素颜色数用多少位二进制数表示,就是图像深度
图像每个像素全部信息用多少位二进制数表示,就是像素深度,值大于等于图像深度
图片格式 | 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 (颜色量级 + 透明度) |
一个[m,n]维度的矩阵,用于对输入图片进行卷积计算;卷积核初始值随机生成,并在学习过程中调整,
直至当前卷积核参数,使指定输入经过计算后能输出预期值
多个Kerner叠加即为过滤器,一说卷积核就是过滤器,这个看个人理解即可。
即体现一个事物特征所涉及的数据维度
单通道:比如黑白图片,可以通过一位数字来表示,0 或 1(0-255 可以调整深浅)
三通道:比如普通彩色图片,每个像素需要三个值来描述, R(0-255)、G(0-255)、B(0-255),
分别提取R、G、B数据,则可以获取图片在这三个维度的一个输入数据
四通道:R、G、B、A;A 即 Alpha , 透明度
其他通道:比如描述声音信息,我们可以采集响度、频率、音色等,作为不同维度的输入;如果我们使用大量猫、狗、海豚、大象的声音,
仅采集频率信息(即单通道)进行卷积计算并修正参数,最终就可以得到一个利用声音频率值分辨上述四种动物的网络
即卷积计算过程中,每一层卷积计算所输出的特征图(feature map)映射回输入图,对应的输入图大小;
卷积层数越多,最终输出的特征图所能映射的最初输入,差距越大,包含信息越多,
下面介绍到卷积计算时,继续深入理解这个概念。
遗留问题:感受野有什么作用?
填充,即在卷积计算时将原图按圈数补充像素点,一般值为 0 ,每填充 1 圈,输入图尺寸长、宽分别增加 2
遗留问题:为什么要填充原图
步长即在卷积计算时,卷积核每计算一次,向右或向下移动的距离(假设从输入图左上角开始做卷积)
即将卷积核做膨胀处理,在卷积核各个相邻元素点之间填充 (dilation_rate - 1)个 0
此处仅做简单概念说明,详细原理和作用请查阅资料
如图,当输入尺寸为 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 个乘积结果最大值作为输出点的值,又称为:最大池化
全连接层(fully connected layers)一般位于神经网络的末尾,用于将池化结果归一化,即对不同维度的特征值
再加权融合,使之包含一个事物的全部特征,这样就能避免出现"管中窥豹"的问题,因此全连接层在神经网络
内起着"分类器"的所用
在神经网络中,除了输入层、输出层,输入到数输出中间,这些用户看不到的处理层,即为隐藏层
对于神经网络来说,经过一次卷积计算,并不能产生什么实际效果,要经过多层处理后,才能输出有效的特
征值,但是因为卷积核值固定,多次计算相当于线性处理,这样就可能产生层与层计算结果相近的情况,也
即梯度下降缓慢,甚至消失,则此时模型训练效率低、结果不可靠
首先,我们要处理的数据绝大部分都是非线性的,而在一般的神经网络中,通过卷积计算,即对每个像素点
进行加权处理,这个操作显然是线性的;这样为了避免梯度下降缓慢甚至消失的问题,我们引入了非线性因
素解决线性模型所不能解决的问题,也即激活函数,用于隐藏层的分层、提高训练效率和可靠性
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
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]]
手写卷积通道合并(左)和手写卷积 R、G、B 融合归一
原图尺寸为 400x400 卷积核为 3x3 手写卷积计算输出图为 398x398 Opencv.filter2D 输出为 400x400
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("----------------------------"));
}
}
}
}