图像边缘检测 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);
                    }
                }
            }
        }
    }
}

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值