Finding Patterns: Edges, Contours, and Shapes

computer-vision
edge-detection
contours
opencv
Author

Hasan

Published

January 22, 2025

Modified

August 23, 2026

0.1 The Detective Story: Teaching Computers to See Patterns

Imagine you’re a detective looking at a crime scene photo. Your brain instantly picks out: - The outline of a footprint in the mud - The edge of a broken window - The shape of a mysterious object on the table

How do you do this so effortlessly? You’re detecting edges and patterns—the fundamental building blocks of vision.

Today, we’re going to teach computers this same superpower. By the end of this post, your computer will be able to find shapes, count objects, and even detect specific patterns in any image!

0.2 The Edge Detection Revolution

Edges are where the magic happens. They’re the boundaries between different regions in an image—where one object ends and another begins. Think of them as the “outlines” in a coloring book.

0.2.1 Why Edges Matter

Before we dive into code, let’s understand why edge detection changed everything:

  • Object Recognition: Edges define object boundaries
  • Feature Extraction: Corners and curves are key features
  • Noise Reduction: Edges help separate signal from noise
  • Compression: JPEG uses edge information to compress images

0.3 Your First Edge Detector: The Canny Algorithm

The Canny edge detector is the gold standard—it’s been the go-to algorithm for over 30 years! Here’s how it works:

Code
import cv2
import numpy as np
import matplotlib.pyplot as plt

# Load and prepare image
img = cv2.imread('images/image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)

# Apply Canny edge detection
edges = cv2.Canny(gray, 50, 150)

# Display the results
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_rgb)
plt.title("Original Image")
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(gray, cmap='gray')
plt.title("Grayscale")
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(edges, cmap='gray')
plt.title("Canny Edges")
plt.axis('off')

plt.tight_layout()
plt.show()

print(f"Found edges in {np.sum(edges > 0)} pixels out of {edges.size} total pixels")

Found edges in 16716 pixels out of 752400 total pixels

🎯 Try it yourself! Open in Colab

0.3.1 Understanding the Canny Parameters

The Canny algorithm has two important parameters—think of them as sensitivity controls:

Code
def explore_canny_thresholds(image, low_thresholds, high_thresholds):
    """Explore different Canny threshold combinations"""
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    
    plt.figure(figsize=(15, 10))
    
    plot_idx = 1
    for low in low_thresholds:
        for high in high_thresholds:
            edges = cv2.Canny(gray, low, high)
            
            plt.subplot(len(low_thresholds), len(high_thresholds), plot_idx)
            plt.imshow(edges, cmap='gray')
            plt.title(f"Low: {low}, High: {high}")
            plt.axis('off')
            
            plot_idx += 1
    
    plt.tight_layout()
    plt.show()

# Test different threshold combinations
low_thresholds = [50, 100, 150]
high_thresholds = [100, 150, 200]
explore_canny_thresholds(img_rgb, low_thresholds, high_thresholds)

0.3.2 The Magic Behind Canny

Here’s what happens inside the Canny algorithm (don’t worry, OpenCV does this for you!):

  1. Blur the image to reduce noise
  2. Calculate gradients to find intensity changes
  3. Non-maximum suppression to thin the edges
  4. Double thresholding to classify edge pixels
  5. Edge tracking to connect broken edge segments

0.4 From Edges to Shapes: Contour Detection

Edges are great, but contours are even better! A contour is a curve that connects all the edge points around an object’s boundary. Think of it as tracing around objects with your finger.

Code
# Find edges first
gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 50, 150)

# Find contours
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

print(f"Found {len(contours)} contours!")

# Draw contours on the original image
img_with_contours = img_rgb.copy()
cv2.drawContours(img_with_contours, contours, -1, (0, 255, 0), 2)

# Display results
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_rgb)
plt.title("Original")
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(edges, cmap='gray')
plt.title("Edges")
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(img_with_contours)
plt.title(f"Contours ({len(contours)} found)")
plt.axis('off')

plt.tight_layout()
plt.show()
Found 42 contours!

0.4.1 Analyzing Contour Properties

Once we have contours, we can learn a lot about each shape:

