本次我们来谈一下图像处理的卷积基础(java语言)
多图预警
首先我们来接触一下卷积的基础理论
卷积我们可以看成两个矩阵进行一定规律的计算(这里不是矩阵相乘啥的),其中有一个模板矩阵,我们称之为卷积核
such as this
但是深度学习图像处理中的卷积操作是与数学概率论中的卷积操作有着一定的区别的,数学的卷积需要进行卷积核翻转,但图像处理中大部分情况下是直接的一对一相乘而不翻转,即计算方法有着区别
而卷积操作基本是分成三步:
1、输入(input)
2、卷积计算(也叫做特征提取)
3、输出(output,也叫做特征映射)
首先是输入,图像处理中我们输入的是图像的颜色参数(rgb)
然后我们进行卷积
卷积操作为卷积核与像素点的元素对应相乘
其中蓝色矩阵代表输入的矩阵,深蓝色中的小数字组成的矩阵是卷积核
在图像中是这个样子的
即
通过卷积操作我们可以将两个二维数组变成一个具有特定意义的数(在这里是某块像素区域的R值加权和,权由卷积核决定,这种操作在图像中也叫做线性滤波),然后可以将卷积产生的数字赋给一个位置(通常赋给被处理矩阵的中心位置),在这里我们赋到中心位
将卷积核在整个图像中滑遍,即可将整个图像进行特征提取,如图所示(由下到上)
将整个图像处理完之后,我们会发现一个问题:
图像边缘的像素点是没有处理到的(如图)
会有几种常见的办法处理图像边缘
1、直接舍掉边缘未处理区域,也就是说输出的只有绿色部分,当然这种办法无法输出全部图像,因而很少使用
2、Padding(填充周围):使用一些假像素(通常是0)来填充边缘,从而产生与输入相同大小的输出。 (借鉴数字图像处理中滤波和卷积操作详细说明)这位博主的边缘处理
3、Striding(步幅增大):使用更大的滑动步幅,常见于图像缩小时(池化)的操作,这里不再赘述
(4、不管边缘,就是如果边缘占比足够小而且对输出结果没啥影响时的操作)
通过边缘的处理,我们最终会得到处理后的图像数据,特征提取完成,输出即可完成特征映射(在这篇文章中我将图像显示当成特征映射)
二维卷积(即两个矩阵的操作)常见于灰度图的操作(例如3*3的卷积核与3x3的处理区域),三维卷积由于有三个需要处理的像素区域(即r,g,b,3x3x3),因而常见于处理彩色图像,我们在这里操作的大部分是三维的彩色图像
卷积的部分代码如下(以锐化为例,边缘处理代码暂无)
//对于不同的功能设置不同大小的矩阵
if(text.equals("锐化")){size = 3;}
//锐化卷积核
double[][] sharpening = {{-1,-1,-1},{-1,9,-1},{-1,-1,-1}};
//下面开始搞卷积算法
//初始化rgb数组
R = new int[size][size];
G = new int[size][size];
B = new int[size][size];
//对应3*3的像素格子进行卷积计算
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
//设置三个值分别存储r,g,b的特征值,一定要在循环内部进行初始化0,这样才能每次有不同的值
int resultOfR = 0;
int resultOfG = 0;
int resultOfB = 0;
//将格子的rgb值都取出,便于之后的卷积操作
for(int j = 0;j <size;j++){
for(int i = 0;i < size;i++){
//将该点的RGB信息取出,放到变量中待操作
int argb = rgbOfImg[x][y];
//分段获取其R,G,B信息
//int变量共4位32字节,0位对应透明度(A),1位对应R值,2位对应G值,3位对应B值
//>>操作:将二进制代码向右移动,左边空位根据符号补充,正号为0,负号为1,右边超过范围的全部舍弃
//&:二进制位与运算符,只有两个变量对应值均为1时该位才返回1,0xff表示全为1的十六进制数(11111111),因此任何与0xff进行位与的结果均为其本身
//先移位后取位与可以将不同值对应的位信息取出,位与的意义是只取32字节的后8字节
R[i][j] = argb>>16 & 0xff;
G[i][j] = argb>>8 & 0xff;
B[i][j] = argb & 0xff;
}
}
if(text.equals("锐化")){
//分别对R,G,B进行卷积操作,对应相乘后加起来
for(int j = 0;j < size;j++){
for(int i = 0;i < size;i++){
resultOfR += (int)(sharpening[i][j]*R[i][j]);
}
}
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
resultOfG += (int)(sharpening[i][j]*G[i][j]);
}
}
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
resultOfB += (int)(sharpening[i][j]*B[i][j]);
}
}
}
//如果超过了rgb的界限(0-255),将其按照最大值或最小值处理
if(resultOfR > 255)resultOfR = 255;
if(resultOfR < 0)resultOfR = 0;
if(resultOfG > 255)resultOfG = 255;
if(resultOfG < 0)resultOfG = 0;
if(resultOfB > 255)resultOfB = 255;
if(resultOfB < 0)resultOfB = 0;
//根据该rgb值创建颜色对象
Color color = new Color(resultOfR, resultOfG, resultOfB);
//设置颜色,其中graphics是图像的画布(见BufferedImage类与Graphics类)
graphics.setColor(color);
//画像素点(drawline用来画线,这里的起始与终点都是同一点,因此可以用来画像素点)
//size/2用来将像素点赋到中心元上
graphics.drawLine(x+size/2, y+size/2, x+size/2, y+size/2);
}
}
下面我们来看一些常见的算子或效果的卷积核产生的效果吧
首先说明:卷积核内的数据相加为1时亮度不变,小于1时亮度降低,大于1时亮度升高
锐化
平滑模糊
高斯模糊(在这里先不详述如何生成高斯模板,有兴趣的同学可以看我的文章内高斯模板的相关内容)
这里使用5*5模板,看上去更有效果
浮雕
强调边缘