2015-04-23 14:46:36 hjimce 阅读数 7779

这段时间为了搞项目,涉及到图像分割算法,由于感觉传统的分割算法得出来的效果都很差。于是就尝试各种图像分割算法,把每种分割算法的代码都写一写,一来是为了提高自己的编程能力,二来是为了更加深刻的了解算法。学习基于测地距离的图像分割算法,是由于之前把grab cut算法实现了一遍,感觉效果很坑爹,于是就尝试基于测地距离的分割算法。这个算法开始前,需要先非常熟悉“测地距离”这个概念,因为之前写过三角网格曲面上的Dijkstra算法,在三维曲面空间中两点间的测地距离就是两点间沿着曲面走的最短路径,因为之前对测地距离这个概念就比较熟悉,所以看《Geodesic Matting: A Framework for Fast Interactive Image这篇paper,也就觉得没有什么难度,所以果断写代码,试验一下效果再说。

1、算法原理基于测地距离的图像分割属于一种图论的分割算法。图论分割算法:即把图像上的每个像素点当做图的顶点,图的每个顶点有四个邻接顶点(每个像素点有四个邻接像素点,除边界点),每两个邻接像素点用相应的边连接,边的长度与两个像素点间的相似度有关(测地距离),而非采用简单的欧式距离作为边长(相邻像素点间的欧式距离为1),具体文献中边长的计算方法见下文。

2、基于测地距离的分割算法流程:给定一张待分割的图片,首先算法会利用该图片构造一张距离图,边的距离大小:参考文献中采用的是现根据已知的标记点前景、背景,对未知的像素点进行核密度估计,并根据相邻相似点间的隶属于前景的概率差,作为边长,我写的demo中是直接采用像素值的差值作为边长。

具体实现:在测地距离的算法中提供两种类型的笔刷,其作用是用来标记为分割时的前景F和相应的背景B

①首先会需要用户在相应的前景和背景区域做标记。

②计算未标记点到已标记点前景的测地距离d1,及背景的测地距离d2。测地距离的计算方法我的demo中采用的是Dijkstra算法进行计算(Dijkstra算法原理请百度搜索)。具体过程如下:首先,把标号为的标记点到目标的测地距离初始化为0,到背景的测地距离初始化为无穷大;同样,把标号为的标记点到背景的测地距离初始化为0,到目标的测地距离初始化为无穷大。根据连通标号矩阵,搜索标号为的所有标记点的4连通邻域像素点,找出这些像素点中权重概率最小的那个像素点,把最小权重概率对应的像素点添加到已排好序的路径上,并通过比较原来的权重概率与加入新顶点后的新概率大小,决定是否更新其它像素点的权重概率。这样,不断地添加新的像素点到已排好序的路径上,直到图像中所有像素点全部搜索完为止。于是由一个或多个权重概率加和得到每个像素点到目标的测地距离d1(x)。同理,可以计算出图像中各像素点到背景的测地距离d2(x)

③根据测地距离的大小,判断未标记点属于前景或背景。根据概率公式,由图像中的每个像素点到目标和背景的测地距离,计算出该像素点分属于目标和背景的概率:

                         

                          

对于图像中的各像素点,若 ,则把该像素点判为目标像素点则判为把该像素点判为背景像素点。

看一下我测试的几张图片分割效果:


原图                                                                   分割结果


  原图                                                                                         分割结果


最后总结一下算法的优缺点:这个算法给我的感觉,至少比grab cut 算法强,2004年的grab  cut 算法paper效果看起看挺不错的,但是试验起来感觉很坑爹。

算法优点:计算速度快,比grab cut算法快好多(demo未做整理,边长重复计算,因此速度达不到文献中速度),对图片中包含半透明区域的图像分割也有一定的效果。

算法缺点:如果前景和背景边缘比较模糊,这时测地距离的图像分割容易出错。这时候用户如果想得到一个较好的分割效果,需要额外的交互或者对图像进行其他的处理。

附:还有一篇2010年文献《Geodesic graph cut for interactive image segmentation》结合了graph cut 算法和测地距离分割算法的优点,把两种算法结合起来,可克服两种算法存在的缺陷。

