Automatic calculation of low and high thresholds for the Canny operation in opencv

In OpenCV, the low and high thresholds for the canny operator are mandatory:

cvCanny(input,output,thresh1,thresh2)

In Matlab, there's an option to calculate those automatically:

edge(input,'canny')

I've looked into Matlab's code for edge, and this is really not straight forward to calculate those automatically.

Are you aware of any implementation of the canny operator along with automatic threshold calculation for OpenCV?


Solution 1:

I stumbled upon this answer while I was searching for a way to automatically compute Canny's threshold values.

Hope this helps anyone who comes looking for a good method for determining automatic threshold values for Canny's algorithm...


If your image consists of distinct foreground and background, then the edge of foreground object can use extracted by following:

  1. Calculate Otsu's threshold using:

    double otsu_thresh_val = cv::threshold(
        orig_img, _img, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU
    );
    

    We don't need the _img. We are interested in only the otsu_thresh_val but unfortunately, currently there is no method in OpenCV which allows you to compute only the threshold value.

  2. Use the Otsu's threshold value as higher threshold and half of the same as the lower threshold for Canny's algorithm.

    double high_thresh_val  = otsu_thresh_val,
           lower_thresh_val = otsu_thresh_val * 0.5;
    cv::Canny( orig_img, cannyOP, lower_thresh_val, high_thresh_val );
    

More information related to this can be found in this paper: The Study on An Application of Otsu Method in Canny Operator. An explaination of Otsu's implementation can be found here.

Solution 2:

You can use the mean value of the your input grayscale image and define lower and upper thresholds using standard deviation. You can have more detailed explanation and opencv code here: http://www.kerrywong.com/2009/05/07/canny-edge-detection-auto-thresholding/

Solution 3:

Also, there is code available to do this automatically, by putting this in the OpenCV build. I found it on the OpenCV-users mailing list, so no guarantees. :)

Discussion: http://opencv-users.1802565.n2.nabble.com/Automatic-thresholding-in-cvCanny-td5871024.html GitHub (code): https://gist.github.com/756833

Solution 4:

Check out this link: http://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/

They implement a similar solution using basic statistics to determine the low and high threshold for Canny edge detection.

def auto_canny(image, sigma=0.33):
     # compute the median of the single channel pixel intensities
     v = np.median(image)

    # apply automatic Canny edge detection using the computed median
    lower = int(max(0, (1.0 - sigma) * v))
    upper = int(min(255, (1.0 + sigma) * v))
    edged = cv2.Canny(image, lower, upper)

    # return the edged image
    return edged

Solution 5:

I have looked through the source code of Matlab Canny edge detection and I managed to write it in Java with OpenCV 3.

private static Mat getpartialedge(Mat image){
    double nonEdgeRate = 0.6;
    double thresholdRate = 0.6;
    double w = image.cols();
    double h = image.rows();
    int bins = 256;
    Mat sobel = new Mat();
    Mat sobelx = new Mat();
    Mat sobely = new Mat();
    Mat sobelxabs = new Mat();
    Mat sobelyabs = new Mat(); 
    Size gsz = new Size(5, 5);
    if(false) {
        Imgproc.Canny(image, sobel, 41, 71);
    }else {

        //Imgproc.GaussianBlur(graycopy,graycopy, gsz, 2);
        //Imgproc.dilate(image, image, kernel8);
        Imgproc.GaussianBlur(image, image, gsz, 2);


        int apertureSize = 3;
        Imgproc.Sobel(image, sobelx, CvType.CV_16S, 1, 0, apertureSize, 1, 0);
        Core.convertScaleAbs(sobelx, sobelxabs);
        Imgproc.Sobel(image, sobely, CvType.CV_16S, 0, 1, apertureSize, 1, 0);
        Core.convertScaleAbs(sobely, sobelyabs);
        Core.addWeighted(sobelxabs, 1, sobelyabs, 1, 0, sobel);
        sobel.convertTo(sobel, CvType.CV_8U);


        Mat equalized = new Mat();
        Imgproc.equalizeHist(sobel, equalized);
        Imgcodecs.imwrite(filePath + "aftersobel(eq).png", equalized);
        Imgcodecs.imwrite(filePath + "aftersobel.png", sobel);


        Mat hist = new Mat();
        List<Mat> matList = new ArrayList<Mat>();
        matList.add(sobel);
        Imgproc.calcHist(matList, new MatOfInt(0), new Mat(), hist, new MatOfInt(bins), new MatOfFloat(0f, 256f));
        float accu = 0;
        float t = (float) (nonEdgeRate * w * h);
        float bon = 0;
        float[] accutemp = new float[bins];
        for (int i = 0; i < bins; i++) {
            float tf[] = new float[1];
            hist.get(i, 0, tf);
            accu = accu + tf[0];
            accutemp[i] = accu;
            if (accu > t) {
                bon = (float) i;
                break;
            }
        }
        Imgproc.threshold(sobel, sobel, bon, 255, Imgproc.THRESH_BINARY);
        double ut = bon;
        double lt = thresholdRate * bon;


        Imgproc.Canny(image, sobel, lt, ut);
        //Imgproc.dilate(sobel, sobel, kernel2);
    }
    return sobel;
}

The filepath is the place to hold the output images. And the input image should be a gray-scale image with U8 data type. The basic principle is to rule out nonEdgeRate(60%) pixel as non-edge pixel by the brightness. A histogram is used to sort the brightness and the upper threshold will be set so that there are 60% pixels below it. The lower threshold is set by multiplying the upper threshold by the thresholdRate(0.6).

Note that the double nonEdgeRate = 0.6 and double thresholdRate = 0.6 is tuned by myself in my specific use case. Th original values are 0.7 and 0.4 separately in matlab.