Code
def analyze_contours(contours, image):
    """Analyze properties of each contour"""
    img_analysis = image.copy()
    
    for i, contour in enumerate(contours):
        # Calculate contour properties
        area = cv2.contourArea(contour)
        perimeter = cv2.arcLength(contour, True)
        
        # Skip tiny contours (probably noise)
        if area < 100:
            continue
        
        # Get bounding rectangle
        x, y, w, h = cv2.boundingRect(contour)
        
        # Calculate aspect ratio
        aspect_ratio = float(w) / h
        
        # Calculate extent (contour area / bounding rectangle area)
        rect_area = w * h
        extent = float(area) / rect_area
        
        # Draw bounding rectangle
        cv2.rectangle(img_analysis, (x, y), (x + w, y + h), (255, 0, 0), 2)
        
        # Add text with properties
        text = f"#{i}: A={int(area)}, AR={aspect_ratio:.2f}"
        cv2.putText(img_analysis, text, (x, y-10), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
        
        print(f"Contour {i}:")
        print(f"  Area: {area:.0f} pixels")
        print(f"  Perimeter: {perimeter:.0f} pixels")
        print(f"  Aspect Ratio: {aspect_ratio:.2f}")
        print(f"  Extent: {extent:.2f}")
        print()
    
    return img_analysis

# Analyze our contours
analyzed_img = analyze_contours(contours, img_rgb)

plt.figure(figsize=(12, 8))
plt.imshow(analyzed_img)
plt.title("Contour Analysis")
plt.axis('off')
plt.show()
Contour 6:
  Area: 136 pixels
  Perimeter: 76 pixels
  Aspect Ratio: 1.29
  Extent: 0.54

Contour 9:
  Area: 111 pixels
  Perimeter: 53 pixels
  Aspect Ratio: 0.81
  Extent: 0.53

Contour 10:
  Area: 165 pixels
  Perimeter: 50 pixels
  Aspect Ratio: 0.81
  Extent: 0.79

Contour 21:
  Area: 248 pixels
  Perimeter: 73 pixels
  Aspect Ratio: 0.79
  Extent: 0.54

Contour 22:
  Area: 176 pixels
  Perimeter: 1243 pixels
  Aspect Ratio: 1.12
  Extent: 0.00

Contour 23:
  Area: 24131 pixels
  Perimeter: 695 pixels
  Aspect Ratio: 1.05
  Extent: 0.62

Contour 24:
  Area: 564 pixels
  Perimeter: 126 pixels
  Aspect Ratio: 0.93
  Extent: 0.72

Contour 25:
  Area: 416 pixels
  Perimeter: 116 pixels
  Aspect Ratio: 0.83
  Extent: 0.60

Contour 26:
  Area: 246 pixels
  Perimeter: 73 pixels
  Aspect Ratio: 0.34
  Extent: 0.85

Contour 27:
  Area: 24144 pixels
  Perimeter: 705 pixels
  Aspect Ratio: 0.95
  Extent: 0.65

Contour 28:
  Area: 24115 pixels
  Perimeter: 657 pixels
  Aspect Ratio: 0.94
  Extent: 0.64

Contour 29:
  Area: 576 pixels
  Perimeter: 149 pixels
  Aspect Ratio: 0.93
  Extent: 0.69

Contour 30:
  Area: 570 pixels
  Perimeter: 127 pixels
  Aspect Ratio: 0.90
  Extent: 0.70

Contour 31:
  Area: 418 pixels
  Perimeter: 116 pixels
  Aspect Ratio: 0.83
  Extent: 0.60

Contour 32:
  Area: 619 pixels
  Perimeter: 163 pixels
  Aspect Ratio: 0.68
  Extent: 0.63

Contour 34:
  Area: 715 pixels
  Perimeter: 2951 pixels
  Aspect Ratio: 0.89
  Extent: 0.01

Contour 35:
  Area: 582 pixels
  Perimeter: 126 pixels
  Aspect Ratio: 0.93
  Extent: 0.74

Contour 36:
  Area: 584 pixels
  Perimeter: 127 pixels
  Aspect Ratio: 0.93
  Extent: 0.70

Contour 37:
  Area: 424 pixels
  Perimeter: 112 pixels
  Aspect Ratio: 0.60
  Extent: 0.58

Contour 38:
  Area: 984 pixels
  Perimeter: 123 pixels
  Aspect Ratio: 0.84
  Extent: 0.81

Contour 39:
  Area: 24210 pixels
  Perimeter: 656 pixels
  Aspect Ratio: 0.94
  Extent: 0.65

Contour 40:
  Area: 24182 pixels
  Perimeter: 699 pixels
  Aspect Ratio: 0.91
  Extent: 0.62

Contour 41:
  Area: 50164 pixels
  Perimeter: 1049 pixels
  Aspect Ratio: 2.24
  Extent: 0.65

0.5 Shape Detection: Teaching Computers Geometry

Now for the really cool part—detecting specific shapes! Let’s build a shape classifier:

Code
def classify_shape(contour):
    """Classify a contour as a specific shape"""
    # Approximate the contour to reduce number of points
    epsilon = 0.02 * cv2.arcLength(contour, True)
    approx = cv2.approxPolyDP(contour, epsilon, True)
    
    # Get number of vertices
    vertices = len(approx)
    
    # Calculate aspect ratio
    x, y, w, h = cv2.boundingRect(contour)
    aspect_ratio = float(w) / h
    
    # Classify based on number of vertices and aspect ratio
    if vertices == 3:
        return "Triangle"
    elif vertices == 4:
        if 0.95 <= aspect_ratio <= 1.05:
            return "Square"
        else:
            return "Rectangle"
    elif vertices == 5:
        return "Pentagon"
    elif vertices > 5:
        # Check if it's circular
        area = cv2.contourArea(contour)
        perimeter = cv2.arcLength(contour, True)
        circularity = 4 * np.pi * area / (perimeter * perimeter)
        
        if circularity > 0.7:
            return "Circle"
        else:
            return "Polygon"
    else:
        return "Unknown"

def detect_shapes(image):
    """Detect and classify shapes in an image"""
    # Preprocessing
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blurred, 50, 150)
    
    # Find contours
    contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # Classify shapes
    result_img = image.copy()
    shape_counts = {}
    
    for contour in contours:
        # Skip small contours
        if cv2.contourArea(contour) < 500:
            continue
        
        # Classify the shape
        shape = classify_shape(contour)
        
        # Count shapes
        shape_counts[shape] = shape_counts.get(shape, 0) + 1
        
        # Draw contour and label
        cv2.drawContours(result_img, [contour], -1, (0, 255, 0), 2)
        
        # Get centroid for text placement
        M = cv2.moments(contour)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
            cv2.putText(result_img, shape, (cx-30, cy), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
    
    return result_img, shape_counts

# Test shape detection
shapes_img, shape_counts = detect_shapes(img_rgb)

plt.figure(figsize=(12, 8))
plt.imshow(shapes_img)
plt.title("Shape Detection Results")
plt.axis('off')
plt.show()

print("Detected shapes:")
for shape, count in shape_counts.items():
    print(f"  {shape}: {count}")

Detected shapes:
  Circle: 4
  Polygon: 7

0.6 Real-World Application: Coin Counter

Let’s build something practical—a coin counting system:

Code
def count_coins(image):
    """Count circular objects (coins) in an image"""
    # Convert to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    
    # Apply median blur to reduce noise
    blurred = cv2.medianBlur(gray, 5)
    
    # Use HoughCircles to detect circular objects
    circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT,
        dp=1,
        minDist=30,
        param1=50,
        param2=30,
        minRadius=10,
        maxRadius=100
    )
    
    result_img = image.copy()
    coin_count = 0
    
    if circles is not None:
        circles = np.round(circles[0, :]).astype("int")
        coin_count = len(circles)
        
        # Draw detected circles
        for (x, y, r) in circles:
            cv2.circle(result_img, (x, y), r, (0, 255, 0), 2)
            cv2.circle(result_img, (x, y), 2, (0, 0, 255), 3)
            cv2.putText(result_img, f"Coin", (x-20, y-r-10), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
    
    return result_img, coin_count

# Test coin counting (works best with images of coins on a plain background)
coins_img, coin_count = count_coins(img_rgb)

plt.figure(figsize=(12, 8))
plt.imshow(coins_img)
plt.title(f"Coin Detection - Found {coin_count} coins")
plt.axis('off')
plt.show()

0.7 Advanced Pattern Detection: Template Matching

Sometimes you want to find a specific pattern or object. Template matching is perfect for this:

Code
def find_template(image, template, threshold=0.8):
    """Find template in image using template matching"""
    # Convert both to grayscale
    gray_img = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    gray_template = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY)
    
    # Perform template matching
    result = cv2.matchTemplate(gray_img, gray_template, cv2.TM_CCOEFF_NORMED)
    
    # Find locations where matching exceeds threshold
    locations = np.where(result >= threshold)
    
    # Draw rectangles around matches
    result_img = image.copy()
    h, w = gray_template.shape
    
    for pt in zip(*locations[::-1]):  # Switch x and y coordinates
        cv2.rectangle(result_img, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 2)
    
    return result_img, len(locations[0])

