图像边缘检测 Canny边缘检测
底下有详细代码
一、介绍
1、图像检测的原理。
图像检测的原理是检测相邻的几个点像素值之间的变化率,相对于对函数求导。求点P(x,y)的变换率,可以在点P周围选取一些点,求x方向的距离Gx,再求y方向上的距离Gy。最后变换率G等于Gx平方加上Gy平方的和的平方差,即G=Math.sqrt(Gx^2+Gy^2)。
2、Canny算子。
Canny算子对噪声不敏感。Canny边缘检测算子是John F. Canny于1986年开发出来的一个多级边缘检测算法。更为重要的是Canny创立了“边缘检测计算理论”(computational theory of edge detection)解释这项技术如何工作。
Canny的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:好的检测- 算法能够尽可能多地标识出图像中的实际边缘。好的定位- 标识出的边缘要与实际图像中的实际边缘尽可能接近。最小响应- 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。为了满足这些要求Canny使用了变分法,这是一种寻找满足特定功能的函数的方法。最优检测使用四个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数。
3、步骤。
(1)高斯过滤。
(2)Sobel边缘检测(其他的边缘检测方法也可以)。
(3)非极大值抑制。
(4)双阈值检测。
二、高斯过滤
1、介绍。
任何边缘检测算法都不可能在未经处理的原始数据上很好地处理,所以第一步是对原始数据与高斯平滑模板作卷积,得到的图像与原始图像相比有些轻微的模糊。这样,单独的一个像素噪声在经过高斯平滑的图像上变得几乎没有影响。
2、核心代码。
可以参考文章:图像处理 高斯滤波(带权值的均值滤波)。
3、结果。而我在canny中,选择的高斯过滤模版是:长度为3,方差sigma为1.5的。即第一张图:高斯滤波_3_2sigma.jpg。
三、Sobel边缘检测
1、介绍。
将经过高斯过滤之后的图像,即上面的“高斯滤波_3_2sigma.jpg”图像进行边缘检测,检测方法使用Sobel算子。可以参考文章:图像边缘检测 Sobel边缘检测。
2、核心代码。
可以参考文章:图像边缘检测 Sobel边缘检测。
3、结果。生成图:Lena-canny-sobel.jpg。
四、非极大值抑制
1、介绍。
(1)、获得角度图。
Sobel算子模版。Gx=f(x+1,y-1)+2f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+2f(x-1,y)+f(x-1,y+1))
Gy=f(x-1,y+1)+2f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+2f(x,y-1)+f(x+1,y-1))
Gx=[-1 0 1] , Gy=[-1 -2 -1]
[-2 0 2] [ 0 0 0]
[-1 0 1] [ 1 2 1]
边缘检测的像素图G=Math.sqrt(Gx^2+Gy^2),其实就是 “Lena-canny-sobel.jpg”。而角度图angle=Gy/Gx。
(2)、获取角度图中点(x,y)的方向,可以根据值f(x,y)求导。
角度图中的值为 -Math.Pi 到 Math.Pi 之间,差不多为 -3.14 到 3.14 之间。将其16等分。
方向1:f(x-1,y)-->f(x,y)-->f(x+1,y) (15/16)Pi-->(1/16)Pi、(7/16)Pi-->(9/16)Pi
方向2:f(x-1,y-1)-->f(x,y)-->f(x+1,y+1) (1/16)Pi-->(3/16)Pi、(9/16)Pi-->(11/16)Pi
方向3:f(x,y-1)-->f(x,y)-->f(x,y+1) (3/5)Pi-->(5/16)Pi、(11/16)Pi-->(13/16)Pi
方向4:f(x-1,y+1)-->f(x,y)-->f(x+1,y-1) (5/7)Pi-->(7/16)Pi、(13/16)Pi-->(15/16)Pi
(3)、非极大值抑制。
在角度图angle中,假如点 P(x,y) 的值是在 (15/16)Pi-->(1/16)Pi 或者 (7/16)Pi-->(9/16)Pi 的范围中,那么点 P(x,y) 的方向就是方向1。那么在其方向上并与之相邻的两个点就是 P1(x-1,y) 和 P2(x+1,y) , 如果点 P 小于 P1 或者 P2 ,那么就将像素图G中点P值为0。
2、核心代码。
private static final double pi16 = Math.PI / 16;
private static final double[] piArray = new double[]{
0, pi16 * 1, 2, pi16 * 3, 4, pi16 * 5, 6, pi16 * 7, 8,
pi16 * 9, 10, pi16 * 11, 12, pi16 * 13, 14, pi16 * 15, 16};
/**
* 非极大值抑制
* 判断沿着梯度方向的3个像素点(中心像素点与另外两个梯度方向区域内的像素点),
* 若中心像素点的梯度值不比其他两个像素点的梯度值大,则将该像素点的灰度值置0。
* 方向1:f(x-1,y)-->f(x,y)-->f(x+1,y) (15/16)Pi-->(1/16)Pi、(7/16)Pi-->(9/16)Pi
* 方向2:f(x-1,y-1)-->f(x,y)-->f(x+1,y+1) (1/16)Pi-->(3/16)Pi、(9/16)Pi-->(11/16)Pi
* 方向3:f(x,y-1)-->f(x,y)-->f(x,y+1) (3/5)Pi-->(5/16)Pi、(11/16)Pi-->(13/16)Pi
* 方向4:f(x-1,y+1)-->f(x,y)-->f(x+1,y-1) (5/7)Pi-->(7/16)Pi、(13/16)Pi-->(15/16)Pi
*
* @param rangeArray 高斯过滤图
* @param angleArray 角度图
*/
private static int[][] getDirection(int[][] rangeArray, double[][] angleArray) {
int row = rangeArray.length;
int column = rangeArray[0].length;
int[][] array = new int[row][column];
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {
for (int j = 1; j < column - 1; j++) {
array[i][j] = rangeArray[i][j];
double angle = angleArray[i][j];
if ((angle > piArray[15] && angle < piArray[1]) || (angle > piArray[7] && angle < piArray[9])) {
//方向1:f(x-1,y)-->f(x,y)-->f(x+1,y) (15/16)Pi-->(1/16)Pi、(7/16)Pi-->(9/16)Pi
if (rangeArray[i][j] < rangeArray[i][j - 1] || rangeArray[i][j] < rangeArray[i][j + 1])
array[i][j] = 0;
} else if ((angle > piArray[1] && angle < piArray[3]) || (angle > piArray[9] && angle < piArray[11])) {
//方向2:f(x-1,y-1)-->f(x,y)-->f(x+1,y+1) (1/16)Pi-->(3/16)Pi、(9/16)Pi-->(11/16)Pi
if (rangeArray[i][j] < rangeArray[i - 1][j - 1] || rangeArray[i][j] < rangeArray[i + 1][j + 1])
array[i][j] = 0;
} else if ((angle > piArray[3] && angle < piArray[5]) || (angle > piArray[11] && angle < piArray[13])) {
//方向3:f(x,y-1)-->f(x,y)-->f(x,y+1) (3/5)Pi-->(5/16)Pi、(11/16)Pi-->(13/16)Pi
if (rangeArray[i][j] < rangeArray[i - 1][j] || rangeArray[i][j] < rangeArray[i + 1][j])
array[i][j] = 0;
} else {
//方向4:f(x-1,y+1)-->f(x,y)-->f(x+1,y-1) (5/7)Pi-->(7/16)Pi、(13/16)Pi-->(15/16)Pi
if (rangeArray[i][j] < rangeArray[i + 1][j - 1] || rangeArray[i][j] < rangeArray[i - 1][j + 1])
array[i][j] = 0;
}
}
}
return array;
}
3、结果。明显比非极大值抑制前的效果好多了。生成图:Lena-canny-非极大值抑制.jpg。
五、双阈值检测
1、介绍。
将图 “Lena-canny-非极大值抑制.jpg” 进行二值化处理,我们生成两张二值化图片,阈值分别为32和96。阈值为32的先称为低阈值图或者弱边缘图,阈值为96的称为高阈值图或者强边缘图。我们以高阈值图为主,低阈值图为辅,有选择性的从低阈值挑选值补充到高阈值图中。
方法1:以高阈值图的点为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。这个理解起来比较简单。
方法2:以高阈值图中边缘的端点(端点指一个点少于两个与之相邻的点)为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。这个理解起来比较难。
2、核心代码。
/**
* 双阈值检测--以高阈值图的点为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。
*
* @param resultArray 结果图片像素数组
* @param lowBinaryArray 低阈值图片像素数组
* @param i 第几行
* @param j 第几列
* @param row 总行数
* @param column 总列数
*/
private static void thresholdDetectionV1(int[][] resultArray, int[][] lowBinaryArray, int i, int j, int row, int column) {
if (i < 0 || i >= row || j < 0 || j >= column) {
return;
}
if (resultArray[i][j] != 0) {//已经被扫描过了
return;
} else {//resultArray填充
resultArray[i][j] = lowBinaryArray[i][j];
}
//扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
if (lowBinaryArray[iAround][jAround] != 0) {
thresholdDetectionV1(resultArray, lowBinaryArray, iAround, jAround, row, column);
}
}
}
}
/**
* 双阈值检测--以高阈值图中边缘的端点(端点指一个点少于两个与之相邻的点)为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。
*
* @param resultArray 结果图片像素数组
* @param lowBinaryArray 低阈值图片像素数组
* @param highBinaryArray 高阈值图片像素数组
* @param i 第几行
* @param j 第几列
* @param row 总行数
* @param column 总列数
*/
private static void thresholdDetectionV2(int[][] resultArray, int[][] lowBinaryArray, int[][] highBinaryArray, int i, int j, int row, int column) {
if (i < 0 || i >= row || j < 0 || j >= column) {
return;
}
if (resultArray[i][j] != 0) {//已经被扫描过了
return;
} else { //resultArray填充
resultArray[i][j] = highBinaryArray[i][j];
}
//高阈值图中扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
int aroundCount = 0;//有几个连接点
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
//判断是否为强边缘
if (highBinaryArray[iAround][jAround] != 0) {
aroundCount++;
thresholdDetectionV2(resultArray, lowBinaryArray, highBinaryArray, iAround, jAround, row, column);
}
}
}
//判断是边缘的端点。如果不是端点的话,至少应该有两个连接点。
if (aroundCount < 2) {
//低阈值图中扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
//判断是否为弱边缘
if (lowBinaryArray[iAround][jAround] != 0) {
highBinaryArray[iAround][jAround] = lowBinaryArray[iAround][jAround];//添加高阈值图的强边缘
thresholdDetectionV2(resultArray, lowBinaryArray, highBinaryArray, iAround, jAround, row, column);
}
}
}
}
}
3、结果。以方法2的图片 “Lena-canny-v2.jpg” 为最终的测试结果,方法1的图片仅供参考。
(1)、方法1。生成图:Lena-canny-v1.jpg。
(2)、方法2。生成图:Lena-canny-v2.jpg。
六、所有图片对比
七、详细代码
这里仅提供主流程代码 EdegDetectionTest.java 和 EdegDetectionUtils.java 。其他详细代码可以查看文章图像边缘检测 Laplacian边缘检测的详细代码,因为一样的,就不重复写出了。
1、主流程代码 EdegDetectionTest.java。
package com.zxj.reptile.test.image.edge;
import com.zxj.reptile.utils.image.EdgeDetectionUtils;
import com.zxj.reptile.utils.image.ImageService;
import com.zxj.reptile.utils.image.ImageUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
/**
* 图像边缘检测
*/
public class EdgeDetectionTest {
public static void main(String[] args) {
cannyTest();
}
private static void cannyTest() {
String path = "G:\\xiaojie-java-test\\img\\边缘检测\\";
String sourcePath = path + "Lena灰度图.jpg";
String targetPath = path + "Lena-canny.jpg";
canny(sourcePath, targetPath);
}
private static void canny(String sourcePath, String targetPath) {
try {
//获取原图像对象,并获取原图像的二维数组
BufferedImage image = ImageIO.read(new File(sourcePath));
int[][] imgArrays = ImageUtils.getImageGray(image);
//生成新图像的二维数组
int[][] newImgArrays = EdgeDetectionUtils.canny(imgArrays, 3, 0.5, 32, 128);
//生成新图片对象,填充像素
BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
ImageUtils.setImageRgbByByte(newImage, newImgArrays);
//生成图片文件
ImageIO.write(newImage, "JPEG", new File(targetPath));
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
2、EdegDetectionUtils.java。
package com.zxj.reptile.utils.image;
/**
* 图像边缘检测
* G = Math.sqrt(Gx^2 + Gy^2)
* angle = arctan(Gy / Gx)
*/
public class EdgeDetectionUtils {
/**
* 边缘检测--Reberts--噪声敏感
* Gx=f(x+1,y+1)-f(x,y)
* Gy=f(x,y+1)-f(x+1,y)
* Gx=[-1 0] , Gy=[0 -1]
* ---[ 0 1] [1 0]
*/
public static int[][] reberts(int[][] array) {
int row = array.length;
int column = array[0].length;
int[][] newArray = new int[row][column];
for (int i = 0; i < row - 1; i++) {//图片第几行
for (int j = 0; j < column - 1; j++) {//图片第几列
//Gx=f(x+1,y+1)-f(x,y),
//Gy=f(x,y+1)-f(x+1,y)
int sumX = array[i + 1][j + 1] - array[i][j];
int sumY = array[i + 1][j] - array[i][j + 1];
newArray[i][j] = (int) Math.round(Math.sqrt(sumX * sumX + sumY * sumY));
}
}
return ImageUtils.rangeToByte(newArray);
}
/**
* 边缘检测--Prewitt
* Gx=f(x+1,y-1)+f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+f(x-1,y)+f(x-1,y+1))
* Gy=f(x-1,y+1)+f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+f(x,y-1)+f(x+1,y-1))
* Gx=[-1 0 1] , Gy=[-1 -1 -1]
* ---[-1 0 1] [ 0 0 0]
* ---[-1 0 1] [ 1 1 1]
*/
public static int[][] prewitt(int[][] array) {
int row = array.length;
int column = array[0].length;
int[][] newArray = new int[row][column];
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {//图片第几行
for (int j = 1; j < column - 1; j++) {//图片第几列
//Gx=f(x+1,y-1)+f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+f(x-1,y)+f(x-1,y+1))
//Gy=f(x-1,y+1)+f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+f(x,y-1)+f(x+1,y-1))
int sumX = array[i - 1][j + 1] + array[i][j + 1] + array[i + 1][j + 1] -
(array[i - 1][j - 1] + array[i][j - 1] + array[i + 1][j - 1]);
int sumY = array[i + 1][j - 1] + array[i + 1][j] + array[i + 1][j + 1] -
(array[i - 1][j - 1] + array[i - 1][j] + array[i - 1][j + 1]);
newArray[i][j] = (int) Math.round(Math.sqrt(sumX * sumX + sumY * sumY));
}
}
return ImageUtils.rangeToByte(newArray);
}
/**
* 边缘检测--Sobel
* Gx=f(x+1,y-1)+2f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+2f(x-1,y)+f(x-1,y+1))
* Gy=f(x-1,y+1)+2f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+2f(x,y-1)+f(x+1,y-1))
* Gx=[-1 0 1] , Gy=[-1 -2 -1]
* ---[-2 0 2] [ 0 0 0]
* ---[-1 0 1] [ 1 2 1]
*/
public static int[][] sobel(int[][] array) {
int row = array.length;
int column = array[0].length;
int[][] newArray = new int[row][column];
sobel(array, newArray, null);
return ImageUtils.rangeToByte(newArray);
}
/**
* 边缘检测--Sobel
* Gx=f(x+1,y-1)+2f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+2f(x-1,y)+f(x-1,y+1))
* Gy=f(x-1,y+1)+2f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+2f(x,y-1)+f(x+1,y-1))
* Gx=[-1 0 1] , Gy=[-1 -2 -1]
* ---[-2 0 2] [ 0 0 0]
* ---[-1 0 1] [ 1 2 1]
*
* @param array 原图
* @param rangeArray 幅值图
* @param angleArray 角度图
*/
private static void sobel(int[][] array, int[][] rangeArray, double[][] angleArray) {
int row = array.length;
int column = array[0].length;
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {//图片第几行
for (int j = 1; j < column - 1; j++) {//图片第几列
//Gx=f(x+1,y-1)+f(x+1,y)+f(x+1,y+1)-(f(x-1,y-1)+f(x-1,y)+f(x-1,y+1))
//Gy=f(x-1,y+1)+f(x,y+1)+f(x+1,y+1)-(f(x-1,y-1)+f(x,y-1)+f(x+1,y-1))
int sumX = array[i - 1][j + 1] + 2 * array[i][j + 1] + array[i + 1][j + 1] -
(array[i - 1][j - 1] + 2 * array[i][j - 1] + array[i + 1][j - 1]);
int sumY = array[i + 1][j - 1] + 2 * array[i + 1][j] + array[i + 1][j + 1] -
(array[i - 1][j - 1] + 2 * array[i - 1][j] + array[i - 1][j + 1]);
if (rangeArray != null)
rangeArray[i][j] = (int) Math.round(Math.sqrt(sumX * sumX + sumY * sumY));
if (angleArray != null)
angleArray[i][j] = Math.atan(sumY * 1.0 / sumX);
}
}
}
/**
* 边缘检测--Laplacian--v1--噪声敏感
* G^2=f(x-1,y)+f(x+1,y)+f(x,y-1)+f(x,y+1)-4f(x,y)
* G^2=[0 1 0]
* ----[1 -4 1]
* ----[0 1 0]
*/
public static int[][] laplacianV1(int[][] array) {
int row = array.length;
int column = array[0].length;
int[][] newArray = new int[row][column];
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {//图片第几行
for (int j = 1; j < column - 1; j++) {//图片第几列
//G^2=f(x-1,y)+f(x+1,y)+f(x,y-1)+f(x,y+1)-4f(x,y)
int sum = array[i][j - 1] + array[i][j + 1] + array[i - 1][j] + array[i + 1][j] - 4 * array[i][j];
newArray[i][j] = (int) Math.round(Math.sqrt(sum));
}
}
return ImageUtils.rangeToByte(newArray);
}
/**
* 边缘检测--Laplacian--v2--噪声敏感
* G^2=f(x-1,y-1)+f(x,y-1)+f(x+1,y-1)+f(x-1,y)+f(x+1,y)+f(x-1,y+1)+f(x,y+1)+f(x+1,y+1)-8f(x,y)
* G^2=[1 1 1]
* ----[1 -8 1]
* ----[1 1 1]
*/
public static int[][] laplacianV2(int[][] array) {
int row = array.length;
int column = array[0].length;
int[][] newArray = new int[row][column];
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {//图片第几行
for (int j = 1; j < column - 1; j++) {//图片第几列
//G^2=f(x-1,y-1)+f(x,y-1)+f(x+1,y-1)+f(x-1,y)-8f(x,y)+f(x+1,y)+f(x-1,y+1)+f(x,y+1)+f(x+1,y+1)
int sum = array[i - 1][j - 1] + array[i - 1][j] + array[i - 1][j + 1] +
array[i][j - 1] - 8 * array[i][j] + array[i][j + 1] +
array[i + 1][j - 1] + array[i + 1][j] + array[i + 1][j + 1];
newArray[i][j] = (int) Math.round(Math.sqrt(sum));
}
}
return ImageUtils.rangeToByte(newArray);
}
/**
* 边缘检测--Canny,高斯过滤-->Sobel算子-->非极大值抑制
*
* @param length 高斯过滤的模版长度,length=6sigma(%99.74), length=4sigma(95%), length=2sigma(68%)
* @param sigma 高斯过滤的模版标准差,length=6sigma(%99.74), length=4sigma(95%), length=2sigma(68%)
* @param lowThreshold 双阈值检测的低阈值
* @param highThreshold 双阈值检测的高阈值
*/
public static int[][] canny(int[][] array, int length, double sigma, int lowThreshold, int highThreshold) {
//高斯过滤,length=6sigma(%99.74)
double[][] gaussTemplateArray = FilterUtils.getGaussTemplate(length, sigma);
int[][] gaussArray = FilterUtils.getGaussFilter(array, gaussTemplateArray);
//Sobel算子, 获取高斯过滤图和角度图
int row = array.length;
int column = array[0].length;
int[][] rangeArray = new int[row][column];
double[][] angleArray = new double[row][column];
sobel(gaussArray, rangeArray, angleArray);
//非极大值抑制
int[][] newArray = getDirection(rangeArray, angleArray);
//双阈值检测
int[][] resultArray = thresholdDetection(newArray, lowThreshold, highThreshold);
return ImageUtils.rangeToByte(resultArray);
}
private static final double pi16 = Math.PI / 16;
private static final double[] piArray = new double[]{
0, pi16 * 1, 2, pi16 * 3, 4, pi16 * 5, 6, pi16 * 7, 8,
pi16 * 9, 10, pi16 * 11, 12, pi16 * 13, 14, pi16 * 15, 16};
/**
* 非极大值抑制
* 判断沿着梯度方向的3个像素点(中心像素点与另外两个梯度方向区域内的像素点),
* 若中心像素点的梯度值不比其他两个像素点的梯度值大,则将该像素点的灰度值置0。
* 方向1:f(x-1,y)-->f(x,y)-->f(x+1,y) (15/16)Pi-->(1/16)Pi、(7/16)Pi-->(9/16)Pi
* 方向2:f(x-1,y-1)-->f(x,y)-->f(x+1,y+1) (1/16)Pi-->(3/16)Pi、(9/16)Pi-->(11/16)Pi
* 方向3:f(x,y-1)-->f(x,y)-->f(x,y+1) (3/5)Pi-->(5/16)Pi、(11/16)Pi-->(13/16)Pi
* 方向4:f(x-1,y+1)-->f(x,y)-->f(x+1,y-1) (5/7)Pi-->(7/16)Pi、(13/16)Pi-->(15/16)Pi
*
* @param rangeArray 高斯过滤图
* @param angleArray 角度图
*/
private static int[][] getDirection(int[][] rangeArray, double[][] angleArray) {
int row = rangeArray.length;
int column = rangeArray[0].length;
int[][] array = new int[row][column];
//注意:x = j = column , y = i = row
for (int i = 1; i < row - 1; i++) {
for (int j = 1; j < column - 1; j++) {
array[i][j] = rangeArray[i][j];
double angle = angleArray[i][j];
if ((angle > piArray[15] && angle < piArray[1]) || (angle > piArray[7] && angle < piArray[9])) {
//方向1:f(x-1,y)-->f(x,y)-->f(x+1,y) (15/16)Pi-->(1/16)Pi、(7/16)Pi-->(9/16)Pi
if (rangeArray[i][j] < rangeArray[i][j - 1] || rangeArray[i][j] < rangeArray[i][j + 1])
array[i][j] = 0;
} else if ((angle > piArray[1] && angle < piArray[3]) || (angle > piArray[9] && angle < piArray[11])) {
//方向2:f(x-1,y-1)-->f(x,y)-->f(x+1,y+1) (1/16)Pi-->(3/16)Pi、(9/16)Pi-->(11/16)Pi
if (rangeArray[i][j] < rangeArray[i - 1][j - 1] || rangeArray[i][j] < rangeArray[i + 1][j + 1])
array[i][j] = 0;
} else if ((angle > piArray[3] && angle < piArray[5]) || (angle > piArray[11] && angle < piArray[13])) {
//方向3:f(x,y-1)-->f(x,y)-->f(x,y+1) (3/5)Pi-->(5/16)Pi、(11/16)Pi-->(13/16)Pi
if (rangeArray[i][j] < rangeArray[i - 1][j] || rangeArray[i][j] < rangeArray[i + 1][j])
array[i][j] = 0;
} else {
//方向4:f(x-1,y+1)-->f(x,y)-->f(x+1,y-1) (5/7)Pi-->(7/16)Pi、(13/16)Pi-->(15/16)Pi
if (rangeArray[i][j] < rangeArray[i + 1][j - 1] || rangeArray[i][j] < rangeArray[i - 1][j + 1])
array[i][j] = 0;
}
}
}
return array;
}
/**
* 双阈值检测
* 高阈值图因为作用的阈值较大,去除了大部分的噪声,同时也有可能去除了有用的边缘信息。
* 低阈值图则保留了较多的边缘信息,我们利用低阈值图来对高阈值图进行补充。
*
* @param array 非极大值抑制之后的图像像素数组
* @param lowThreshold 双阈值检测的低阈值
* @param highThreshold 双阈值检测的高阈值
*/
private static int[][] thresholdDetection(int[][] array, int lowThreshold, int highThreshold) {
int row = array.length;
int column = array[0].length;
int[][] resultArray = new int[row][column];
//二值化,一个地阈值,一个高阈值
int[][] lowBinaryArray = ImageUtils.binaryProcess(array, lowThreshold);
int[][] highBinaryArray = ImageUtils.binaryProcess(array, highThreshold);
//低阈值图补充高阈值图
//注意:x = j = column , y = i = row
int[][] highBinaryArrayCopy = highBinaryArray.clone();
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
if (highBinaryArrayCopy[i][j] != 0) {
// thresholdDetectionV1(resultArray, lowBinaryArray, i, j, row, column);
thresholdDetectionV2(resultArray, lowBinaryArray, highBinaryArray, i, j, row, column);
}
}
}
return resultArray;
}
/**
* 双阈值检测--以高阈值图的点为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。
*
* @param resultArray 结果图片像素数组
* @param lowBinaryArray 低阈值图片像素数组
* @param i 第几行
* @param j 第几列
* @param row 总行数
* @param column 总列数
*/
private static void thresholdDetectionV1(int[][] resultArray, int[][] lowBinaryArray, int i, int j, int row, int column) {
if (i < 0 || i >= row || j < 0 || j >= column) {
return;
}
if (resultArray[i][j] != 0) {//已经被扫描过了
return;
} else {//resultArray填充
resultArray[i][j] = lowBinaryArray[i][j];
}
//扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
if (lowBinaryArray[iAround][jAround] != 0) {
thresholdDetectionV1(resultArray, lowBinaryArray, iAround, jAround, row, column);
}
}
}
}
/**
* 双阈值检测--以高阈值图中边缘的端点(端点指一个点少于两个与之相邻的点)为中心,只要低阈值图的点与之相邻,就在高阈值图中新增这点,递归这个操作。
*
* @param resultArray 结果图片像素数组
* @param lowBinaryArray 低阈值图片像素数组
* @param highBinaryArray 高阈值图片像素数组
* @param i 第几行
* @param j 第几列
* @param row 总行数
* @param column 总列数
*/
private static void thresholdDetectionV2(int[][] resultArray, int[][] lowBinaryArray, int[][] highBinaryArray, int i, int j, int row, int column) {
if (i < 0 || i >= row || j < 0 || j >= column) {
return;
}
if (resultArray[i][j] != 0) {//已经被扫描过了
return;
} else { //resultArray填充
resultArray[i][j] = highBinaryArray[i][j];
}
//高阈值图中扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
int aroundCount = 0;//有几个连接点
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
//判断是否为强边缘
if (highBinaryArray[iAround][jAround] != 0) {
aroundCount++;
thresholdDetectionV2(resultArray, lowBinaryArray, highBinaryArray, iAround, jAround, row, column);
}
}
}
//判断是边缘的端点。如果不是端点的话,至少应该有两个连接点。
if (aroundCount < 2) {
//低阈值图中扫描周围八个点:f(x-1,y-1)、f(x,y-1)、f(x+1,y-1)、f(x-1,y)、f(x+1,y)、f(x-1,y+1)、f(x,y+1)、f(x+1,y+1)
for (int k = -1; k <= 1; k++) {
for (int z = -1; z <= 1; z++) {
if (k == 0 && z == 0) {
continue;
}
int iAround = i + k;
int jAround = j + z;
//判断是否为弱边缘
if (lowBinaryArray[iAround][jAround] != 0) {
highBinaryArray[iAround][jAround] = lowBinaryArray[iAround][jAround];//添加高阈值图的强边缘
thresholdDetectionV2(resultArray, lowBinaryArray, highBinaryArray, iAround, jAround, row, column);
}
}
}
}
}
}
上一篇: 深度学习实现工业零件的缺陷检测
下一篇: 「图像处理」Canny边缘检测原理解释