自适应阈值Canny边缘检测

本文主要以代码(java)的形式,修复重构了一种自适应阈值的Canny边缘检测算法。

搭建Eclipse&&OpenCV开发环境

参考Using OpenCV Java with Eclipse搭建自己的Eclipse && OpenCV开发环境。

自适应Canny阈值算法

  • 求取灰度图像的梯度图imge和梯度的最大值maxv
  • 设置梯度图的直方图hist的hist_size=maxv, ranges在[0, maxv]范围内,并计算直方图hist;
  • 设置非边缘像素点占整幅图像像素点的比例PercentOfPixelsNotEdges
  • 设置total阈值,total = size.height * size.width * PercentOfPixelsNotEdges
  • 遍历直方图hist中,每个梯度值对应的像素点个数,并求和保存在sum变量中;
  • 如果sum变量的值大于total的值,退出hist遍历的循环;
  • 计算Canny的低阈值和高阈值。
    a.如果某一像素位置的幅值超过高阈值, 该像素被保留为边缘像素。
    b.如果某一像素位置的幅值小于低阈值, 该像素被排除。
    c.如果某一像素位置的幅值在两个阈值之间, 该像素仅仅在连接到一个高于高阈值的像素时被保留。

Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。

Utils.java

package com.jt; // 根据你创建的包名而不同

import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;
import java.util.List;

import static java.lang.StrictMath.abs;

/**
 * Created by Administrator on 2016/12/7.
 */
public class Utils {

    private static double m_cannyLowTh;  /* !< the lower threshold for Canny. */
    private static double m_cannyHighTh; /* !< the higher threshold for Canyy. */

    public void Utils() {

    }


    public double getM_cannyLowTh () {
        return m_cannyLowTh;
    }


    public double getM_cannyHighTh() {
        return m_cannyHighTh;
    }


    /**
     * Find thresholds for Canny detector.
     * @param src input image.
     * @param aperture_size the window size for Canny detector.
     * @param PercentOfPixelsNotEdges the precision of pixels which not belong to edge.
     */
    public void FindAdaptiveThreshold(Mat src, int aperture_size, double PercentOfPixelsNotEdges)
    {
        Mat dx = new Mat(src.rows(), src.cols(), CvType.CV_16SC1);
        Mat dy = new Mat(src.rows(), src.cols(), CvType.CV_16SC1);
        Imgproc.Sobel(src, dx, CvType.CV_16S, 1, 0, aperture_size, 1, 0, Core.BORDER_DEFAULT);
        Imgproc.Sobel(src, dy, CvType.CV_16S, 0, 1, aperture_size, 1, 0, Core.BORDER_DEFAULT);
        _FindApdaptiveThreshold(dx, dy, PercentOfPixelsNotEdges);
    }


    /**
     *  Find thresholds for Canny detector (core function).
     * @param dx gradient of x orientation.
     * @param dy gradient of y orientation.
     * @param PercentOfPixelsNotEdges the precision of pixels which not belong to edge.
     */
    private static void _FindApdaptiveThreshold(Mat dx, Mat dy, double PercentOfPixelsNotEdges)
    {
        int i, j;
        Size size = dx.size();
        Mat imge = Mat.zeros(size, CvType.CV_32FC1);
        // Compute the strong of edge and store the result in image
        double maxv = 0.0, data;
        for (i = 0; i < size.height; i++) {
            for (j = 0; j < size.width; j++) {
                data = abs(dx.get(i, j)[0]) + abs(dy.get(i, j)[0]);
                imge.put(i, j, data);
                maxv = maxv < data ? data : maxv;
            }
        }
        if (0.0 == maxv) {
            m_cannyLowTh = 0.0;
            m_cannyHighTh = 0.0;
            return;
        }

        // Compute histogram
        int histSize = 256;
        histSize = histSize > (int)maxv ? (int)maxv : histSize;
        MatOfInt hist_size = new MatOfInt(histSize);
        MatOfFloat ranges = new MatOfFloat(0, (float) maxv);
        MatOfInt channels = new MatOfInt(0);
        // Compute hist
        Mat hist = new Mat();
        List images = new ArrayList<>();
        images.add(imge);
        Imgproc.calcHist(images.subList(0, 1), channels, new Mat(), hist, hist_size, ranges, false);

        double sum = 0.0;
        int icount = hist.rows();
        double total = size.height * size.width * PercentOfPixelsNotEdges;
        for (i = 0; i < icount; i++) {
            sum += hist.get(i, 0)[0];
            if (sum > total) {
                break;
            }
        }
        // Compute high and low threshold of Canny
        m_cannyLowTh = (i + 1) * maxv / histSize;
        if(0.0 == m_cannyLowTh) {
            m_cannyHighTh = 0.0;
        } else {
            m_cannyHighTh = 2.5 * m_cannyLowTh; // Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。2~3 --> 2.5
            if (m_cannyHighTh > 255.0) {
                m_cannyHighTh = 255.0;
            }
        }
    }
}

HelloCV.java

package com.jt; // 根据你创建的包名而不同

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

/**
 * Created by Administrator on 2016/12/7.
 */
public class TestCV {

    private static void testCanny(String pathOfPic) {
        Mat srcImg = Imgcodecs.imread(pathOfPic);
        if (srcImg.empty()) {
            System.out.println("Please check the path of input image!");
            return;
        }
        final int imgRows = srcImg.rows();
        final int imgCols = srcImg.cols();

        // Step1: Denoise
        Imgproc.GaussianBlur(srcImg, srcImg, new Size(3, 3), 0, Core.BORDER_DEFAULT);

        // Step2: Convert to gray
        Mat grayImg = Mat.zeros(imgRows, imgCols, CvType.CV_8UC1);
        if (srcImg.channels() == 3) {
            Imgproc.cvtColor(srcImg, grayImg, Imgproc.COLOR_BGR2GRAY);
        }
        Imgproc.medianBlur(grayImg, grayImg, 3);

        // Step3: Binary
        int maskRoiX = (int)(imgCols/12.0);
        int maskRoiY = (int)(imgRows/8.0);
        int maskRoiW = (int)(10/12.0*imgCols);
        int maskRoiH = (int)(6/8.0*imgRows);
        Rect maskRoi = new Rect(maskRoiX, maskRoiY, maskRoiW, maskRoiH);
        Mat maskSrc = new Mat(grayImg, maskRoi);

        Utils utils = new Utils();
        utils.FindAdaptiveThreshold(maskSrc, 3, 0.80);
        double thCannyLow = utils.getM_cannyLowTh();
        double thCannyHigh = utils.getM_cannyHighTh();

        Mat maskImg = Mat.zeros(imgRows, imgCols, CvType.CV_8UC1);
        Imgproc.Canny(grayImg, maskImg, thCannyLow, thCannyHigh, 3, false);

        System.out.println("Canny threshold lowth = " + thCannyLow + "\thighth = " + thCannyHigh);
        Imgcodecs.imwrite("D:/LenaCannyBinary.jpg", maskImg);
    }


    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        String inputImgPath = "D:/lena.jpg";
        testCanny(inputImgPath);
    }
}

测试图像

测试结果

Canny threshold lowth = 76.5625 highth = 191.40625

参考

  1. Canny 边缘检测
  2. Java+Opencv 入门汇总
  3. 在OpenCV中自适应确定canny算法的分割门限

你可能感兴趣的:(OpenCV)