如何用Python从PNG中仅提取拐角顶点数据?
Hey there! Let's fix your issue where your current PIL code extracts every non-transparent pixel (including all the middle points of line segments) when you only need the corner vertices. I'll walk you through two practical solutions—one using OpenCV (great for shape detection) and another sticking with PIL if you prefer that library.
Why Your Current Code Isn't Working
Your existing code simply collects every pixel that's not the transparent white background. Line segments are made of hundreds of consecutive non-transparent pixels, so this approach pulls in all of them instead of just the turning points (corners) you care about.
Solution 1: Use OpenCV for Contour & Polygon Approximation
OpenCV has built-in tools to detect image contours and simplify them down to their key vertices, which is perfect for this task. Here's how to implement it:
First, install OpenCV if you haven't already:
pip install opencv-python
Then use this code:
import cv2 import numpy as np def extract_corner_vertices(filename): # Load the image with alpha channel support img = cv2.imread(filename, cv2.IMREAD_UNCHANGED) # Handle transparent backgrounds: mask out fully transparent areas if img.shape[2] == 4: alpha_channel = img[:, :, 3] _, transparency_mask = cv2.threshold(alpha_channel, 0, 255, cv2.THRESH_BINARY) # Keep only non-transparent parts of the RGB channels img = cv2.bitwise_and(img[:, :, :3], img[:, :, :3], mask=transparency_mask) # Convert to grayscale for contour detection gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Binarize the image: turn non-background pixels white, background black _, binary_img = cv2.threshold(gray_img, 240, 255, cv2.THRESH_BINARY_INV) # Find external contours in the binary image contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) corner_vertices = [] for contour in contours: # Simplify the contour to a polygon (this removes middle line points) # Epsilon controls how "tight" the approximation is—adjust based on your image epsilon = 0.01 * cv2.arcLength(contour, True) approximated_polygon = cv2.approxPolyDP(contour, epsilon, True) # Convert vertices to your desired format (scaled x*5, y*5) for point in approximated_polygon: x, y = point[0] # Grab the color of the vertex from the original image b, g, r = img[y, x] corner_vertices.append({"x": x*5, "y": y*5, "color": (r, g, b)}) return corner_vertices
How This Works:
- Transparency Handling: We mask out any fully transparent parts of the image so they don't interfere with contour detection.
- Binarization: Converts the image to black and white to clearly separate your lines from the background.
- Contour Detection: Finds the outline of your drawn shapes.
- Polygon Approximation: The
cv2.approxPolyDPfunction takes the full contour (all line pixels) and simplifies it to only the corner vertices by merging straight line segments. Theepsilonvalue lets you adjust how precise the approximation is—smaller values mean closer matches to the original shape.
Solution 2: Stick with PIL (Neighborhood Analysis)
If you want to keep using PIL, you can check each non-transparent pixel's neighborhood to determine if it's a corner. A corner point will have a different pattern of neighboring non-transparent pixels compared to a middle line point.
Here's the code:
from PIL import Image def is_corner(pixel_access, x, y, img_width, img_height): # Check all 8 surrounding neighbors of the current pixel neighbor_directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] non_transparent_neighbors = [] for dx, dy in neighbor_directions: nx = x + dx ny = y + dy # Make sure we don't go out of image bounds if 0 <= nx < img_width and 0 <= ny < img_height: if pixel_access[nx, ny] != (255, 255, 255, 0): non_transparent_neighbors.append((dx, dy)) # Rule out middle line points: they have exactly 2 opposite neighbors if len(non_transparent_neighbors) == 2: (dx1, dy1), (dx2, dy2) = non_transparent_neighbors # Check if neighbors are directly opposite (e.g., left/right, up/down, diagonal opposite) if dx1 == -dx2 and dy1 == -dy2: return False # All other cases are corners return True def imageProcess(filename): corner_data = [] with Image.open(filename) as im: px = im.load() width, height = im.size for x in range(width): for y in range(height): if px[x, y] != (255, 255, 255, 0): if is_corner(px, x, y, width, height): corner_data.append({"x": x*5, "y": y*5, "color": px[x, y]}) return corner_data
How This Works:
- The
is_cornerfunction checks the 8 pixels around the current point. - A middle line pixel (like on a straight horizontal line) will have exactly two neighbors: one to the left and one to the right (opposite directions). We filter these out.
- Any pixel that doesn't fit this pattern (e.g., 3 neighbors for a sharp corner, or 2 neighbors that aren't opposite) is marked as a corner.
Which Solution to Choose?
- Use OpenCV if your shapes are made of straight lines (like polygons). It's faster and more accurate for this use case, especially with complex shapes.
- Use PIL if you need a lightweight solution without adding new libraries, or if you're working with irregular, freehand-style corners (adjust the
is_cornerlogic if needed for your specific image).
内容的提问来源于stack exchange,提问作者Oliver Dimes

