Introduction
So far, we have seen several edge detection mechanisms – The Sobel/Scharr filters and the Laplace filter. Now we will see yet another powerful edge detection mechanism – the Canny edge detector. We will get some basic information about it and we will see the OpenCV functions which we use in order to apply it to any image. As always, I will provide you with links for more detailed information.
Canny edge detector
The Canny edge detection algorithm contains several steps. Firstly, we apply a Gaussian filter in order to remove any noise. Next step is to find the intensity gradients of the image. For this purpose, we can use the Sobel operator. After we have found both the Gx and the Gy derivatives, we go ahead and calculate the gradient strength and the direction using the following formulas:

As a next step in the Canny edge detection algorithm, we apply a non-maximum suppression. This operation will remove pixels which are not part of the edge and will leave only a thin line. After this is over, we go to the final step – applying a hysteresis. We provide two threshold values – lower and upper. If the pixel intensity is higher than the upper threshold, we add this pixel to the edge. In case of pixel being below the lower threshold, we remove it. Finally, if the pixel is between the two thresholds, we check if it is connected to another pixel, which is above the upper threshold. In case it is connect, then we add it to the edge. If not – we remove it.
Sounds complicated, right? Good thing is that OpenCV provides us with a ready to use function. Let us see how to use it.
Please, find more details about the Canny edge detector here and in this video.
Applying the Canny edge detector to an image
I have developed two functions, which show how to use the OpenCV ready functions. There is one overloaded version of the function so we will see both of them. Let us start with the first version. See the code below, followed by the explanation.
Using the normal OpenCV function
// Apply the Canny Edge Detector to an image void applyCanny() { // Path to the input image std::string l_pathToInputImage{ "../Resources/building.jpg" }; // Create an object to hold the image data of the first image Mat l_image; // Read the image date from a file with no change to color scheme l_image = imread(l_pathToInputImage, IMREAD_UNCHANGED); // Check if we have read the first image data correctly if (!l_image.data) { std::cout << "No image data \n"; return; } // Create the output image matrices Mat l_noiseRemovedImage{}; Mat l_outputImage{}; // Remove any noise GaussianBlur(l_image, l_noiseRemovedImage, Size(3, 3), 0, 0, BORDER_DEFAULT); // Create the Canny constants to play with constexpr int c_kernelSize = 3; constexpr double c_lowerThreshold = 30.0; constexpr double c_upperThreshold = c_lowerThreshold * 3.0; // Apply the canny edge detection Canny(l_noiseRemovedImage, l_outputImage, c_lowerThreshold, c_upperThreshold, c_kernelSize, false); // Display the input image namedWindow("Input", WINDOW_NORMAL); cv::imshow("Input", l_image); // Display the result image namedWindow("Result", WINDOW_NORMAL); cv::imshow("Result", l_outputImage); }
We will start as usual with the loading of an image from a file. Then we create two matrices – one will hold the result of applying a Gaussian filter and the other one will hold the result of the Canny edge detection. Then we apply the Gaussian blur to the image in order to remove any noise from it. We already saw how to apply this function in a previous tutorial. Any noise which can affect our final results is undesirable.
Coming next is setting of some constants, which we will use in the Canny function. Here I am selecting the lower and upper thresholds. They suggest to keep the upper threshold three times bigger than the lower one. And I am following this suggestion.
The OpenCV function
Now, it is time to apply the Canny edge detection. The function to do this is Canny(). It takes the input image as a first argument. Here, I provide the image with the removed noise from the previous steps. Second argument is the output image. The next two arguments are the lower and upper thresholds. Following is the kernel size. The last argument is one flag, which decides what formula to use in order to calculate the gradient magnitude. You can play with both versions and see the results.
Also, play with the threshold values and see how the end result changes.
The result
Finally, we display the detected edges. You can see on my side what I get. Notice how thin the edge lines are. We are getting most of the edges, where we have a change in the color intensity. If you play with the threshold values you can detect more edges.
On my input picture you can see where we have regions with change of the intensity. Our algorithm has detected most of these regions.
Also, notice that I am using a colorful image. You can try to update the imread function to use IMREAD_GRAYSCALE instead and work with a grayscale image. Most of the examples in the Web work with grayscale images. You can get different results with a grayscale image.

Using the overloaded function
OpenCV provides one more function to apply the Canny edge detection mechanism. This function applies the same operations but takes different arguments. As you can see from its description from the link, it takes the first derivatives for X and Y as the first two arguments. Then we need to provide the output matrix to hold the edges, the two thresholds and also the flag, stating whether to use a more accurate or the default gradient magnitude formula. Here is the code.
// Apply the Canny Edge Detector to an image void applyCannyWithDerivatives() { // Path to the input image std::string l_pathToInputImage{ "../Resources/building.jpg" }; // Create an object to hold the image data of the first image Mat l_image; // Read the image date from a file with no change to color scheme l_image = imread(l_pathToInputImage, IMREAD_GRAYSCALE); // Check if we have read the first image data correctly if (!l_image.data) { std::cout << "No image data \n"; return; } // Create the output image matrices Mat l_noiseRemovedImage{}; Mat l_outputImage{}; // Remove any noise GaussianBlur(l_image, l_noiseRemovedImage, Size(3, 3), 0, 0, BORDER_DEFAULT); // Create the Canny constants to play with constexpr int c_kernelSize = 3; constexpr double c_lowerThreshold = 30.0; constexpr double c_upperThreshold = c_lowerThreshold * 3.0; // Create the gradients results Mat l_xGradient{}; Mat l_yGradient{}; // Apply the spatial gradient spatialGradient(l_noiseRemovedImage, l_xGradient, l_yGradient, c_kernelSize); // Apply the canny edge detection Canny(l_xGradient, l_yGradient, l_outputImage, c_lowerThreshold, c_upperThreshold, false); // Display the input image namedWindow("Input", WINDOW_NORMAL); cv::imshow("Input", l_image); // Display the result image namedWindow("Result", WINDOW_NORMAL); cv::imshow("Result", l_outputImage); }
This function is almost similar to the previous one. The difference here is that after we smooth the image, we get its first derivatives by calling the spatialGradient() function. Then we call the overloaded Canny() function. Finally, we display the results.
Notice here that in the imread function I have selected to load the image in IMREAD_GRAYSCALE mode. This time I will be working with a grayscale image. The results however should not be too different from the previous one.

Conclusion
In this tutorial we saw the functions, which OpenCV provides in order to apply the Canny edge detection algorithm and find the edges in one image. We saw how to use them and what are the results from their application.
The code you saw here is available in GitHub and as an archive below.
Next, we will see one very important algorithm – the Hough Line Detection algorithm.

Passionate developer, loving husband and caring father. As an introvert programming is my life. I work as a senior software engineer in a big international company. My hobbies include playing computer games (mostly World of Warcraft), watching TV series and hiking.