# Create a simple template (you can crop from your image)
# For demo, let's use a small patch from the original image
template = img_rgb[50:150, 50:150]  # 100x100 patch

matched_img, match_count = find_template(img_rgb, template, threshold=0.6)

plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(template)
plt.title("Template to Find")
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(img_rgb)
plt.title("Original Image")
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(matched_img)
plt.title(f"Matches Found: {match_count}")
plt.axis('off')

plt.tight_layout()
plt.show()

0.8 Building a Complete Object Detection Pipeline

Let’s combine everything into a comprehensive object detection system:

Code
class SimpleObjectDetector:
    def __init__(self):
        self.min_contour_area = 100
        self.canny_low = 50
        self.canny_high = 150
    
    def preprocess(self, image):
        """Preprocess image for better detection"""
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        return blurred
    
    def find_objects(self, image):
        """Find all objects in the image"""
        # Preprocess
        processed = self.preprocess(image)
        
        # Edge detection
        edges = cv2.Canny(processed, self.canny_low, self.canny_high)
        
        # Find contours
        contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # Filter contours by area
        valid_contours = [c for c in contours if cv2.contourArea(c) > self.min_contour_area]
        
        return valid_contours, edges
    
    def analyze_objects(self, contours, image):
        """Analyze detected objects"""
        results = []
        result_img = image.copy()
        
        for i, contour in enumerate(contours):
            # Basic properties
            area = cv2.contourArea(contour)
            perimeter = cv2.arcLength(contour, True)
            
            # Bounding rectangle
            x, y, w, h = cv2.boundingRect(contour)
            aspect_ratio = float(w) / h
            
            # Shape classification
            shape = classify_shape(contour)
            
            # Store results
            obj_info = {
                'id': i,
                'area': area,
                'perimeter': perimeter,
                'aspect_ratio': aspect_ratio,
                'shape': shape,
                'bbox': (x, y, w, h)
            }
            results.append(obj_info)
            
            # Draw on image
            cv2.drawContours(result_img, [contour], -1, (0, 255, 0), 2)
            cv2.rectangle(result_img, (x, y), (x + w, y + h), (255, 0, 0), 1)
            cv2.putText(result_img, f"{shape} #{i}", (x, y-10), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
        
        return results, result_img
    
    def detect(self, image):
        """Complete detection pipeline"""
        contours, edges = self.find_objects(image)
        results, result_img = self.analyze_objects(contours, image)
        
        return {
            'objects': results,
            'result_image': result_img,
            'edges': edges,
            'object_count': len(results)
        }

# Use the detector
detector = SimpleObjectDetector()
detection_results = detector.detect(img_rgb)

# Display results
plt.figure(figsize=(15, 10))

plt.subplot(2, 2, 1)
plt.imshow(img_rgb)
plt.title("Original Image")
plt.axis('off')

plt.subplot(2, 2, 2)
plt.imshow(detection_results['edges'], cmap='gray')
plt.title("Edge Detection")
plt.axis('off')

plt.subplot(2, 2, 3)
plt.imshow(detection_results['result_image'])
plt.title(f"Detected Objects: {detection_results['object_count']}")
plt.axis('off')

plt.subplot(2, 2, 4)
# Create a summary plot
object_shapes = [obj['shape'] for obj in detection_results['objects']]
shape_counts = {}
for shape in object_shapes:
    shape_counts[shape] = shape_counts.get(shape, 0) + 1

if shape_counts:
    shapes = list(shape_counts.keys())
    counts = list(shape_counts.values())
    plt.bar(shapes, counts)
    plt.title("Object Type Distribution")
    plt.xticks(rotation=45)
else:
    plt.text(0.5, 0.5, "No objects detected", ha='center', va='center', transform=plt.gca().transAxes)
    plt.title("No Objects Found")

plt.tight_layout()
plt.show()

# Print detailed results
print("Detection Results:")
print(f"Total objects found: {detection_results['object_count']}")
print("\nObject details:")
for obj in detection_results['objects']:
    print(f"  Object {obj['id']}: {obj['shape']}")
    print(f"    Area: {obj['area']:.0f} pixels")
    print(f"    Aspect Ratio: {obj['aspect_ratio']:.2f}")
    print(f"    Bounding Box: {obj['bbox']}")

Detection Results:
Total objects found: 30

Object details:
  Object 0: Polygon
    Area: 260 pixels
    Aspect Ratio: 1.81
    Bounding Box: (662, 543, 29, 16)
  Object 1: Polygon
    Area: 119 pixels
    Aspect Ratio: 0.81
    Bounding Box: (696, 542, 13, 16)
  Object 2: Polygon
    Area: 142 pixels
    Aspect Ratio: 0.77
    Bounding Box: (733, 541, 17, 22)
  Object 3: Circle
    Area: 169 pixels
    Aspect Ratio: 0.72
    Bounding Box: (648, 541, 13, 18)
  Object 4: Polygon
    Area: 152 pixels
    Aspect Ratio: 1.12
    Bounding Box: (746, 522, 19, 17)
  Object 5: Polygon
    Area: 137 pixels
    Aspect Ratio: 0.94
    Bounding Box: (691, 517, 17, 18)
  Object 6: Polygon
    Area: 320 pixels
    Aspect Ratio: 1.45
    Bounding Box: (658, 517, 32, 22)
  Object 7: Polygon
    Area: 251 pixels
    Aspect Ratio: 0.79
    Bounding Box: (713, 476, 19, 24)
  Object 8: Polygon
    Area: 206 pixels
    Aspect Ratio: 1.31
    Bounding Box: (818, 423, 21, 16)
  Object 9: Circle
    Area: 24126 pixels
    Aspect Ratio: 1.09
    Bounding Box: (423, 420, 202, 185)
  Object 10: Polygon
    Area: 124 pixels
    Aspect Ratio: 0.67
    Bounding Box: (786, 417, 12, 18)
  Object 11: Polygon
    Area: 142 pixels
    Aspect Ratio: 1.75
    Bounding Box: (833, 395, 28, 16)
  Object 12: Circle
    Area: 181 pixels
    Aspect Ratio: 0.78
    Bounding Box: (818, 393, 14, 18)
  Object 13: Polygon
    Area: 248 pixels
    Aspect Ratio: 0.75
    Bounding Box: (834, 349, 18, 24)
  Object 14: Polygon
    Area: 560 pixels
    Aspect Ratio: 0.93
    Bounding Box: (702, 317, 27, 29)
  Object 15: Polygon
    Area: 414 pixels
    Aspect Ratio: 0.86
    Bounding Box: (675, 317, 24, 28)
  Object 16: Polygon
    Area: 562 pixels
    Aspect Ratio: 1.00
    Bounding Box: (643, 317, 28, 28)
  Object 17: Polygon
    Area: 558 pixels
    Aspect Ratio: 0.96
    Bounding Box: (611, 317, 27, 28)
  Object 18: Rectangle
    Area: 243 pixels
    Aspect Ratio: 0.36
    Bounding Box: (596, 317, 10, 28)
  Object 19: Polygon
    Area: 412 pixels
    Aspect Ratio: 0.86
    Bounding Box: (568, 317, 24, 28)
  Object 20: Circle
    Area: 24141 pixels
    Aspect Ratio: 0.95
    Bounding Box: (331, 317, 188, 198)
  Object 21: Polygon
    Area: 616 pixels
    Aspect Ratio: 0.72
    Bounding Box: (539, 309, 26, 36)
  Object 22: Polygon
    Area: 574 pixels
    Aspect Ratio: 0.96
    Bounding Box: (665, 259, 27, 28)
  Object 23: Polygon
    Area: 575 pixels
    Aspect Ratio: 0.93
    Bounding Box: (611, 258, 27, 29)
  Object 24: Polygon
    Area: 422 pixels
    Aspect Ratio: 0.62
    Bounding Box: (642, 253, 21, 34)
  Object 25: Circle
    Area: 971 pixels
    Aspect Ratio: 0.89
    Bounding Box: (576, 251, 32, 36)
  Object 26: Polygon
    Area: 177 pixels
    Aspect Ratio: 0.75
    Bounding Box: (396, 181, 18, 24)
  Object 27: Circle
    Area: 24217 pixels
    Aspect Ratio: 0.94
    Bounding Box: (741, 109, 188, 199)
  Object 28: Polygon
    Area: 198 pixels
    Aspect Ratio: 0.94
    Bounding Box: (331, 109, 188, 199)
  Object 29: Polygon
    Area: 50177 pixels
    Aspect Ratio: 2.25
    Bounding Box: (423, 19, 414, 184)

0.9 Your Challenge: Build a Practical Application

Now it’s your turn! Try building one of these applications:

0.9.1 🎯 Challenge 1: Document Page Counter

Code
def count_pages(image):
    """Count rectangular pages/documents in an image"""
    # Your code here!
    # Hint: Look for large rectangular contours
    pass

0.9.2 🎯 Challenge 2: Parking Space Detector

Code
def detect_parking_spaces(image):
    """Detect empty parking spaces (rectangular regions)"""
    # Your code here!
    # Hint: Look for rectangular shapes of a certain size
    pass

0.9.3 🎯 Challenge 3: Playing Card Detector

Code
def detect_playing_cards(image):
    """Detect playing cards in an image"""
    # Your code here!
    # Hint: Cards are rectangular with a specific aspect ratio
    pass

0.10 What’s Coming Next?

In our next adventure, “Feature Magic: What Makes Images Unique”, we’ll explore:

  • Keypoint detection (finding interesting points)
  • Feature descriptors (describing what makes each point unique)
  • Feature matching (finding the same object in different images)
  • Panorama stitching (combining multiple photos)

You’ve just learned to find shapes and patterns—next, we’ll learn to recognize and match them!

0.11 Key Takeaways

  • Edges are fundamental to computer vision
  • Canny edge detection is the gold standard algorithm
  • Contours help us find and analyze shapes
  • Shape classification uses geometric properties
  • Template matching finds specific patterns
  • Combining techniques creates powerful applications
TipHands-On Lab

Ready to detect patterns in your own images? Try the complete interactive notebook: Pattern Detection Lab

Upload photos and watch your computer find shapes, count objects, and detect patterns!


You’ve just taught computers to see patterns and shapes! This is a huge milestone—you’re now working with the same techniques used in industrial quality control, medical imaging, and autonomous vehicles.

Reuse

Citation

BibTeX citation:
@online{2025,
  author = {, Hasan},
  title = {Finding {Patterns:} {Edges,} {Contours,} and {Shapes}},
  date = {2025-01-22},
  langid = {en}
}
For attribution, please cite this work as:
Hasan. 2025. “Finding Patterns: Edges, Contours, and Shapes.” January 22.
Free email course · 7 days

Industrial Vision Foundations

AI can write the code, but someone still has to know which method fits a real inspection line. One short lesson a day for 7 days on the classical computer vision techniques that still run production systems.

Runnable code in every lesson. No spam, unsubscribe any time.

Questions, corrections or feedback?

Found a mistake, or want a topic covered next? Leave a comment below (sign in with GitHub), or write to hasanme1412@gmail.com. I read everything.