杰瑞科技汇

python approxpoly

Of course! Let's dive into approxPolyDP, one of the most useful functions in OpenCV for shape detection.

python approxpoly-图1
(图片来源网络,侵删)

The name is a bit of a mouthful: cv2.approxPolyDP. It stands for "Approximate Polygon using Douglas-Peucker algorithm".

What is approxPolyDP?

In simple terms, approxPolyDP takes a shape (represented by a contour) and simplifies it by reducing the number of vertices, while still preserving its overall shape.

Imagine you have a hand-drawn square. It's not a perfect square; it has slight wiggles and imperfections. approxPolyDP can smooth out those wiggles and give you a perfect 4-sided polygon.


The Core Concept: The Douglas-Peucker Algorithm

This function isn't just a random simplifier; it uses a well-known and effective algorithm.

python approxpoly-图2
(图片来源网络,侵删)
  1. Start with Extremes: The algorithm starts by drawing a straight line between the first and last points of your shape.
  2. Find the Furthest Point: It then finds the point in your shape that is the farthest away from this straight line.
  3. Make a Decision:
    • If this farthest point is closer than a certain epsilon value (a tolerance you provide), it means the line segment is a good enough approximation. The algorithm is done, and it returns just the first and last points.
    • If the point is farther than epsilon, it means the line segment is not a good approximation. The shape "bends" too much at that point.
  4. Recursively Simplify: The algorithm then recursively applies the same process to the two new segments: one from the first point to the farthest point, and another from the farthest point to the last point.

It continues this process until all parts of the shape are simplified within the epsilon tolerance.


How to Use approxPolyDP in Python

The function signature is:

cv2.approxPolyDP(curve, epsilon, closed)

Parameters:

  • curve: This is your input contour. It should be a list of points, like the one you get from cv2.findContours(). It's a NumPy array of shape N x 1 x 2, where N is the number of points.
  • epsilon: This is the most important parameter. It's the maximum distance between the original curve and the approximated curve. A small epsilon will result in a shape very close to the original (with many vertices). A large epsilon will result in a very simplified shape (with few vertices).
  • closed: A boolean flag. If True, the approximated curve is closed (i.e., the first and last points are connected). For shapes like polygons, this should almost always be True.

Return Value:

It returns the approximated contour, which is a new list of points.


Practical Example: Detecting Shapes

This is the classic use case for approxPolyDP. Let's build a complete example.

python approxpoly-图3
(图片来源网络,侵删)

Goal:

Detect and identify different shapes (triangle, square, pentagon, circle) in an image.

Step 1: Setup and Image Preparation

First, let's create a sample image with different shapes. You can also use your own image.

import cv2
import numpy as np
# Create a blank image
width, height = 400, 400
image = np.zeros((height, width, 3), dtype=np.uint8) + 255 # White background
# Draw shapes
# Triangle
pts_tri = np.array([[100, 50], [150, 150], [50, 150]], np.int32)
cv2.fillPoly(image, [pts_tri], (255, 0, 0)) # Blue
# Square
pts_sq = np.array([[200, 50], [300, 50], [300, 150], [200, 150]], np.int32)
cv2.fillPoly(image, [pts_sq], (0, 255, 0)) # Green
# Pentagon
pts_penta = np.array([[100, 250], [50, 320], [100, 350], [150, 350], [200, 320]], np.int32)
cv2.fillPoly(image, [pts_penta], (0, 0, 255)) # Red
# Circle
cv2.circle(image, (300, 300), 50, (0, 255, 255), -1) # Yellow
# Save the image for reference
cv2.imwrite('shapes.png', image)
# Show the image
cv2.imshow('Original Shapes', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Step 2: Find Contours

Now, we'll find the outlines of these shapes.

# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply a threshold to get a binary image
# (In this case, our image is already mostly black and white, but it's good practice)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# Find contours
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draw all contours on a copy of the original image
contour_image = image.copy()
cv2.drawContours(contour_image, contours, -1, (0, 0, 0), 3)
cv2.imshow('Contours Found', contour_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Step 3: Approximate and Identify Shapes

This is where the magic happens. We'll loop through each contour, approximate it, and identify the shape based on the number of vertices.

# Loop through each contour
for contour in contours:
    # Calculate the perimeter of the contour
    peri = cv2.arcLength(contour, True)
    # Approximate the contour
    # The epsilon is a percentage of the perimeter. 2% is a good starting point.
    # You may need to adjust this value depending on your image.
    approx = cv2.approxPolyDP(contour, 0.02 * peri, True)
    # Draw the approximated contour on the original image
    # This will show the simplified shape
    cv2.drawContours(image, [approx], 0, (0, 255, 0), 3)
    # Get the number of vertices in the approximated polygon
    vertices = len(approx)
    # Identify the shape based on the number of vertices
    if vertices == 3:
        shape_name = "Triangle"
    elif vertices == 4:
        # Check if it's a square or a rectangle
        x, y, w, h = cv2.boundingRect(approx)
        aspect_ratio = float(w) / h
        if 0.95 <= aspect_ratio <= 1.05:
            shape_name = "Square"
        else:
            shape_name = "Rectangle"
    elif vertices == 5:
        shape_name = "Pentagon"
    elif vertices > 8:
        # A circle will have many vertices after approximation
        shape_name = "Circle"
    else:
        shape_name = "Polygon"
    # Get the center of the contour to place the text
    M = cv2.moments(contour)
    if M["m00"] != 0:
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])
    else:
        cX, cY = 0, 0
    # Put the shape name on the image
    cv2.putText(image, shape_name, (cX - 40, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
# Display the final result
cv2.imshow("Shape Detection", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Key Takeaways and Tips

  1. epsilon is Crucial: The epsilon value is the most critical parameter. A good rule of thumb is to start with a small percentage of the contour's perimeter, like 02 * peri (2%). If your shapes are not being simplified enough, increase the epsilon. If they are being oversimplified (e.g., a pentagon becomes a triangle), decrease it.
  2. Use cv2.arcLength: The perimeter (peri) is essential for setting a meaningful epsilon. Using an absolute value for epsilon (e.g., 10) might work for one image but fail completely for another. Scaling it with the perimeter makes your code more robust.
  3. closed = True: For detecting closed shapes like polygons, always set this to True.
  4. Combine with Other Functions: approxPolyDP is rarely used alone. It's part of a pipeline:
    • cv2.cvtColor -> Grayscale
    • cv2.threshold or cv2.Canny -> Binary/Edge image
    • cv2.findContours -> Find the outlines
    • cv2.approxPolyDP -> Simplify the outlines
    • cv2.boundingRect or cv2.minAreaRect -> Analyze the simplified shape further.
分享:
扫描分享到社交APP
上一篇
下一篇