You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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')

代码说明

  1. 点-in-多边形判断:用射线法实现精确的像素归属判断,确保只有顶点定义的多边形内部像素被填充。
  2. 像素级填充:遍历图像所有像素,仅对多边形内部像素设置填充色,从根源避免PIL填充的溢出问题。
  3. 独立绘制轮廓:填充完成后用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:12:45