61

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

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

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

谁能告诉我如何做到这一点?

4

6 回答 6

91

答案很简单:

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)
于 2010-09-17T01:35:44.667 回答
30

作为@Anil 答案的稍微更直接的替代方案,matplotlibmatplotlib.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 函数。

于 2010-09-07T03:34:49.687 回答
26

乔的评论更新。自评论发布以来,Matplotlib API 发生了变化,现在您需要使用 submodule 提供的方法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
于 2016-04-21T04:01:11.803 回答
15

作为@Yusuke N.答案的一个小替代方案,考虑使用matplotlib.path,它与 by 一样有效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在此处输入图像描述

于 2018-07-06T10:46:44.543 回答
4

您可以尝试使用 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')

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

于 2010-09-06T21:30:15.547 回答
2

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

poly_mask()接受多个多边形作为输入,因此输出掩码可以由多个最终不连接的多边形区域组成。此外,因为在某些情况下 0 不是一个好的掩码值(例如,如果 0 是必须应用掩码的数组的有效值),我添加了一个value关键字来设置实际的掩码值(例如非常小/大number 或 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://stackoverflow.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

而不是 (width, height) 我更喜欢直接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
于 2020-11-17T13:23:10.373 回答