本文主要以代码(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
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
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<Mat> 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边缘检测_像素点

测试结果

Canny threshold lowth = 76.5625 highth = 191.40625

自适应阈值Canny边缘检测_直方图_02

参考

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