Introduction
In the previous tutorial we saw several OpenCV functions which can help us generate kernel matrices for several filters like Sobel and Gaussian. In this tutorial we will see two more OpenCV functions which we will use to apply Sobel and Scharr filters. We use these filters in order to detected edges in an image. An edge is an area, where the intensity of the color changes sharply. For example, if we have black and white image, these filters will get the areas where the image transitions from white to black and vice versa.
Sobel operator
Applying a Sobel operator is basically applying a specific kernel over the whole image. We can apply the operator on both the X and the Y directions. For each one of them we have separate matrices, which will contain negative and positive values. These kernel matrices look like this:

In the places where we have a big difference in the intensities of the colors, applying the above kernels will produce a bigger number as a result. In the places, where the intensities are not so different, the result of the convolution will be close to 0. So, in the first case we will have white pixel values and in the second – black. More information you can find in the following page. Also, check this video, which has some really nice explanation of the filter.
Applying the Sobel operator
Now, let us see how to use the OpenCV functions in order to apply the Sobel operation.
// Apply the Sobel filter to an image void applySobelFilter() { // Path to the input image std::string l_pathToInputImage{ "../Resources/youngBlood.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_outputImage{}; // Create the Sobel results Mat l_xSobel{}; Mat l_ySobel{}; constexpr int c_depth = -1; constexpr int c_size = 3; constexpr double c_scale = 1.0; constexpr double c_delta = 0.0; // Apply the Sobel filters Sobel(l_image, l_xSobel, c_depth, 0, 1, c_size, c_scale, c_delta); Sobel(l_image, l_ySobel, c_depth, 1, 0, c_size, c_scale, c_delta); // Convert back to CV_8U Mat l_xAbsSobel{}; Mat l_yAbsSobel{}; convertScaleAbs(l_xSobel, l_xAbsSobel); convertScaleAbs(l_ySobel, l_yAbsSobel); // Combine both X and Y direction results constexpr double c_alpha = 0.5; constexpr double c_beta = 0.5; addWeighted(l_xAbsSobel, c_alpha, l_yAbsSobel, c_beta, 0, l_outputImage); // 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 with the loading of the image. I am loading one grayscale image. I do all the required checks. Then I create the output matrix, which will hold the result.
Next, I create two matrices. One will hold the result for applying the Sobel operator in the X direction and the other one will hold the result for the Y direction. If you remember from the previous tutorial, we created separate matrices for each direction. I also define some variables, which you can go ahead and play with in order to get different results.
The OpenCV functions
The function, which I need to call in order to apply the Sobel operator is Sobel() function. I provide it with the input image matrix and the output image matrix as the first two arguments. The third argument is the depth, which I leave to -1 in order to use the depth of the input image. Next, I have two arguments, which define the way we will create the Sobel kernel. They can only get the values of 0 and 1 and in the same time only one of them could be one, while the other is zero. The one that is zero will mean that for the respective direction the Sobel will be applied. I already explained this in the previous tutorial.
Next argument is the size of the kernel. The only available values here are 1, 3, 5 and 7. Then we have scale. I leave it to 1 so no scaling in our case. After that, we have the delta value, which we can apply to each pixel value after we finish processing it. I leave it to zero. And the last argument borderType I also leave with its default value.
We have two function calls to Sobel(). If you look at the dx and dy arguments you will see that the first time we calculate the Sobel operator on the X direction and the second time – on the Y direction. This way we will have it in both directions.
Scaling and combining
The results we have will not be in 8bit resolution. We need to convert them to this resolution in order to display them. For this need, we use the convertScaleAbs() function, which will scale and convert the input matrix to CV_8U format. It takes the input matrix, which is the result of the Sobel() function call and puts the result into the second argument – the output matrix.
Once we have converted both the X and the Y direction results it is time to combine them into one image. For this case we use the addWeighted() function, which combines two images into one. We have already seen this function in a previous tutorial.
The result
At the end we display the combined results. Below is how it looks like on my side. I am using one grayscale image. You can see that on every region, where we have some sharp change of the intensities, there are white pixels. The rest is black pixels. You can also test with colorful images.

Spatial gradient
There is one more OpenCV function, which can be used instead of the Sobel() function – spatialGradient(). Instead of calling the Sobel function twice to calculate for both the X and the Y directions, we can call this function. It needs a CV_8UC1 image so when calling the imread function we need to use IMREAD_GRAYSCALE. Here is the code.
// Apply the Sobel filter to an image using spatial gradient void applySpatialGradient() { // Path to the input image std::string l_pathToInputImage{ "../Resources/youngBlood.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_outputImage{}; // Create the Sobel results Mat l_xSobel{}; Mat l_ySobel{}; constexpr int c_size = 3; // Apply the spatial gradient spatialGradient(l_image, l_xSobel, l_ySobel, c_size); // Convert back to CV_8U Mat l_xAbsSobel{}; Mat l_yAbsSobel{}; convertScaleAbs(l_xSobel, l_xAbsSobel); convertScaleAbs(l_ySobel, l_yAbsSobel); // Combine both X and Y direction results constexpr double c_alpha = 0.5; constexpr double c_beta = 0.5; addWeighted(l_xAbsSobel, c_alpha, l_yAbsSobel, c_beta, 0, l_outputImage); // 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); }
As you can see we only call the spatialGradient() function once and we provide it with the input image matrix plus the matrices for the X and Y directions results. Then we do everything else the same as in the first example.
The result looks like this. You can check if it is better than the first one.

Scharr operator as an alternative to Sobel
Let us look at one more operator, which is used to detected edges. It is the Scharr operator. This operator is similar to the Sobel, but it gives more accurate results. Actually, in the description of the Sobel operator you can find information about the Scharr filter.
Let us look at the code for the Scharr filter.
// Apply the Scharr filter to an image void applyScharrFilter() { // Path to the input image std::string l_pathToInputImage{ "../Resources/youngBlood.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_outputImage{}; // Create the Scharr results Mat l_xScharr{}; Mat l_yScharr{}; constexpr int c_depth = -1; constexpr double c_scale = 1.0; constexpr double c_delta = 0.0; // Apply the Scharr filters Scharr(l_image, l_xScharr, c_depth, 0, 1, c_scale, c_delta); Scharr(l_image, l_yScharr, c_depth, 1, 0, c_scale, c_delta); // Convert back to CV_8U Mat l_xAbsScharr{}; Mat l_yAbsScharr{}; convertScaleAbs(l_xScharr, l_xAbsScharr); convertScaleAbs(l_yScharr, l_yAbsScharr); // Combine both X and Y direction results constexpr double c_alpha = 0.5; constexpr double c_beta = 0.5; addWeighted(l_xAbsScharr, c_alpha, l_yAbsScharr, c_beta, 0, l_outputImage); // 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); }
The code is the same as for the Sobel filter. The only difference is that we use the Scharr() function (and I have renamed some variables). Everything else is the same.
The Scharr() function takes almost the same arguments as the Sobel() function. The only difference is that it does not use size argument.
If you look at the result of the computation, you will see that the Scharr filter detects more regions with change in the intensity. If you need better accuracy, use this function.

Conclusion
In this tutorial we saw two OpenCV functions – Sobel() and Scharr(). We use them to detect edges or regions, where the intensity of the colors changes sharply. They are very useful function and we will most probably see them again in some of the next tutorials.
As always, the code is available in GitHub and as an archive below.
Next time we will see yet another filter – Laplace filter. See ya.

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.