SciPy 创建 2D 多边形蒙版

Posted

技术标签:

【中文标题】SciPy 创建 2D 多边形蒙版【英文标题】:SciPy Create 2D Polygon Mask 【发布时间】:2011-04-08 22:21:22 【问题描述】:

我需要使用标准 Python 包创建一个代表多边形二进制掩码的 numpy 二维数组。

输入:多边形顶点、图像尺寸 输出:多边形的二进制掩码(numpy 2D 数组)

(更大的上下文:我想使用 scipy.ndimage.morphology.distance_transform_edt 获取此多边形的距离变换。)

谁能教我怎么做?

【问题讨论】:

【参考方案1】:

答案很简单:

import numpy
from PIL import Image, ImageDraw

# polygon = [(x1,y1),(x2,y2),...] or [x1,y1,x2,y2,...]
# width = ?
# height = ?

img = Image.new('L', (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
mask = numpy.array(img)

【讨论】:

我认为这种方法只适用于整数坐标(即网格坐标)。如果顶点坐标是浮点数,则其他解决方案仍然有效。 来自:@jmetz “仅供参考:我做了一个简单的时序测试,PIL 方法比 matplotlib 版本快 70 倍!!!” 嗨,如果我在多边形中的点是浮点类型,我该怎么办。 @DeepakUmredkar 如果您的点是浮点数,请将它们四舍五入。无论如何,您的蒙版应该是二进制的,因此它们必须是像素坐标。 可能对未来的访问者有用:多边形列表的方向排序似乎并不重要。它总是会为内部着色。您可以将它们以顺时针或逆时针方式插入。只需确保与此选择保持一致 - 极角应该严格增加或减少(混合坐标对应于数学上不同的多边形)。【参考方案2】:

作为@Anil 答案的稍微更直接的替代方案,matplotlib 具有matplotlib.nxutils.points_inside_poly,可用于快速栅格化任意多边形。例如

import numpy as np
from matplotlib.nxutils import points_inside_poly

nx, ny = 10, 10
poly_verts = [(1,1), (5,1), (5,9),(3,2),(1,1)]

# Create vertex coordinates for each grid cell...
# (<0,0> is at the top left of the grid in this system)
x, y = np.meshgrid(np.arange(nx), np.arange(ny))
x, y = x.flatten(), y.flatten()

points = np.vstack((x,y)).T

grid = points_inside_poly(points, poly_verts)
grid = grid.reshape((ny,nx))

print grid

产生(布尔型 numpy 数组):

[[False False False False False False False False False False]
 [False  True  True  True  True False False False False False]
 [False False False  True  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]]

您应该能够很好地将grid 传递给任何 scipy.ndimage.morphology 函数。

【讨论】:

我避免使用 points_inside_poly 因为它使用坐标列表而不是直接在二进制图像上操作。正因为如此,而且因为 PIL 可能能够使用硬件加速来渲染我的多边形,所以在我看来 Anil 的解决方案更有效。 @Issac - 很公平。据我所知,PIL 不使用任何类型的硬件加速,但是......(最近有变化吗?)另外,如果您使用 PIL,则无需像您在上面的评论中提到的那样做M = numpy.reshape(list(img.getdata()), (height, width))) . numpy.array(img) 做同样的事情,效率更高。 太远了!感谢您指出 numpy.array(img) 功能。而且,确实,PIL 可能仍然不使用硬件加速。 仅供参考:我做了一个简单的计时测试,PIL 方法比matplotlib 版本快~ 70 倍!!! 嗨,如果我在多边形中的点是浮点类型,我该怎么办【参考方案3】:

Joe 评论的更新。 Matplotlib API 自评论发布后发生了变化,现在您需要使用子模块matplotlib.path 提供的方法。

工作代码如下。

import numpy as np
from matplotlib.path import Path

nx, ny = 10, 10
poly_verts = [(1,1), (5,1), (5,9),(3,2),(1,1)]

# Create vertex coordinates for each grid cell...
# (<0,0> is at the top left of the grid in this system)
x, y = np.meshgrid(np.arange(nx), np.arange(ny))
x, y = x.flatten(), y.flatten()

points = np.vstack((x,y)).T

path = Path(poly_verts)
grid = path.contains_points(points)
grid = grid.reshape((ny,nx))

print grid

【讨论】:

N:我正在尝试您的解决方案,但在 contains_points 中出现内存错误。你能帮我弄清楚吗?【参考方案4】:

作为@Yusuke N. 答案的一个小替代方案,考虑使用matplotlib.path,它与from PIL import Image, ImageDraw 的效率一样高(无需安装Pillow,无需考虑integerfloat。对我有用吗?)

工作代码如下:

import pylab as plt
import numpy as np
from matplotlib.path import Path

width, height=2000, 2000

polygon=[(0.1*width, 0.1*height), (0.15*width, 0.7*height), (0.8*width, 0.75*height), (0.72*width, 0.15*height)]
poly_path=Path(polygon)

x, y = np.mgrid[:height, :width]
coors=np.hstack((x.reshape(-1, 1), y.reshape(-1,1))) # coors.shape is (4000000,2)

mask = poly_path.contains_points(coors)
plt.imshow(mask.reshape(height, width))
plt.show()

结果图如下,其中暗区False亮区True

【讨论】:

坐标前面的因素有什么意义?这些是任意确定的还是它们对应的东西? 它们的存在是为了显示给定答案的可视化。【参考方案5】:

您可以尝试使用 python 的图像库 PIL。首先你初始化画布。然后创建一个绘图对象,然后开始制作线条。这是假设多边形位于 R^2 中并且输入的顶点列表的顺序正确。

输入 = [(x1, y1), (x2, y2), ..., (xn, yn)] , (width, height)

from PIL import Image, ImageDraw

img = Image.new('L', (width, height), 0)   # The Zero is to Specify Background Color
draw = ImageDraw.Draw(img)

for vertex in range(len(vertexlist)):
    startpoint = vertexlist[vertex]
    try: endpoint = vertexlist[vertex+1]
    except IndexError: endpoint = vertexlist[0] 
    # The exception means We have reached the end and need to complete the polygon
    draw.line((startpoint[0], startpoint[1], endpoint[0], endpoint[1]), fill=1)

# If you want the result as a single list
# You can make a two dimensional list or dictionary by iterating over the height and width variable
list(img.getdata())

# If you want the result as an actual Image
img.save('polgon.jpg', 'JPEG')

这是您正在寻找的东西,还是您要求不同的东西?

【讨论】:

谢谢 Anil,这基本上就是我想要的。如果使用 ImageDraw.polygon 方法(ImageDraw.Draw(img).polygon(vertices, outline=1, fill=1)) 会更好,我使用 numpy.reshape 函数从图像数据中高效获取二维数组(导入 numpy, M = numpy.reshape(list(img.getdata()), (height, width)))。如果您编辑它以包含这些内容,我会接受您的回答。【参考方案6】:

这是一个实现@IsaacSutherland 方法(已接受的答案)的函数,并进行了一些我认为有用的修改。欢迎评论!

poly_mask() 接受多个多边形作为输入,因此输出掩码可以由多个最终不连接的多边形区域组成。 此外,因为在某些情况下 0 不是一个好的掩码值(例如,如果 0 是要应用掩码的数组的有效值),我添加了一个 value 关键字来设置实际的掩码值(例如,非常小/大数字或 NAN):为了实现这一点,掩码被转换为浮点数组。

def poly_mask(shape, *vertices, value=np.nan):
"""
Create a mask array filled with 1s inside the polygon and 0s outside.
The polygon is a list of vertices defined as a sequence of (column, line) number, where the start values (0, 0) are in the
upper left corner. Multiple polygon lists can be passed in input to have multiple,eventually not connected, ROIs.
    column, line   # x, y
    vertices = [(x0, y0), (x1, y1), ..., (xn, yn), (x0, y0)] or [x0, y0, x1, y1, ..., xn, yn, x0, y0]
Note: the polygon can be open, that is it doesn't have to have x0,y0 as last element.

adapted from: https://***.com/questions/3654289/scipy-create-2d-polygon-mask/64876117#64876117
:param shape:    (tuple) shape of the output array (height, width)
:param vertices: (list of tuples of int): sequence of vertices defined as
                                           [(x0, y0), (x1, y1), ..., (xn, yn), (x0, y0)] or
                                           [x0, y0, x1, y1, ..., xn, yn, x0, y0]
                                           Multiple lists (for multiple polygons) can be passed in input
:param value:    (float or NAN)      The masking value to use (e.g. a very small number). Default: np.nan
:return:         (ndarray) the mask array
"""
width, height = shape[::-1]
# create a binary image
img = Image.new(mode='L', size=(width, height), color=0)  # mode L = 8-bit pixels, black and white
draw = ImageDraw.Draw(img)
# draw polygons
for polygon in vertices:
    draw.polygon(polygon, outline=1, fill=1)
# replace 0 with 'value'
mask = np.array(img).astype('float32')
mask[np.where(mask == 0)] = value
return mask

我更喜欢直接使用shape 作为输入而不是(宽度,高度),这样我就可以像这样使用它:

polygon_lists = [
    [(x0, y0), (x1, y1), ..., (xn, yn), (x0, y0)],
    [# ... another sequence of coordinates...],
    [# ...yet another sequence of coordinates...],
                ]
my_mask = poly_mask(my_array.shape, *polygon_lists)

其中my_array 是必须应用蒙版的数组(当然,也可以是其他具有相同形状的数组)。

my_array_masked = my_array * my_mask

【讨论】:

以上是关于SciPy 创建 2D 多边形蒙版的主要内容,如果未能解决你的问题,请参考以下文章

PADS如何添加2D线,错误提示不能创建极小的多边形

如何在没有精确光栅蒙版的情况下光栅化形状?

Cocos2d-x v3.0物理系统 利用PhysicsEditor创建多边形

如何计算Mask Rcnn中对象的单个蒙版区域

4.Libgdx扩展学习之Box2D_创建多边形刚体和圆角矩形

4.Libgdx扩展学习之Box2D_创建多边形刚体和圆角矩形