
Lecture 6
Computer Science Department, Colorado School of Mines
By the end of this lecture, you should be able to:
Distinguish image edges from meaningful object boundaries and identify different physical causes of edges.
Approximate image derivatives using finite differences and explain how derivative filters combine differentiation and smoothing.
Compute gradient magnitude and direction, and relate them to edge strength and orientation.
Explain why differentiation amplifies noise and how Gaussian smoothing, Gaussian derivatives, and the Laplacian of Gaussian help detect edges.
Explain the stages of Canny edge detection, including non-maximum suppression and hysteresis thresholding.
Apply edge detection in OpenCV and predict how smoothing and threshold choices affect the result.


Which contours separate the elephants from the background or from each other?
Would you also mark the wrinkles, shadows, and grass as object boundaries?

Human annotations emphasize meaningful contours, while omitting much of the texture.
We use shape and context to interpret boundaries, even where the intensity contrast is weak.

An edge detector responds to local changes in image intensity.
These changes can come from object boundaries, but also from texture, shadows, and surface markings.

Edge strength does not necessarily correspond to our perception of boundaries.
A strong intensity change may occur within an object, such as a wrinkle or shadow.
An important object boundary may have weak contrast when neighboring regions have similar brightness.

Which result better matches the boundaries you perceive? Why?
Should every visible change in brightness count as an edge?

Where does the road end, and where do the snow-covered surroundings begin?
You can recognize vehicles ahead, but can you trace their complete outlines?
Do the strongest edges identify the most important boundaries?

Tissue engineering
Identify and count blood vessels.

Autonomous vehicles
Detect lane markings and road boundaries.

Image inpainting
Define a region to remove and fill.

Behavioral genetics
Trace worm contours to study shape and motion.

Semantic scene segmentation
Delineate regions such as roads, vehicles, and trees.

Interactive image editing
Follow object boundaries for selection and editing.

Can you find edges caused by different physical properties of the scene?
At the wall–floor junction, the surface changes direction: its surface normal changes.
Differently oriented surfaces can receive different amounts of light, producing an intensity edge.
Across the wall’s outline, the visible surface changes from the nearby wall to the distant landscape.
This is an occlusion boundary: the wall blocks our view of what lies behind it.
The black outlines and colored paint create edges within the same wall surface.
The surface’s reflectance changes, without requiring a change in depth or orientation.
The shadow boundary separates sunlit and shaded parts of the same ground surface.
The illumination changes, even though the surface continues across the edge.

Surface height represents grayscale intensity at each pixel.
Edges occur where intensity changes sharply over a short distance. Look for the steep transition along the mountain silhouette.

Edge detection
Identifying locations in an image where intensity changes sharply over a short distance.
How could we detect sharp changes in image intensity?
Take derivatives.
How do we differentiate a discrete image—or any other sampled signal?
Use finite differences.
For samples spaced one pixel apart:
\[ \left.\frac{dI}{dx}\right|_{x=n} \approx \frac{I[n+1]-I[n-1]}{2}. \]


Forward difference definition:
\[ I'(x)= \lim_{\Delta x\to 0} \frac{I(x+\Delta x)-I(x)}{\Delta x} \]
Central difference definition:
\[ I'(x)= \lim_{\Delta x\to 0} \frac{I(x+\Delta x)-I(x-\Delta x)} {2\Delta x} \]
With one-pixel spacing, the forward difference is
\[ I'[n]\approx I[n+1]-I[n] \]
The centered difference is
\[ I'[n]\approx \frac{I[n+1]-I[n-1]}{2} \]



Each Sobel kernel is the product of a column vector and a row vector:

| Filter | Column vector | Row vector |
|---|---|---|
| Sobel x | Vertical weighted smoothing | Horizontal derivative |
| Sobel y | Vertical derivative | Horizontal weighted smoothing |
Sobel combines differentiation in one direction with smoothing in the perpendicular direction.

How are these filters related? How could we build a derivative filter larger than \(3\times3\)?

\[ \begin{aligned} I_x &= h_x\, \otimes \,I\\ I_y &= h_y\,\otimes \,I \end{aligned} \]
\[ \underbrace{ \nabla I= \begin{bmatrix} I_x\\ I_y \end{bmatrix} }_{\text{Gradient}} \qquad \underbrace{ M=\sqrt{I_x^2+I_y^2} }_{\text{Magnitude}} \qquad \underbrace{ \theta=\operatorname{atan2}(I_y,I_x) }_{\text{Direction}} \]
Direction is undefined where \(M=0\).
Sobel filters for intensity changes in the x and y directions:

Filter the image with each kernel to obtain two derivative images: \(I_x\) and \(I_y\).

The two derivatives form a vector at every pixel:
\[ \nabla I= \begin{bmatrix} I_x\\ I_y \end{bmatrix}, \qquad M=\|\nabla I\|=\sqrt{I_x^2+I_y^2}. \]

Use both signed derivatives to determine the direction:
\[ \theta=\operatorname{atan2}(I_y,I_x). \]


How does the gradient direction relate to the orientation of edges in the image?
# Convert the color image to floating-point grayscale.
I = cv2.cvtColor(I_gradient_color, cv2.COLOR_BGR2GRAY)
I = I.astype(np.float32) / 255.0
# Normalized Sobel filters for x and y derivatives.
h_grad_x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]], dtype=np.float32) / 8
h_grad_y = h_grad_x.T.copy()
# Filter the image; -1 preserves the floating-point input type.
Ix = cv2.filter2D(I, -1, h_grad_x, borderType=cv2.BORDER_REPLICATE)
Iy = cv2.filter2D(I, -1, h_grad_y, borderType=cv2.BORDER_REPLICATE)
# Gradient magnitude.
M = np.sqrt(Ix**2 + Iy**2)
# Gradient direction in degrees.
theta = np.degrees(np.arctan2(Iy, Ix))
# For display, hide directions where the gradient is weak.
threshold = 0.05 * M.max()
theta[M <= threshold] = np.nan
How can we distinguish the edge from noise?

What could we do before taking the derivative?

We can combine smoothing and differentiation into one filter.
\[ \frac{d}{dx}\bigl(G_\sigma * I\bigr) = \left(\frac{dG_\sigma}{dx}\right)*I, \qquad *\text{ denotes convolution}. \]

\[ d_{\mathrm{right}}=I[n+1]-I[n], \qquad d_{\mathrm{left}}=I[n]-I[n-1]. \]
\[ I''[n]\approx d_{\mathrm{right}}-d_{\mathrm{left}} = I[n+1]-2I[n]+I[n-1]. \]

\[ \nabla^2 I= \frac{\partial^2 I}{\partial x^2} + \frac{\partial^2 I}{\partial y^2}. \]
Smooth first, then take the second derivative—or combine both operations into one filter.


\[ G_\sigma(x,y)= \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}} \]
\[ \frac{\partial G_\sigma}{\partial x} = -\frac{x}{\sigma^2}G_\sigma \]
\[ \nabla^2G_\sigma = \frac{x^2+y^2-2\sigma^2}{\sigma^4}G_\sigma \]
In the derivative kernel images, gray represents zero, white positive values, and black negative values.

