It took me a lot of time to decide what is the best intro for this article. I should have published this article more than a year ago and I just want to say:
This article is a continuation to the first one. Please go back and check it out if you have not already, since it heavily depends on it.
We are going to cover the following in this article:
1. Implementation for Sobel filter through X axis (Vertically).
2. Implementation for Sobel filter through Y axis (Horizontally).
3.Implementation for Sobel filter through X and Y together.
The main usage of Sobel filter is for Edge Detection. It does that by calculating the gradient of the image at each pixel which results in finding the largest increase from light to dark pixels and the rate of change. Edge can be defined as a set of contiguous pixel positions where a sudden change of the intensity values occur in the pixel. So:
Sobel Filter -> Edge Detection -> Object Boundaries.
The edges can be found by convolving 3x3 Sobel kernel through the image. The convolution process is done for every pixel in the image by multiplying each of the Sobel kernel values with the corresponding pixel values from the image, then summing up the values and replacing the source pixel with the result as shown in the example figure below.
Regarding boundary pixels, where we cannot apply the convolution operation, you have either of the two following options:
1. Expand the image by adding/padding zeros to boundaries while applying the filter.
2. Keep the original values for the boundary pixels.
The image is stored in memory as a contiguous array. For example, if the image resolution is 25*25 and it has 3 channels (RGB) and the data type is integer (0–255), then the total image size in memory will be:
img_size_in_byte = number_of_channels * img_width*img_height = 25*25*3 = 1875.
So, if your image is stored in unsigned (integer/char) array, you can access it like:
Please, refer to the first article for further details.
For simplicity, our implementation for Sobel filter in this tutorial will deal with:
- 0–255 values pixels.
- Gray Scale images (one channel).
- Using data raw pointer provided by cv::Mat .
- Boundary pixel values will be set to zeros (if the input is BW, then you can copy the values to the output — Try to add that to the implementation ^_^ ).
Someone might complain and ask for more complicated things like supporting RGB images. OK, you got it, it is going to be the topic of our next article where we are going to apply Guassian Blur filter (link in the bottom :) ).
Sobel Filter through X axis
The 3x3 kernel for applying the Sobel filter on X-axis is:
If you are interested in how this kernel is calculated, check this.
Our sobel_x function will have two parameters of the type cv::Mat and they both must be one channel image (GrayScale).
Take few minutes and try to understand the code yourself before going to the explanation.
- Line 3: Check that both input and output have just one channel.
- Line 6: We define the kernel as a vector of integers of size 9 and we initialize it with the filter kernel values.
- Line 9–10: Get raw pointers to the data for the input and output cv::Mat.
- Line 11–30: Loop though the pixels and calculate the new values (line 27).
1. Line 14: Condition to check if the pixel is a boundary pixel and set its value to zero.
2. Line 21: Loop through kernel pixels values. Here we used two loops to make it easier to track the index for the corresponding pixel in the image. Challenge yourself and try to implement it using one loop.
3. Line 21–22: k_row and k_col ranges are from -1 to +1 if the kernel size is 3.
4. Line 23: summing up the multiplication values to update the pixel value when the loop (kernel loop) is finished at line 27.
*Note:* Referring to input.step*(row+k_row)+col+k_col on Line 23. If you are not able to understand this at all, then you need to go and check part 1 (or drop a comment below :D ). The additional part here is to consider the kernel indices so that we added the k_row and k_col.
Sobel Filter through Y axis
What do you think the difference will be here? YES, you are right!
The only difference is the kernel and everything else should remain the same.
Sobel Filter Horizontally & Vertically
Simply, we use both kernels to calculate the gradient horizontally and vertically. Then, we calculate the gradient magnitude using the following:
Where Gx is the pixel value after applying the filter through the x-axis and Gy is the corresponding one for the y-axis.
The best part is always the result part ^_^ . Let us now run our Sobel filter implementation and run the OpenCV one to compare the results.
Hmmm, the results are pretty close but not exactly the same? This is because the OpenCV function might be using a different kernel. The most important part is to get the edges for the objects and it is clear that our ImageOperator Sobel can do that.
One final note: the solutions I have provided in this article may not be fully optimized. Our goal is to practice together, so do not hesitate to suggest any modifications that can be done. Also, feel free to give any ideas or share any concerns.
Check out Part 3 of this series :) .