总结:图像分割算法基本上鲁棒性都很差,加点噪声,边缘较弱等对所有的图像分割算法影响都很大,算法一般只对文献上的图片效果较好。

本文地址:http://blog.csdn.net/hjimce/article/details/45221607    作者:hjimce     联系qq:1393852684

更多资源请关注我的博客:http://blog.csdn.net/hjimce                  原创文章,转载请保留这两行作者信息


2019-04-11 02:10:11 qq_35306281 阅读数 220

@图像处理_距离变换_c++代码实现

距离变换:
分为:欧式距离,城区距离,棋盘距离。

算法步骤:

  1. 输入:二值图像。
  2. 从图像左上角第二行开始,从左向右、从上到下移动窗口扫描每个像素,类似滤波过程,检测在中心像素P的周围四个像素与中心像素的距离,若中心像素为0,则跳过。保存最小距离与位置作为结果。
  3. 从右下角倒数第二行开始,从右向左,从下到上扫描图像。其它同2。
  4. 输出图像。

代码如下:

/*created at 2019/4/11*/
#include <iostream>
#include <algorithm>
#include <opencv2/opencv.hpp>

void distance_transform_3x3(cv::Mat& src)
{
	int rows = src.rows;
	int cols = src.cols;
	float sum[5];
	/*第一次扫描*/
	for(int r = 1; r < rows - 1; ++r)
	{
		for(int c = 1; c < cols - 1; ++c)
		{
			if(src.at<uchar>(r,c))
			{
				sum[0] = 1.4142 + src.at<uchar>(r-1,c-1);
				sum[1] = 1      + src.at<uchar>(r-1,  c);
				sum[2] = 1.4142 + src.at<uchar>(r-1,c+1);
				sum[3] = 1	    + src.at<uchar>(r,  c-1);
				sum[4] =          src.at<uchar>(r,    c); 
				std::sort(sum, sum+5);
				src.at<uchar>(r,c) = sum[0];
			}
		}
	}
	/*第二次扫描*/
	for(int r = rows - 1; r > 0; --r)
	{
		for(int c = cols - 1; c > 0; --c)
		{
			if(src.at<uchar>(r,c))
			{
				sum[0] =          src.at<uchar>(r,    c);
				sum[1] = 1	    + src.at<uchar>(r,  c+1);
				sum[2] = 1.4142 + src.at<uchar>(r+1,c-1);
				sum[3] = 1      + src.at<uchar>(r+1,  c);
				sum[4] = 1.4142	+ src.at<uchar>(r+1,c+1);
				std::sort(sum, sum+5);
				src.at<uchar>(r,c) = sum[0];
			}
		}
	}
}

int main(int argc, char* argv[])
{
	cv::Mat src = cv::imread("test.jpg");
	distance_transform_3x3(src );
	cv::imshow("out.jpg", src );
	cv::waitKey(0);
	return 0;
}

原图:
在这里插入图片描述
输出:
在这里插入图片描述

2019-11-19 23:47:23 qq_36511401 阅读数 777

底下有详细代码

一、介绍

1、图像检测的原理。

        图像检测的原理是检测相邻的几个点像素值之间的变化率,相对于对函数求导。求点P(x,y)的变换率,可以在点P周围选取一些点,求x方向的距离Gx,再求y方向上的距离Gy。最后变换率G等于Gx平方加上Gy平方的和的平方差,即G=Math.sqrt(Gx^2+Gy^2)。

2、Sobel算子。

        索贝尔算子对噪声不敏感。是计算机视觉领域的一种重要处理方法。主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。索贝尔算子是把图像中每个像素的上下左右四领域的灰度值加权差,在边缘处达到极值从而检测边缘。
        索贝尔算子主要用作边缘检测。在技术上,它是一离散性差分算子,用来运算图像亮度函数的梯度之近似值。在图像的任何一点使用此算子,将会产生对应的梯度矢量或是其法矢量。
        索贝尔算子不但产生较好的检测效果,而且对噪声具有平滑抑制作用,但是得到的边缘较粗,且可能出现伪边缘。

3、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]

二、主要代码

