如何生成自相交多边形相交区域为1的二值掩码?
自相交多边形转二值掩码(相交区域填充)的解决方法
问题背景
需要将自相交多边形转换为相交区域为1的二值掩码,但使用PIL的ImageDraw.polygon或polygon2mask工具生成的结果不符合预期:生成的掩码按非零环绕规则填充,而预期是相交区域(交叉次数为奇数的区域)被标记为1。
核心原因
PIL的ImageDraw.polygon默认使用非零环绕填充规则,而我们需要的是奇偶填充规则(Even-Odd Rule)——该规则会填充多边形交叉次数为奇数的区域,正好匹配自相交多边形相交区域的标记需求。
可行解决方案
方案1:使用OpenCV实现奇偶规则填充
OpenCV的fillPoly函数支持指定填充规则,直接设置cv2.FILLRULE_EVEN_ODD即可实现预期效果。
代码示例:
import numpy as np import cv2 from PIL import Image def polygons_to_mask_even_odd(poly_str, shape): # 解析多边形点 def parse_point(point_str): x, y = map(float, point_str.split(",")) return (int(x), int(y)) points = [parse_point(p) for p in poly_str.split(";")] # 转换为OpenCV需要的格式:(num_polygons, num_points, 2) pts = np.array([points], dtype=np.int32) # 创建空掩码(OpenCV的shape是(高,宽),对应输入的(宽,高)反转) mask = np.zeros(shape[::-1], dtype=np.uint8) # 使用奇偶规则填充 cv2.fillPoly(mask, pts, color=1, lineType=cv2.LINE_AA, fillRule=cv2.FILLRULE_EVEN_ODD) return mask # 测试代码 poly = "30,20;30,60;60,60;60,30;20,30;20,40;50,40;50,50;40,50;40,20" mask = polygons_to_mask_even_odd(poly, (80, 80)) # 显示结果 Image.fromarray(mask * 255).show()
方案2:使用Shapely处理自相交多边形
Shapely库可以正确解析自相交多边形的内部区域(自动处理为多个简单多边形的并集),再通过栅格化生成掩码。
先安装依赖:
pip install shapely rasterio
代码示例:
import numpy as np from shapely.geometry import Polygon from rasterio.features import rasterize from PIL import Image def polygons_to_mask_shapely(poly_str, shape): # 解析点 def parse_point(point_str): x, y = map(float, point_str.split(",")) return (x, y) points = [parse_point(p) for p in poly_str.split(";")] # 创建自相交多边形,Shapely会自动处理为有效几何对象 poly = Polygon(points) # 修复无效多边形(自相交情况) if not poly.is_valid: poly = poly.buffer(0) # 生成掩码:设置坐标原点为左上角,像素大小1x1 transform = rasterio.transform.from_origin(0, shape[1], 1, 1) mask = rasterize([poly], out_shape=shape[::-1], transform=transform, fill=0, default_value=1) return mask # 测试代码 poly = "30,20;30,60;60,60;60,30;20,30;20,40;50,40;50,50;40,50;40,20" mask = polygons_to_mask_shapely(poly, (80, 80)) # 显示结果 Image.fromarray(mask * 255).show()
原代码小问题修正
原代码存在两处语法/逻辑错误:
points = [ points_to_float(x) for x in poly.split(";"))]多了一个右括号,应改为points = [ points_to_float(x) for x in poly.split(";")]resize函数未定义,可替换为PIL的Image.resize方法,例如img = Image.fromarray(np.uint8(img)).resize((300,300))
内容的提问来源于stack exchange,提问作者Boris G
相关产品推荐
相关产品推荐

