Image Processing Best Practices — C++ Part 2

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:

Image for post
Image for post

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.

Sobel filter

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.

Image for post
Image for post

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.

Quick Recap

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:

  1. 0–255 values pixels.
  2. Gray Scale images (one channel).
  3. Using data raw pointer provided by cv::Mat .
  4. 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:

Image for post
Image for post

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.

sobel_x filter

Code Explanation:

  1. Line 3: Check that both input and output have just one channel.
  2. Line 6: We define the kernel as a vector of integers of size 9 and we initialize it with the filter kernel values.
  3. Line 9–10: Get raw pointers to the data for the input and output cv::Mat.
  4. 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.

Image for post
Image for post

Silence… !

Image for post
Image for post

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:

Image for post
Image for post

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.

Image for post
Image for post

The result:

Image for post
Image for post

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.

Bye Bye:

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 :) .

Written by

Software Engineer at Microsoft. Former Computer Vision Engineer at Shiseido Group 資生堂. Interested in drones, CV, ML and DL.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store