1、EdgeDetectionTest 类。

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) {
        sobelTest();
    }

    private static void sobelTest() {
        String sourcePath = "G:\\xiaojie-java-test\\img\\边缘检测\\Lena灰度图.jpg";
        String targetPath = "G:\\xiaojie-java-test\\img\\边缘检测\\Lena-sobel.jpg";
        sobel(sourcePath, targetPath);
        sobelBinaryTest(128);
        sobelBinaryTest(64);
        sobelBinaryTest(32);
    }

    private static void sobel(String sourcePath, String targetPath) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getBytes(image);
            //生成新图像的二维数组
            int[][] newImgArrays = EdgeDetectionUtils.sobel(imgArrays);
            //生成新图片对象,填充像素
            BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
            ImageUtils.setIntsForGray(newImage, newImgArrays);
            //生成图片文件
            ImageIO.write(newImage, "JPEG", new File(targetPath));
            Thread.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sobelBinaryTest(int threshold) {
        String sourcePath = "G:\\xiaojie-java-test\\img\\边缘检测\\Lena-sobel.jpg";
        String targetPath = "G:\\xiaojie-java-test\\img\\边缘检测\\Lena-sobel-binary-" + threshold + ".jpg";
        ImageService.toBinaryImg(sourcePath, targetPath, threshold);
    }
}

2、EdgeDetectionUtils 类。

package com.zxj.reptile.utils.image;

/**
 * 图像边缘检测
 */
public class EdgeDetectionUtils {
    /*
     * 一阶微分算子:Roberts 、Sobel 、Prewitt
     * G = Math.sqrt(Gx^2 + Gy^2)
     * angle = arctan(Gy / Gx)
     */


    /**
     * 边缘检测--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];
        //注意:x = j = column , y = i = row
        for (int i = 0; i < row; i++) {//图片第几行
            for (int j = 0; j < column; j++) {//图片第几列
                int sum, sumX, sumY;
                if (i > 0 && j > 0 && i < row - 1 && j < column - 1) {
                    //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))
                    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]);
                    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]);
                    sum = (int) Math.sqrt(sumX * sumX + sumY * sumY);
                    newArray[i][j] = sum > 0xff ? 0xff : sum;
                } else if (i < row - 1 && j < column - 1) {
                    //Gx=f(x+1,y+1)-f(x,y),
                    //Gy=f(x,y+1)-f(x+1,y)
                    sumX = array[i + 1][j + 1] - array[i][j];
                    sumY = array[i + 1][j] - array[i][j + 1];
                    sum = (int) Math.sqrt(sumX * sumX + sumY * sumY);
                    newArray[i][j] = sum > 0xff ? 0xff : sum;
                } else if (j == column - 1) {
                    //最后一列
                    newArray[i][j] = newArray[i][j - 1];
                } else if (i == row - 1) {
                    //最后一行
                    newArray[i][j] = newArray[i - 1][j];
                }
            }
        }
        return ImageUtils.rangeByte(newArray);
    }
}

三、结果

1、原图。

2、生成的结果截图。

3、图片边缘检测的图片。

4、将图片边缘检测的图片二值化,其中阈值分别为:32、64、128。                                                           

 

四、详细代码

详细代码可以查看文章图像边缘检测 Reberts边缘检测的详细代码,因为一样的,就不重复写出了。

2019-11-26 17:12:42 qq_36511401 阅读数 828

底下有详细代码

一、介绍

1、图像检测的原理。

        图像检测的原理是检测相邻的几个点像素值之间的变化率,相对于对函数求导。求点P(x,y)的变换率,可以在点P周围选取一些点,求x方向的距离Gx,再求y方向上的距离Gy。最后变换率G等于Gx平方加上Gy平方的和的平方差,即G=Math.sqrt(Gx^2+Gy^2)。

2、Laplacian算子。

        拉普拉斯算子对噪声敏感。对图像中的阶跃性边缘点定位正确,对噪声十分的敏感,会丢失一部分边缘的方向信息,造成一些不连续的检测边缘。Laplacian 算子是n维欧几里德空间中的一个二阶微分算子。

3、Laplacian算子模版有两个。

(1)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]

(2)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]

二、Laplacian模版V1

1、主流程代码。

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) {
        laplacianTest();
    }

    private static void laplacianTest() {
        String path = "G:\\xiaojie-java-test\\img\\边缘检测\\";
        String sourcePath = path + "Lena灰度图.jpg";

        //laplacianV1
        String targetPath1 = path + "Lena-laplacianV1.jpg";
        laplacianV1(sourcePath, targetPath1);
        //二值化
        ImageService.toBinaryImg(targetPath1, path + "Lena-laplacianV1-binary-96.jpg", 96);
        ImageService.toBinaryImg(targetPath1, path + "Lena-laplacianV1-binary-64.jpg", 64);
        ImageService.toBinaryImg(targetPath1, path + "Lena-laplacianV1-binary-32.jpg", 32);
    }

    private static void laplacianV1(String sourcePath, String targetPath) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getImageGray(image);
            //生成新图像的二维数组
            int[][] newImgArrays = EdgeDetectionUtils.laplacianV1(imgArrays);
            //生成新图片对象,填充像素
            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、核心代码。

package com.zxj.reptile.utils.image;

/**
 * 图像边缘检测
 * G = Math.sqrt(Gx^2 + Gy^2)
 * angle = arctan(Gy / Gx)
 */
public class EdgeDetectionUtils {

