\[ \begin{align}\begin{aligned}\newcommand\blank{~\underline{\hspace{1.2cm}}~}\\% Bold symbols (vectors) \newcommand\bs[1]{\mathbf{#1}}\\% Differential \newcommand\dd[2][]{\mathrm{d}^{#1}{#2}} % use as \dd, \dd{x}, or \dd[2]{x}\\% Poor man's siunitx \newcommand\unit[1]{\mathrm{#1}} \newcommand\num[1]{#1} \newcommand\qty[2]{#1~\unit{#2}}\\\newcommand\per{/} \newcommand\squared{{}^2} \newcommand\cubed{{}^3} % % Scale \newcommand\milli{\unit{m}} \newcommand\centi{\unit{c}} \newcommand\kilo{\unit{k}} \newcommand\mega{\unit{M}} % % Percent \newcommand\percent{\unit{{\kern-4mu}\%}} % % Angle \newcommand\radian{\unit{rad}} \newcommand\degree{\unit{{\kern-4mu}^\circ}} % % Time \newcommand\second{\unit{s}} \newcommand\s{\second} \newcommand\minute{\unit{min}} \newcommand\hour{\unit{h}} % % Distance \newcommand\meter{\unit{m}} \newcommand\m{\meter} \newcommand\inch{\unit{in}} \newcommand\foot{\unit{ft}} % % Force \newcommand\newton{\unit{N}} \newcommand\kip{\unit{kip}} % kilopound in "freedom" units - edit made by Sri % % Mass \newcommand\gram{\unit{g}} \newcommand\g{\gram} \newcommand\kilogram{\unit{kg}} \newcommand\kg{\kilogram} \newcommand\grain{\unit{grain}} \newcommand\ounce{\unit{oz}} \newcommand\pound{\unit{lbs}} % % Temperature \newcommand\kelvin{\unit{K}} \newcommand\K{\kelvin} \newcommand\celsius{\unit{{\kern-4mu}^\circ C}} \newcommand\C{\celsius} \newcommand\fahrenheit{\unit{{\kern-4mu}^\circ F}} \newcommand\F{\fahrenheit} % % Area \newcommand\sqft{\unit{sq\,\foot}} % square foot % % Volume \newcommand\liter{\unit{L}} \newcommand\gallon{\unit{gal}} % % Frequency \newcommand\hertz{\unit{Hz}} \newcommand\rpm{\unit{rpm}} % % Voltage \newcommand\volt{\unit{V}} \newcommand\V{\volt} \newcommand\millivolt{\milli\volt} \newcommand\mV{\milli\volt} \newcommand\kilovolt{\kilo\volt} \newcommand\kV{\kilo\volt} % % Current \newcommand\ampere{\unit{A}} \newcommand\A{\ampere} \newcommand\milliampereA{\milli\ampere} \newcommand\mA{\milli\ampere} \newcommand\kiloampereA{\kilo\ampere} \newcommand\kA{\kilo\ampere} % % Resistance \newcommand\ohm{\Omega} \newcommand\milliohm{\milli\ohm} \newcommand\kiloohm{\kilo\ohm} % correct SI spelling \newcommand\kilohm{\kilo\ohm} % "American" spelling used in siunitx \newcommand\megaohm{\mega\ohm} % correct SI spelling \newcommand\megohm{\mega\ohm} % "American" spelling used in siunitx % % Capacitance \newcommand\farad{\unit{F}} \newcommand\F{\farad} \newcommand\microfarad{\micro\farad} \newcommand\muF{\micro\farad} % % Inductance \newcommand\henry{\unit{H}} \newcommand\H{\henry} \newcommand\millihenry{\milli\henry} \newcommand\mH{\milli\henry} % % Power \newcommand\watt{\unit{W}} \newcommand\W{\watt} \newcommand\milliwatt{\milli\watt} \newcommand\mW{\milli\watt} \newcommand\kilowatt{\kilo\watt} \newcommand\kW{\kilo\watt} % % Energy \newcommand\joule{\unit{J}} \newcommand\J{\joule} % % Composite units % % Torque \newcommand\ozin{\unit{\ounce}\,\unit{in}} \newcommand\newtonmeter{\unit{\newton\,\meter}} % % Pressure \newcommand\psf{\unit{psf}} % pounds per square foot \newcommand\pcf{\unit{pcf}} % pounds per cubic foot \newcommand\pascal{\unit{Pa}} \newcommand\Pa{\pascal} \newcommand\ksi{\unit{ksi}} % kilopound per square inch \newcommand\bar{\unit{bar}} \end{aligned}\end{align} \]

Apr 28, 2026 | 1077 words | 11 min read

6.2.1. Task 1#

Learning Objectives#

  • Implement a Sobel Filter from scratch

  • Understand how thresholding is used in Sobel filtering

Introduction#

In the previous task, you applied a Gaussian filter to smooth an image and reduce noise. In this task, you will build a Sobel filter that uses the smoothed image to detect and highlight edges.

How do we find the edges in an image?#

Now that we have blurred the images using the Gaussian filter from Section 5.2.3 we will be constructing a Sobel filter to highlight the edges within the blurred image.

The Sobel filter works by passing the image through 2 different Sobel kernels (one for the \(x\) direction and one for the \(y\) direction). These kernels work in the same way that the Gaussian kernel worked in the previous task, but the values are chosen in such a way as to calculate what is called the gradient in the respective direction.

You can think of the gradient as the measure of slope in the pixel values in a given direction. To illustrate how these kernels actually behave on an image let’s start with a horizontal and vertical line demonstration :

Simple 22 by 11 sample image containing one horizontal line and one vertical line.

Fig. 6.1 Original 22x11 Horizontal / Vertical#

We will now apply the \(\text{Sobel}_x\) kernel to show the gradient in the \(x\) direction:

Result of applying the Sobel X kernel to the sample image, emphasizing vertical edges.

Fig. 6.2 Sobel X Kernel#

Since this filter should only highlight the areas where the pixels change from left to right it makes sense that the horizontal line essentially disappears (except for where it starts and stops) while the edges of the original vertical line are emphasized.

Applying the \(\text{Sobel}_y\) kernel to the original image, we should see the areas where pixels change from top to bottom:

Result of applying the Sobel Y kernel to the sample image, emphasizing horizontal edges.

Fig. 6.3 Sobel Y Kernel#

As we can see, each of the kernels are able to highlight the changes in their respective directions, so in order to find all the edges in an image we simply need to combine these results, which gives us :

Combined Sobel X and Sobel Y output for the sample image, showing all edges detected.

Fig. 6.4 Combined Sobel Kernels#

Note

This is ONLY the output of combining the two Sobel kernels’ outputs. A Sobel filter would still have the final step of pixel thresholding so that all pixels would be either white or black indicating whether that pixel IS or IS NOT an edge.

How do we construct the Sobel filter?#

The Sobel filter starts with the two directional kernels, \(\text{Sobel}_x\) and \(\text{Sobel}_y\), which are defined as follows:

(6.1)#\[\begin{split}\text{Sobel}_x = \left(\begin{array}{rrr} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}\right) \quad \text{Sobel}_y = \left(\begin{array}{rrr} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{array}\right)\end{split}\]

We will apply each of these kernels to the original image to get two images, one that has been processed with the \(\text{Sobel}_x\) kernel (known as the x-gradient image) and one that has been processed with the \(\text{Sobel}_y\) kernel (known as the y-gradient image). This process is the same as the one used for the Gaussian kernel, but the negative values in these kernels require that both the image and kernel arrays use floating-point arithmetic.

Combining the gradient images#

To combine these two images, we will go pixel by pixel and compute the gradient magnitude which we will set as the pixel value in the combined image:

(6.2)#\[\text{gradient_magnitude} = \sqrt{gx^2 + gy^2}\]

Where :

  • \(gx\) is the pixel value in the x-gradient image.

  • \(gy\) is the pixel value in the y-gradient image.

  • \(\text{gradient_magnitude}\) is the corresponding pixel’s value in the combined image.

Note

This formula CAN result in a \(\text{gradient_magnitude}\) that exceeds \(255\), which is not allowed for a grayscale image. You will need to ensure that values above \(255\) are clipped down to \(255\).

Thresholding#

The last step of this filter is to set a pixel threshold. The combined output of the Sobel kernels will likely result in some pixels having a value that is not strictly black or white, this means that the pixel is somewhat of an edge pixel. To fix this, we set a threshold which will tell us the minimum value a pixel must have to be considered an edge.

Iterating through each pixel of the combined Sobel kernel image we apply the following logic:

(6.3)#\[\begin{split}\text{If pixel_value} \ge \text{threshold} :\\ \text{pixel_value} = 255 \\\\ \text{If pixel_value} < \text{threshold} :\\ \text{pixel_value} = 0\end{split}\]

Thresholding can have a major impact on the output image from the Sobel filter, and a poorly chosen value can completely disrupt the whole point of the filter in the first place. To showcase this lets look at the following outputs :

WITHOUT Thresholding :

Sobel filter output before thresholding, showing a grayscale edge image.

Fig. 6.5 Sobel Filter Without Thresholding#

Threshold value set to \(15\):

Sobel filter output using a low threshold of 15, causing too many pixels to be classified as edges.

Fig. 6.6 Sobel Filter Low Thresholding#

Threshold value set to \(240\):

Sobel filter output using a high threshold of 240, causing too few pixels to be classified as edges.

Fig. 6.7 Sobel Filter High Thresholding#

Threshold value set to \(50\):

Sobel filter output using a threshold of 50, showing a balanced edge detection.

Fig. 6.8 Sobel Filter Good Thresholding#

As we can see, low thresholding results in far too many pixels being classified as edges, high thresholding results in not enough pixels being classified as edges, and in both cases we lose most of the details that were present in the original image.

For the images that we will be trying to process, the traffic signs, the threshold value we found to be a good balance of edge detail across the board was \(50\) for this dataset.

Note

Threshold values are entirely dependent on the images that you are processing, so it is always best to find a value that best represents YOUR data set whenever using the Sobel Filter.

Task Instructions#

Deliverable Reminder

Create a flowchart of your algorithm and save it as tp2_team_1_teamnumber.pdf. Start your program from a copy of the ENGR133_Python_Template.py template and name it tp2_team_1_teamnumber.py.

Write a function named sobel_filter that takes in a blurred grayscale image (produced by gaussian_filter from Section 5.2.3) and returns a uint8 NumPy array of the Sobel-filtered image. The function should:

  1. Initialize the \(\text{Sobel}_x\) and \(\text{Sobel}_y\) kernels.

  2. Convert the image and kernel arrays to float type to handle the negative kernel values:

    data_array = np.array(data, dtype=float)
    
  3. Pad the image with zeros (as in Section 5.2.3), then compute the x-gradient and y-gradient images by applying the respective Sobel kernels.

  4. Combine the gradient images using the gradient magnitude formula ((6.2)). Clip any values above \(255\) down to \(255\).

  5. Apply a threshold of \(50\) to each pixel in the combined image ((6.3)).

Organize your code to use functions that break up the task into manageable pieces.

Main Function#

Note

You will need the load_img and rgb_to_grayscale functions developed in Section 15.2.2 and the gaussian_filter function developed in Section 5.2.3. Copy them into your program.

In your main function, you will need to do the following:

  1. Ask the user for an image filename (for example, ref_col_a.png).

  2. Load the image using load_img.

  3. Convert the image to grayscale using rgb_to_grayscale.

  4. Apply gaussian_filter with \(\sigma = 1\).

  5. Apply sobel_filter to the blurred grayscale image.

  6. Display and save the edge-detected image.

Use the files provided in Table 6.3 to test your code.

Table 6.3 Image Files#

Image File Name

Description

ref_col_a.png

A color image

ref_col_b.png

A color image

Sample Output#

Use the values in Table 6.4 below to test your program.

Table 6.4 Test Cases#

Case

image_path

1

ref_col_a.png

2

ref_col_b.png

Ensure your program’s output matches the provided samples exactly. This includes all characters, white space, and punctuation. In the samples, user input is highlighted like this for clarity, but your program should not highlight user input in this way.

Case 1 Sample Output

$ python3 tp2_team_1_teamnumber.py Enter the path to the image file: ref_col_a.png

ref col a edge

Fig. 6.9 Case_1_ref_col_a_edge.png#

Case 2 Sample Output

$ python3 tp2_team_1_teamnumber.py Enter the path to the image file: ref_col_b.png

ref col b edge

Fig. 6.10 Case_2_ref_col_b_edge.png#

Table 6.5 Deliverables#

Deliverables

Description

tp2_team_1_teamnumber.pdf

Flowchart(s) for this task.

tp2_team_1_teamnumber.py

Your completed Python code.