杰瑞科技汇

Python中findContours如何正确使用与参数解析?

Of course! Let's dive deep into cv2.findContours() in Python. This is one of the most fundamental and powerful functions in OpenCV for analyzing shapes in an image.

Python中findContours如何正确使用与参数解析?-图1
(图片来源网络,侵删)

What are Contours?

Think of a contour as a curve joining all the continuous points (along the boundary) that have the same color or intensity. In simple terms, contours are outlines of objects in an image.

Important Note: findContours() works on binary images. So, before you can find contours, you almost always need to convert your image to grayscale and then apply a threshold or a Canny edge detector to get a binary image.


The cv2.findContours() Function

The function signature is:

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy

Let's break down the most important parameters:

Python中findContours如何正确使用与参数解析?-图2
(图片来源网络,侵删)
  1. image: The source image, which must be 8-bit single-channel (i.e., a binary/grayscale image). If you have a color image, you must first convert it to grayscale.
  2. mode: This tells the function how to retrieve the contours. It's a crucial parameter.
    • cv2.RETR_EXTERNAL: Retrieves only the outermost contours (child contours are ignored). This is useful if you only care about the outlines of objects.
    • cv2.RETR_LIST: Retrieves all contours but doesn't establish any hierarchical relationships.
    • cv2.RETR_CCOMP: Retrieves all contours and organizes them into a two-level hierarchy: external contours are in the top level, and internal "holes" are in the second level.
    • cv2.RETR_TREE: Retrieves all contours and reconstructs a full family hierarchy (nested contours). This is the most common and powerful mode.
  3. method: This specifies the approximation method for the contours.
    • cv2.CHAIN_APPROX_NONE: Stores all the contour points. This is memory-intensive but gives you the most accurate shape.
    • cv2.CHAIN_APPROX_SIMPLE: Compresses horizontal, vertical, and diagonal segments, leaving only their end points. This saves a lot of memory and is generally what you want. For example, a rectangular contour would be represented by just 4 points instead of thousands.
  4. Return Values:
    • contours: A Python list of all the contours found. Each individual contour is a NumPy array of (x, y) coordinates of boundary points.
    • hierarchy: A NumPy array (optional) containing information about the image topology. It tells you which contour is a child of which, which is a parent, etc. This is only useful if you used a RETR_ mode that builds hierarchy.

A Complete Step-by-Step Example

Let's walk through a complete example to find and draw contours on a simple image.

Step 1: Setup and Imports

First, make sure you have OpenCV installed: pip install opencv-python numpy

Then, import the necessary libraries.

import cv2
import numpy as np

Step 2: Create a Sample Image

For this example, we'll create a blank image and draw some shapes on it. This ensures we know exactly what to expect.

# Create a black image
image = np.zeros((500, 800, 3), dtype="uint8")
# Draw a white rectangle
cv2.rectangle(image, (50, 50), (250, 250), (255, 255, 255), -1)
# Draw a white filled circle
cv2.circle(image, (400, 150), 80, (255, 255, 255), -1)
# Draw a white line
cv2.line(image, (600, 50), (750, 250), (255, 255, 255), 15)
# Save the image or display it
cv2.imshow("Original Image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Step 3: Preprocess the Image for Contour Detection

As mentioned, findContours needs a binary image. We'll convert to grayscale and then apply a threshold.

# 1. Convert to Grayscale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 2. Apply Thresholding
# Any pixel value > 127 will become 255 (white), others become 0 (black).
# The second value (255) is the value to use for the maxVal.
_, threshold_image = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)
# Display the thresholded image
cv2.imshow("Threshold Image", threshold_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Step 4: Find the Contours

Now we call the cv2.findContours() function.

# Find contours
# The image must be 8-bit single-channel
# We use RETR_TREE to get all contours and their hierarchy
# We use CHAIN_APPROX_SIMPLE to save memory
contours, hierarchy = cv2.findContours(threshold_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

Note: In older versions of OpenCV (pre-3.0), the function returned three values: image, contours, hierarchy. The new version returns only two. The image is modified in-place, so you don't get it back.

Step 5: Draw the Contours

The cv2.drawContours() function is used to draw the detected contours back onto an image.

# Create a copy of the original image to draw on
contour_image = image.copy()
# Draw all contours
# -1 means draw all contours
# (0, 255, 0) is the color (Green)
# 3 is the thickness of the contour lines
cv2.drawContours(contour_image, contours, -1, (0, 255, 0), 3)
# Display the image with contours
cv2.imshow("Contours Found", contour_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

You should see an image with green outlines around the rectangle, circle, and line.


Practical Application: Counting Objects

A very common use case is to count distinct objects in an image. The key here is to use cv2.RETR_EXTERNAL, which ensures you only get the outer boundary of each object, ignoring any holes.

Let's use an image with multiple objects.

# 1. Load an image (replace 'objects.png' with your image path)
# For this example, let's create one again
image_objects = np.zeros((400, 600, 3), dtype="uint8")
cv2.circle(image_objects, (150, 150), 60, (255, 255, 255), -1)
cv2.rectangle(image_objects, (300, 100), (450, 250), (255, 255, 255), -1)
cv2.circle(image_objects, (500, 300), 50, (255, 255, 255), -1)
# 2. Preprocess
gray_objects = cv2.cvtColor(image_objects, cv2.COLOR_BGR2GRAY)
_, threshold_objects = cv2.threshold(gray_objects, 127, 255, cv2.THRESH_BINARY)
# 3. Find contours using RETR_EXTERNAL
contours_external, _ = cv2.findContours(threshold_objects, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 4. Count and draw
print(f"Number of objects found: {len(contours_external)}")
# Draw each contour with a different color and label
output_image = image_objects.copy()
for i, contour in enumerate(contours_external):
    # Draw the contour
    cv2.drawContours(output_image, [contour], -1, (0, 255, 0), 3)
    # 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 a label on the object
    cv2.putText(output_image, str(i+1), (cX - 10, cY + 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
cv2.imshow("Object Counting", output_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

This script will print Number of objects found: 3 and display the image with each object numbered.

Summary of Best Practices

  1. Always Preprocess: Convert to grayscale and apply thresholding or Canny edge detection before calling findContours.
  2. Choose the Right mode:
    • Use cv2.RETR_EXTERNAL for simple object counting.
    • Use cv2.RETR_TREE if you need to understand nested shapes (e.g., a shape with a hole inside it).
  3. Use CHAIN_APPROX_SIMPLE: It's almost always the right choice for saving memory without losing important shape information.
  4. Draw on a Copy: When using cv2.drawContours(), draw on a copy of your original image to avoid modifying the source data.
  5. Check the Result: Always print the number of contours (len(contours)) to make sure the function is working as expected.
分享:
扫描分享到社交APP
上一篇
下一篇