    /**
     * 边缘检测--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);
    }
}

3、结果。

(1)原图和结果。

 

(2)结果二值化,阈值分别为32、64、96。

(3)截图。

三、Laplacian模版V2

1、主流程代码。

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) {
        laplacianTest();
    }

    private static void laplacianTest() {
        String path = "G:\\xiaojie-java-test\\img\\边缘检测\\";
        String sourcePath = path + "Lena灰度图.jpg";

        //laplacianV2
        String targetPath2 = path + "Lena-laplacianV2.jpg";
        laplacianV2(sourcePath, targetPath2);
        //二值化
        ImageService.toBinaryImg(targetPath2, path + "Lena-laplacianV2-binary-96.jpg", 96);
        ImageService.toBinaryImg(targetPath2, path + "Lena-laplacianV2-binary-64.jpg", 64);
        ImageService.toBinaryImg(targetPath2, path + "Lena-laplacianV2-binary-32.jpg", 32);
    }

    private static void laplacianV2(String sourcePath, String targetPath) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getImageGray(image);
            //生成新图像的二维数组
            int[][] newImgArrays = EdgeDetectionUtils.laplacianV2(imgArrays);
            //生成新图片对象,填充像素
            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、核心代码。

package com.zxj.reptile.utils.image;

/**
 * 图像边缘检测
 * G = Math.sqrt(Gx^2 + Gy^2)
 * angle = arctan(Gy / Gx)
 */
public class EdgeDetectionUtils {
    /**
     * 边缘检测--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);
    }
}

3、结果。

(1)原图和结果。

(2)结果二值化,阈值分别为32、64、96。

(3)截图。

四、详细代码

1、FilterUtils.java。

package com.zxj.reptile.utils.image;

import com.zxj.reptile.utils.algorithm.SortUtils;

public class FilterUtils {
    /**
     * 中值滤波
     *
     * @param imgArrays    图像二维数组
     * @param filterLength 过滤的长度(阈值)
     */
    public static int[][] getMiddleFilter(int[][] imgArrays, int filterLength) {
        final int imgHeight = imgArrays.length;
        final int imgWidth = imgArrays[0].length;
        int[][] newImgArrays = new int[imgHeight][imgWidth];
        //模版半径
        final int filterRadius = (filterLength - 1) / 2;
        final int filterSize = filterLength * filterLength;
        //获取数据
        for (int h = 0; h < imgHeight; h++) {//图片第几行
            for (int w = 0; w < imgWidth; w++) {//图片第几列
                int count = 0;
                int[] templateArray = new int[filterSize];
                for (int templateH = -filterRadius; templateH <= filterRadius; templateH++) {//模版第几行
                    int rowIndex = h + templateH;
                    if (rowIndex < 0 || rowIndex > imgHeight - 1) {
                        continue;
                    }
                    for (int templateW = -filterRadius; templateW <= filterRadius; templateW++) {//模版第几列
                        int columnIndex = w + templateW;
                        if (columnIndex < 0 || columnIndex > imgWidth - 1) {
                            continue;
                        }
                        templateArray[count++] = imgArrays[rowIndex][columnIndex];
                    }
                }
                //
                int[] newTemplateArray;
                if (count != templateArray.length) {
                    newTemplateArray = new int[count];
                    for (int i = 0; i < count; i++) {
                        newTemplateArray[i] = templateArray[i];
                    }
                    SortUtils.countSort(newTemplateArray);//计数排序
                    newImgArrays[h][w] = newTemplateArray[count / 2];
                } else {
                    SortUtils.countSort(templateArray);//计数排序
                    newImgArrays[h][w] = templateArray[filterRadius];
                }
            }
        }
        return newImgArrays;
    }