Which boundaries stand out, and which texture details also produce responses?
# Convert the color image to floating-point grayscale.
image_color = cv2.imread("assets/06_edge_detection/rockies_sunset.jpg")
I = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
I = I.astype(np.float32) / 255.0
# Construct a 2D Gaussian from a 1D Gaussian.
sigma = 5.0
radius = int(4 * sigma)
g = cv2.getGaussianKernel(2 * radius + 1, sigma).astype(np.float32)
G = g @ g.T
# Pixel coordinates relative to the kernel center.
coordinates = np.arange(-radius, radius + 1, dtype=np.float32)
X, Y = np.meshgrid(coordinates, coordinates)
# Gaussian x derivative
Gx = -(X / sigma**2) * G
# Laplacian
LoG = ((X**2 + Y**2 - 2 * sigma**2) / sigma**4) * G
LoG -= LoG.mean() # Ensure zero response to constant intensity.
# Filter the image
I_smooth = cv2.filter2D(I, -1, G)
Ix = cv2.filter2D(I, -1, Gx)
I_log = cv2.filter2D(I, -1, LoG)Can we use image gradient to localize edges and reduce them to thin boundaries?
How can we obtain thin, connected edges while suppressing noise?
Canny Edge Detection Algorithm
The output of the Canny Edge Detection Algorithm is a binary edge map.


\[ M=\sqrt{I_x^2+I_y^2}, \qquad \theta=\operatorname{atan2}(I_y,I_x) \]

Responses often form a thick ridge. Which pixels should represent the edge?
Non-max supression keeps a pixel only if its gradient magnitude is a local maximum along the gradient direction.

The retained values are still gradient magnitudes, not a binary edge map.
The comparison locations may fall between pixel centers:
\[ (x_\pm,y_\pm)=(x\pm\cos\theta,\ y\pm\sin\theta). \]
Bilinear interpolation estimates the magnitude from four neighboring pixels.
First, interpolate horizontally:
\[ \text{Top}=0.75(2)+0.25(6)=3 \]
\[ \text{Bottom}=0.75(4)+0.25(8)=5 \]
Then, interpolate vertically:
\[ M(0.25,0.5)=0.5(3)+0.5(5)=4 \]
It is a weighted average: closer pixels contribute more.


Which of these responses should we keep?
Apply user-provided low & high thresholds to the magnitude (after non-maximum suppression).

Should every weak pixel become an edge?

The output is a binary edge map: retained edge pixels are 1, and all other pixels are 0.

Connectivity provides evidence that a weak response belongs to an edge.

Which detected edges correspond to object boundaries, and which come from surface texture or illumination?

The 2 thresholds are fixed across the examples above.
Which scale best matches the boundaries you want to detect?
# Each setting is: (sigma, low, high).
settings = [
(1.0, 20, 50),
(3.0, 20, 50),
(1.0, 40, 100)]
outputs = []
# I_u8 is the grayscale image in uint8 format.
for sigma, low, high in settings:
blurred = cv2.GaussianBlur(
I_u8, (0, 0), sigmaX=sigma
)
result = cv2.Canny(
blurred,
threshold1=low,
threshold2=high,
apertureSize=3,
L2gradient=True
)
outputs.append(result)
sigma controls the explicit Gaussian smoothing before Canny.apertureSize=3 selects 3×3 Sobel derivatives; L2gradient=True uses \(\sqrt{I_x^2+I_y^2}\).Edges are local intensity changes. They can arise from changes in depth, surface orientation, reflectance, or illumination. They do not always correspond to object boundaries.
Image derivatives can be computed by filtering. Gradient magnitude measures change strength, while gradient direction points across the local edge.
Smoothing reduces sensitivity to noise. Larger \(\sigma\) suppresses more detail and can merge nearby boundaries.
First-derivative extrema and second-derivative zero crossings provide ways to locate intensity transitions.
Canny produces a binary edge map: Gaussian derivatives, gradient magnitude and direction, non-maximum suppression, then hysteresis thresholding.
Non-maximum suppression thins responses across edges. Hysteresis keeps weak responses connected to strong ones.
Parameter Choices Matter
Choose the smoothing scale and thresholds for the boundaries you want to detect. No single setting captures every meaningful boundary while rejecting all texture and noise.
Computer Vision; Colorado School of Mines | Kaveh Fathian