解决PIL绘制填充多边形时填充超出轮廓的问题
解决PIL多边形填充溢出轮廓的问题
问题分析
使用PIL的ImageDraw.polygon()同时设置fill和outline时,填充区域会轻微超出轮廓边界,这是因为PIL的填充逻辑与轮廓绘制的坐标计算规则不一致导致的。要实现填充完全不超出轮廓的效果,可以通过先精确填充多边形内部像素,再单独绘制轮廓的方式解决,优先使用numpy工具实现像素级的精确控制。
基础解决方案代码
import numpy as np from PIL import Image, ImageDraw def point_in_polygon(point, vertices): """射线法判断点是否在多边形内部(numpy实现)""" x, y = point n = len(vertices) inside = False px, py = vertices[0] for i in range(n + 1): qx, qy = vertices[i % n] if ((py > y) != (qy > y)): x_intersect = (y - py) * (qx - px) / (qy - py) + px if x <= x_intersect: inside = not inside px, py = qx, qy return inside def draw_clean_polygon(vertices, image_size=(150,150), bg_color='black', fill_color='white', outline_color='blue', outline_width=1): # 初始化numpy图像数组 bg_rgb = np.array(bg_color, dtype=np.uint8) img_array = np.full((image_size[1], image_size[0], 3), bg_rgb, dtype=np.uint8) # 遍历所有像素,判断是否在多边形内部并填充 fill_rgb = np.array(fill_color, dtype=np.uint8) for y in range(image_size[1]): for x in range(image_size[0]): if point_in_polygon((x, y), vertices): img_array[y, x] = fill_rgb # 转换为PIL图像,绘制轮廓 image = Image.fromarray(img_array) draw = ImageDraw.Draw(image) draw.line(vertices, fill=outline_color, width=outline_width) return image # 测试示例 vertices = [(44, 124), (50, 48), (74, 46), (73, 123), (44, 124)] # 生成并保存图像 clean_image = draw_clean_polygon(vertices) clean_image.save(r'clean_filled.png')
代码说明
- 点-in-多边形判断:用射线法实现精确的像素归属判断,确保只有顶点定义的多边形内部像素被填充。
- 像素级填充:遍历图像所有像素,仅对多边形内部像素设置填充色,从根源避免PIL填充的溢出问题。
- 独立绘制轮廓:填充完成后用
draw.line()单独绘制轮廓,保证轮廓与填充区域的边界完全对齐。
大尺寸图像优化方案
如果处理大尺寸图像,遍历单个像素效率较低,可以改用numpy向量化操作批量判断:
def vectorized_point_in_polygon(xs, ys, vertices): """向量化版本的点-in-多边形判断""" n = len(vertices) inside = np.zeros(xs.shape, dtype=bool) px, py = vertices[0] for i in range(n + 1): qx, qy = vertices[i % n] # 筛选y坐标在py和qy之间的像素 y_cross = (py > ys) != (qy > ys) # 计算射线与边的x交点 with np.errstate(divide='ignore', invalid='ignore'): x_intersect = (ys - py) * (qx - px) / (qy - py) + px # 更新内部像素标记 mask = y_cross & (xs <= x_intersect) inside[mask] = ~inside[mask] px, py = qx, qy return inside def draw_clean_polygon_fast(vertices, image_size=(150,150), bg_color='black', fill_color='white', outline_color='blue', outline_width=1): # 创建网格坐标 xs = np.arange(image_size[0]) ys = np.arange(image_size[1]) X, Y = np.meshgrid(xs, ys) # 批量判断所有像素是否在多边形内部 inside_mask = vectorized_point_in_polygon(X, Y, vertices) # 批量填充内部像素 bg_rgb = np.array(bg_color, dtype=np.uint8) fill_rgb = np.array(fill_color, dtype=np.uint8) img_array = np.where(inside_mask[..., None], fill_rgb, bg_rgb) # 转换为PIL图像并绘制轮廓 image = Image.fromarray(img_array) draw = ImageDraw.Draw(image) draw.line(vertices, fill=outline_color, width=outline_width) return image
该版本利用numpy的向量化特性,避免逐像素循环,处理大尺寸图像时效率会显著提升。
内容的提问来源于stack exchange,提问作者Fnord
相关产品推荐
相关产品推荐