    /**
     * 均值滤波
     *
     * @param imgArrays    图像二维数组
     * @param filterLength 过滤的长度(阈值)
     */
    public static int[][] getAverageFilter(int[][] imgArrays, int filterLength) {
        final int imgHeight = imgArrays.length;
        final int imgWidth = imgArrays[0].length;
        int[][] newImgArrays = new int[imgHeight][imgWidth];
        //模版半径
        final int filterRadius = (filterLength - 1) / 2;
        //获取数据
        for (int h = 0; h < imgHeight; h++) {//图片第几行
            for (int w = 0; w < imgWidth; w++) {//图片第几列
                int count = 0;
                int sum = 0;
                for (int templateH = -filterRadius; templateH <= filterRadius; templateH++) {//模版第几行
                    int rowIndex = h + templateH;
                    if (rowIndex < 0 || rowIndex > imgHeight - 1) {
                        continue;
                    }
                    for (int templateW = -filterRadius; templateW < filterRadius; templateW++) {//模版第几列
                        int columnIndex = w + templateW;
                        if (columnIndex < 0 || columnIndex > imgWidth - 1) {
                            continue;
                        }
                        sum += imgArrays[rowIndex][columnIndex];
                        count++;
                    }
                }
                newImgArrays[h][w] = sum / count;
            }
        }
        return newImgArrays;
    }

    /**
     * 获取高斯滤波模版的二维数组
     *
     * @param length 模版长度,length=6sigma(%99.74), length=4sigma(95%), length=2sigma(68%)
     * @param sigma  模版标准差,length=6sigma(%99.74), length=4sigma(95%), length=2sigma(68%)
     */
    public static double[][] getGaussTemplate(int length, double sigma) {
        double[][] gaussTemplate = new double[length][length];    // 用于存储结果
        int centerIndex = (length - 1) / 2;//模版的中心点
        double variance = sigma * sigma;//方差
        double sum = 0;
        for (int i = 0; i < length; ++i) {
            for (int j = 0; j < length; ++j) {
                int xLength = j - centerIndex;//x上的距离
                int yLength = i - centerIndex;//y上的距离
                double e = Math.exp(-(xLength * xLength + yLength * yLength) / (2 * variance));
                gaussTemplate[i][j] = e / (2 * Math.PI * variance);
                sum += gaussTemplate[i][j];
            }
        }
        //占得比重
        for (int i = 0; i < length; ++i) {
            for (int j = 0; j < length; ++j) {
                gaussTemplate[i][j] = gaussTemplate[i][j] / sum;
            }
        }
//        //打印模版
//        System.out.println(String.format("生成大小为%d,标准差为%.3f的二维高斯模版:", length, sigma));
//        for (double[] doubles : gaussTemplate) {
//            for (int j = 0; j < gaussTemplate.length; j++) {
//                System.out.print(String.format("%8.3f", doubles[j]));
//            }
//            System.out.println();
//        }
        return gaussTemplate;
    }

