本文用到了卷积的内容,如有了解较少的同学建议转到java图像处理(卷积,强调边缘,平滑与高斯模糊)先了解一下
1、边缘
图像边缘是图像最基本的特征。
所谓边缘(Edge) 是指图像局部特性的不连续性。灰度或结构等信息的突变处称之为边缘。例如,灰度级的突变、颜色的突变,、纹理结构的突变等。
边缘是一个区域的结束,也是另一个区域的开始,利用该特征可以分割图像。
2、导数
图像的边缘有方向和幅度两种属性。
边缘通常可以通过一阶导数或二阶导数检测得到。
一阶导数是以最大值作为对应的边缘的位置,而二阶导数则以过零点作为对应边缘的位置。
3、梯度
为了达到寻找边缘的目的,检测灰度变化可用一阶导数或二阶导数来完成。下面将讨论一阶导数。
为了在一幅图像f 的(x,y)位置处寻找边缘的强度和方向,所以选择的工具就是梯度,梯度用▽f来表示,并用向量来定义,定义如下所示:
其中,梯度▽f 为一个向量,它表示f 在位置(x,y) 处的最大变化率的方向,计算方法为
对于以为函数f(x) 在点 x 处的导数的近似:将函数f(x+△ x) 展开为 x 的泰勒级数,令△ x=1,且只保该级数的线性项,则函数f(x) 的梯度▽f 计算为:
由上面的数学推导可知,要得到一幅图像的梯度,则要求在图像的每个像素点位置处计算偏导数。我们处理的是数字量,因此要求关于一点的邻域上的偏导数的数字近似,因此一幅图像f,在(x,y)位置处的x和y方向上的梯度大小分别计算为:
矩阵表示为
用于计算梯度偏导数的滤波器模板,通常称之为梯度算子、边缘算子和边缘检测子等。
我们可以通过泰勒级数的展开找到可以表示成卷积的可能性
(借鉴于此链接)
(我爱康娜)
前三个算子都是水平垂直方向进行的边缘检测,使用以下两个卷积核对同一点进行操作,然后取梯度值作为测试灰度值,再设置一个阈值,根据阈值判断是否是边界点(图中的max值)
Krisch算子和Robinson算子都是八个方向进行的边缘检测,使用八个卷积核对同一点进行操作,每个卷积核代表不同的方向,然后取最大值作为测试灰度值
这里只以Krisch算子作为实例
Laplace算子是在卷积核上直接实现了四个方向或八个方向的边缘检测,即一个卷积核代表八个方向的检测值
在这里我们只实现八个方向的拉普拉斯算子的效果
四个方向的算子卷积核:{ { 0 , 1 , 0 } , { 1 , -4 , 1 } , { 0 , 1 , 0 } }
根据结果我们可以看出,Roberts算子的效果还不错,但是阈值的要求较高,而且准确度相对低;Prewitt算子对相邻点的处理不佳;Krisch算子与Laplace算子的噪点相对较高,Sobel算子效果最好。
算子处的代码,请注意注释内容
public void ceshi(String text){
int size = 0;
//根据不同请求调用不同的处理方法
if(text.equals("Roberts边缘检测"))size = 2;
if(text.equals("Sobel边缘检测"))size = 3;
if(text.equals("Prewitt边缘检测"))size = 3;
if(text.equals("Krisch边缘检测"))size = 3;
if(text.equals("Laplace边缘检测"))size = 3;
//设置算子的卷积核
//Roberts算子卷积核
double[][] robertsX = {{1,0},{0,-1}};
double[][] robertsY = {{0,1},{-1,0}};
//Sobel算子卷积核
double[][] sobelX = {{1,0,-1},{2,0,-2},{1,0,-1}};
double[][] sobelY = {{1,2,1},{0,0,0},{-1,-2,-1}};
//Prewitt算子卷积核
double[][] prewittX = {{-1,0,1},{-1,0,1},{-1,0,1}};
double[][] prewittY = {{1,1,1},{0,0,0},{-1,-1,-1}};
//Krisch算子卷积核
double[][] krischN = {{5,5,5},{-3,0,-3},{-3,-3,-3}};
double[][] krischNE = {{-3,5,5},{-3,0,5},{-3,-3,-3}};
double[][] krischE = {{-3,-3,5},{-3,0,5},{-3,-3,5}};
double[][] krischSE = {{-3,-3,-3},{-3,0,5},{-3,5,5}};
double[][] krischS = {{-3,-3,-3},{-3,0,-3},{5,5,5}};
double[][] krischSW = {{-3,-3,-3},{5,0,-3},{5,5,-3}};
double[][] krischW = {{5,-3,-3},{5,0,-3},{5,-3,-3}};
double[][] krischNW = {{5,5,-3},{5,0,-3},{-3,-3,-3}};
//Laplace算子卷积核
double[][] laplace = {{1,1,1},{1,-8,1},{1,1,1}};
//首先将图片灰度化
toGray();
//下面代码中width代表图片宽度,height代表图片高度,gray为图片的灰度数组,存储的是每个像素点的灰度值
//前三个算子请仔细读第一个算子的代码,之后的是一样的
if(text.equals("Roberts边缘检测")){
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
//设置x,y方向的结果变量,一定要在循环内初始化,因为每次循环都要清零重新加
int tempX = 0;
int tempY = 0;
//对size*size区域进行卷积操作
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
tempX += gray[x+i][y+j]*robertsX[i][j];
tempY += gray[x+i][y+j]*robertsY[i][j];
}
}
//求梯度值
int result = (int) Math.sqrt(tempX*tempX+tempY*tempY);
//设置阈值
int RMax = 200;
//根据阈值设置黑白度
if(result > RMax)result = 255;
if(result <= RMax)result = 0;
//设置颜色来描点
Color color = new Color(result,result,result);
graphics.setColor(color);
graphics.drawLine(x+size/2,y+size/2, x+size/2, y+size/2);
}
}
}
if(text.equals("Sobel边缘检测")){
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
int tempX = 0;
int tempY = 0;
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
tempX += gray[x+i][y+j]*sobelX[i][j];
tempY += gray[x+i][y+j]*sobelY[i][j];
}
}
int result = (int) Math.sqrt(tempX*tempX+tempY*tempY);
//设置阈值
int GMax = 200;
if(result > GMax)result = 255;
if(result <= GMax)result = 0;
Color color = new Color(result,result,result);
graphics.setColor(color);
graphics.drawLine(x+size/2,y+size/2, x+size/2, y+size/2);
}
}
}
if(text.equals("Prewitt边缘检测")){
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
int tempX = 0;
int tempY = 0;
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
tempX += gray[x+i][y+j]*prewittX[i][j];
tempY += gray[x+i][y+j]*prewittY[i][j];
}
}
int result = (int) Math.sqrt(tempX*tempX+tempY*tempY);
//设置阈值
int PMax = 200;
if(result > PMax)result = 255;
if(result < PMax)result = 0;
Color color = new Color(result,result,result);
graphics.setColor(color);
graphics.drawLine(x+size/2,y+size/2, x+size/2, y+size/2);
}
}
}
if(text.equals("Krisch边缘检测")){
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
//设置一个数组存储八个方向的值,按顺时针方向从北极开始
int[] temp = {0,0,0,0,0,0,0,0};
//对size*size区域进行卷积操作
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
temp[0] += gray[x+i][y+j]*krischN[i][j];
temp[1] += gray[x+i][y+j]*krischNE[i][j];
temp[2] += gray[x+i][y+j]*krischE[i][j];
temp[3] += gray[x+i][y+j]*krischSE[i][j];
temp[4] += gray[x+i][y+j]*krischS[i][j];
temp[5] += gray[x+i][y+j]*krischSW[i][j];
temp[6] += gray[x+i][y+j]*krischW[i][j];
temp[7] += gray[x+i][y+j]*krischNW[i][j];
}
}
;
//找出八个方向的最大值(代码为数组列表求最大值)
int result = Arrays.stream(temp).max().getAsInt();
//若是求得结果超出灰度值的0-255范围,将其设成最大值或最小值
if(result > 255)result = 255;
if(result < 0)result = 0;
//画图
Color color = new Color(result,result,result);
graphics.setColor(color);
graphics.drawLine(x+size/2,y+size/2, x+size/2, y+size/2);
}
}
}
if(text.equals("Laplace边缘检测")){
for(int x = 0;x < width-size+1;x++){
for(int y = 0;y < height-size+1;y++){
int result = 0;
//对size*size区域进行卷积操作
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
result += gray[x+i][y+j]*laplace[i][j];
}
}
//设置阈值
int LMax = 150;
//根据阈值设置黑白度
if(result > LMax)result = 255;
if(result < LMax)result = 0;
//画图
Color color = new Color(result,result,result);
graphics.setColor(color);
graphics.drawLine(x+size/2,y+size/2, x+size/2, y+size/2);
}
}
}