    /**
     * 高斯滤波
     *
     * @param imgArrays     图像二维数组
     * @param gaussTemplate 高斯滤波模版的二维数组
     */
    public static int[][] getGaussFilter(int[][] imgArrays, double[][] gaussTemplate) {
        final int imgHeight = imgArrays.length;
        final int imgWidth = imgArrays[0].length;
        int[][] newImgArrays = new int[imgHeight][imgWidth];
        //模版长度和半径
        final int filterLength = gaussTemplate.length;
        final int filterRadius = (filterLength - 1) / 2;
        //高斯过滤
        for (int h = 0; h < imgHeight; h++) {//图片第几行
            for (int w = 0; w < imgWidth; w++) {//图片第几列
                double weightTotal = 0;
                double sum = 0;
                for (int templateH = -filterRadius; templateH <= filterRadius; templateH++) {//模版第几行
                    int rowIndex = h + templateH;
                    if (rowIndex < 0 || rowIndex > imgHeight - 1) {
                        continue;
                    }
                    for (int templateW = -filterRadius; templateW < filterRadius; templateW++) {//模版第几列
                        int columnIndex = w + templateW;
                        if (columnIndex < 0 || columnIndex > imgWidth - 1) {
                            continue;
                        }
                        double weight = gaussTemplate[templateH + filterRadius][templateW + filterRadius];
                        sum += (imgArrays[rowIndex][columnIndex] * weight);
                        weightTotal += weight;
                    }
                }
                newImgArrays[h][w] = (int) (sum / weightTotal);
            }
        }
        return newImgArrays;
    }
}

2、ImageService.java。

package com.zxj.reptile.utils.image;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

public class ImageService {
    /**
     * 图片灰度化的方法
     *
     * @param sourcePath 源图片路径
     * @param targetPath 目标图片路径
     * @param grayType   灰度化方法
     */
    public static void toGrayImg(String sourcePath, String targetPath, int grayType) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getImageRgb(image);
            //生成新图像的二维数组
            int[][] newImgArrays = ImageUtils.grayProcess(imgArrays, grayType);//灰度化
            //生成新图片对象,填充像素
            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();
        }
    }

    /**
     * 生成不同rgb通道图片的方法
     *
     * @param sourcePath   源图片路径
     * @param targetPath   rgb通道图片路径
     * @param channelColor r、g、b颜色通道
     */
    public static void toChannelImg(String sourcePath, String targetPath, int channelColor) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getImageRgb(image);
            //生成新图像的二维数组
            int[][] newImgArrays = ImageUtils.channelProcess(imgArrays, channelColor);
            //生成新图片对象,填充像素
            BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
            ImageUtils.setImageRgb(newImage, newImgArrays);
            //生成图片文件
            ImageIO.write(newImage, "JPEG", new File(targetPath));
            Thread.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 二值化
     *
     * @param sourcePath 源图片路径
     * @param targetPath 目标二值图片路径
     * @param threshold  阈值
     */
    public static void toBinaryImg(String sourcePath, String targetPath, int threshold) {
        try {
            //获取原图像对象,并获取原图像的二维数组
            BufferedImage image = ImageIO.read(new File(sourcePath));
            int[][] imgArrays = ImageUtils.getImageGray(image);
            //生成新图像的二维数组
            int[][] newImgArrays = ImageUtils.binaryProcess(imgArrays, threshold);//二值化
            //生成新图片对象,填充像素
            BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
            ImageUtils.setImageBytes(newImage, newImgArrays);
            //生成图片文件
            ImageIO.write(newImage, "JPEG", new File(targetPath));
            Thread.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、ImageUtils.java。

package com.zxj.reptile.utils.image;

import java.awt.image.BufferedImage;

/**
 * 要用int,不能使用byte,因为byte最大值为128
 */
public class ImageUtils {
    /**
     * 灰度处理的方法
     */
    public static final byte Gray_Type_Min = 1;//最大值法
    public static final byte Gray_Type_Max = 2;//最小值法
    public static final byte Gray_Type_Average = 3;//平均值法
    public static final byte Gray_Type_Weight = 4;//加权法
    public static final byte Gray_Type_Red = 5;//红色值法
    public static final byte Gray_Type_Green = 6;//绿色值法
    public static final byte Gray_Type_Blue = 7;//蓝色值法
    public static final byte Gray_Type_Default = Gray_Type_Weight;//默认加权法

    /**
     * 不同颜色通道的图片
     */
    public static final byte Channel_Color_Red = 1;
    public static final byte Channel_Color_Green = 2;
    public static final byte Channel_Color_Blue = 3;

    /**
     * 灰度化处理,彩色int[][] 转 灰度byte[][]
     *
     * @param imgArrays 图像二维数组
     * @param grayType  灰度化方法
     */
    public static int[][] grayProcess(int[][] imgArrays, int grayType) throws Exception {
        int[][] newImgArrays = new int[imgArrays.length][imgArrays[0].length];
        for (int h = 0; h < imgArrays.length; h++)
            for (int w = 0; w < imgArrays[0].length; w++)
                newImgArrays[h][w] = getImageGray(getImageRgb(imgArrays[h][w]), grayType);
        return newImgArrays;
    }

    /**
     * 颜色通道处理,彩色int[][] 转 彩色int[][]
     *
     * @param imgArrays    图像二维数组
     * @param channelColor 不同颜色通道
     */
    public static int[][] channelProcess(int[][] imgArrays, int channelColor) {
        int[][] newImgArrays = new int[imgArrays.length][imgArrays[0].length];
        for (int h = 0; h < imgArrays.length; h++) {
            for (int w = 0; w < imgArrays[0].length; w++) {
                final int pixel = imgArrays[h][w];
                if (channelColor == Channel_Color_Red) {
                    newImgArrays[h][w] = pixel & 0xff0000;
                } else if (channelColor == Channel_Color_Green) {
                    newImgArrays[h][w] = pixel & 0x00ff00;
                } else if (channelColor == Channel_Color_Blue) {
                    newImgArrays[h][w] = pixel & 0x0000ff;
                }
            }
        }
        return newImgArrays;
    }

    /**
     * 二值化处理,灰度byte[][] 转 二值byte[][]
     *
     * @param imgArrays 灰度 int[][]
     * @param threshold 阈值
     */
    public static int[][] binaryProcess(int[][] imgArrays, int threshold) {
        int[][] newImgArrays = new int[imgArrays.length][imgArrays[0].length];
        for (int h = 0; h < imgArrays.length; h++)
            for (int w = 0; w < imgArrays[0].length; w++) {
                newImgArrays[h][w] = (imgArrays[h][w] < threshold ? 0 : 0xff);
            }
        return newImgArrays;
    }

    /**
     * 根据像素,返回r、g、b 的 byte[]
     *
     * @param pixel 像素值
     */
    private static int[] getImageRgb(int pixel) {
        int[] rgb = new int[3];
        rgb[0] = ((pixel >> 16) & 0xff);
        rgb[1] = ((pixel >> 8) & 0xff);
        rgb[2] = (pixel & 0xff);
        return rgb;
    }

    /**
     * 获取像素值
     *
     * @param pixel 像素值
     */
    public static long getPixel(int pixel) {
        return (pixel & 0xff) + (pixel & 0xff00) + (pixel & 0xff0000);
    }

    /**
     * 获取像素值
     *
     * @param rgb r、g、b 的 byte[]
     */
    public static long getPixel(int[] rgb) {
        return (rgb[0] << 16) + (rgb[1] << 8) + rgb[2];
    }

    /**
     * 根据r、g、b 的 byte[],返回灰度值
     *
     * @param rgb      r、g、b颜色通道的值
     * @param grayType 不同灰度处理的方法
     */
    private static int getImageGray(int[] rgb, int grayType) throws Exception {
        if (grayType == Gray_Type_Average) {
            return ((rgb[0] + rgb[1] + rgb[2]) / 3);   //rgb之和除以3
        } else if (grayType == Gray_Type_Weight) {
            return (int) (0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]);
        } else if (grayType == Gray_Type_Red) {
            return rgb[0];//取红色值
        } else if (grayType == Gray_Type_Green) {
            return rgb[1];//取绿色值
        } else if (grayType == Gray_Type_Blue) {
            return rgb[2];//取蓝色值
        }
        //比较三个数的大小
        int gray = rgb[0];
        for (int i = 1; i < rgb.length; i++) {
            if (grayType == Gray_Type_Min) {
                if (gray > rgb[i]) {
                    gray = rgb[i];//取最小值
                }
            } else if (grayType == Gray_Type_Max) {
                if (gray < rgb[i]) {
                    gray = rgb[i];//取最大值
                }
            } else {
                throw new Exception("grayType出错");
            }
        }
        return gray;
    }

    /**
     * 获取图像像素 byte[][] rgb值
     *
     * @param image BufferedImage图像对象
     */
    public static int[][] getImageRgb(BufferedImage image) {
        int[][] imgArrays = new int[image.getHeight()][image.getWidth()];
        for (int i = 0; i < image.getHeight(); i++)
            for (int j = 0; j < image.getWidth(); j++)
                imgArrays[i][j] = image.getRGB(j, i);
        return imgArrays;
    }

    /**
     * 获取图像像素 byte[][] 灰度值
     *
     * @param image BufferedImage图像对象
     */
    public static int[][] getImageGray(BufferedImage image) throws Exception {
        return grayProcess(getImageRgb(image), Gray_Type_Default);
    }

    /**
     * 获取图像像素 byte[][] 二值
     *
     * @param image BufferedImage图像对象
     */
    public static int[][] getImageBinary(BufferedImage image) throws Exception {
        return binaryProcess(grayProcess(getImageRgb(image), Gray_Type_Default), 0xff / 2);
    }

    /**
     * 图像像素填充 byte[][]
     *
     * @param image     BufferedImage图像对象
     * @param imgArrays 二维像素
     */
    public static void setImageBytes(BufferedImage image, int[][] imgArrays) {
        for (int i = 0; i < image.getHeight(); i++)
            for (int j = 0; j < image.getWidth(); j++)
                image.setRGB(j, i, (byte) imgArrays[i][j]);
    }

    /**
     * 图像像素填充 byte[][]
     *
     * @param image     BufferedImage图像对象
     * @param imgArrays 二维像素
     */
    public static void setImageBytes(BufferedImage image, byte[][] imgArrays) {
        for (int i = 0; i < image.getHeight(); i++)
            for (int j = 0; j < image.getWidth(); j++)
                image.setRGB(j, i, imgArrays[i][j]);
    }

    /**
     * 图像像素填充 int[][]
     *
     * @param image     BufferedImage图像对象
     * @param imgArrays 二维像素
     */
    public static void setImageRgb(BufferedImage image, int[][] imgArrays) {
        for (int i = 0; i < image.getHeight(); i++)
            for (int j = 0; j < image.getWidth(); j++)
                image.setRGB(j, i, imgArrays[i][j]);
    }

    /**
     * 图像像素填充 将 byte[][] 变为 int[][] 进行填充
     *
     * @param image     BufferedImage图像对象
     * @param imgArrays 二维像素
     */
    public static void setImageRgbByByte(BufferedImage image, int[][] imgArrays) {
        for (int i = 0; i < image.getHeight(); i++)
            for (int j = 0; j < image.getWidth(); j++)
                image.setRGB(j, i, imgArrays[i][j] + (imgArrays[i][j] << 8) + (imgArrays[i][j] << 16));
    }

    /**
     * @param arrays 将数组的值域分布到0--0xff之间
     */
    public static int[][] rangeToByte(int[][] arrays) {
        int max = arrays[0][0], min = arrays[0][0];
        for (int[] array : arrays) {
            for (int value : array) {
                if (value > max) {
                    max = value;
                } else if (value < min) {
                    min = value;
                }
            }
        }
        //
        int[][] newArrays = new int[arrays.length][];
        int range = max - min + 1;
        double multiply = (0xff + 1.0) / range;
        for (int i = 0; i < arrays.length; i++) {
            int[] array = arrays[i];
            int[] newArray = new int[array.length];
            for (int j = 0; j < array.length; j++) {
                int value = (int) Math.round((array[j] - min) * multiply);
                newArray[j] = value > 0xff ? 0xff : value;
            }
            newArrays[i] = newArray;
        }
        return newArrays;
    }
}
2019-11-26 17:33:17 qq_36511401 阅读数 839

底下有详细代码

一、介绍

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);
                    }
                }
            }
        }
    }
}

 

 

没有更多推荐了,返回首页