SciPy 创建 2D 多边形蒙版

发布于 2024-09-18 03:41:04 字数 233 浏览 8 评论 0原文

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

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

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

任何人都可以告诉我如何做到这一点?

I need to create a numpy 2D array which represents a binary mask of a polygon, using standard Python packages.

  • input: polygon vertices, image dimensions
  • output: binary mask of polygon (numpy 2D array)

(Larger context: I want to get the distance transform of this polygon using scipy.ndimage.morphology.distance_transform_edt.)

Can anyone show me how to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

权谋诡计 2024-09-25 03:41:04

答案其实很简单:

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)

The answer turns out to be quite simple:

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)
洒一地阳光 2024-09-25 03:41:04

作为 @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 函数。

As a slightly more direct alternative to @Anil's answer, matplotlib has matplotlib.nxutils.points_inside_poly that can be used to quickly rasterize an arbitrary polygon. E.g.

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

Which yields (a boolean numpy array):

[[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]]

You should be able to pass grid to any of the scipy.ndimage.morphology functions quite nicely.

爱格式化 2024-09-25 03:41:04

乔评论的更新。
自评论发布以来,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

An update on Joe's comment.
Matplotlib API has changed since the comment was posted, and now you need to use a method provided by a submodule matplotlib.path.

Working code is below.

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
獨角戲 2024-09-25 03:41:04

作为 @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
输入图像描述这里

As a slight alternative to @Yusuke N.'s answer, consider using matplotlib.path, which is just as efficient as the one by from PIL import Image, ImageDraw(no need to install Pillow, no need to consider integer or float. Useful me?)

Working code is below:

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()

And the result image is below, where dark area is False, bright area is True.
enter image description here

丢了幸福的猪 2024-09-25 03:41:04

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

这是您正在寻找的东西,还是您在询问不同的东西?

You could try to use python's Image Library, PIL. First you initialize the canvas. Then you create a drawing object, and you start making lines. This is assuming that the polygon resides in R^2 and that the vertex list for the input are in the correct order.

Input = [(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')

Is this what you were looking for, or were you asking something different?

我很坚强 2024-09-25 03:41:04

这是一个实现 @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://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

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

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

Here is a function that implements @IsaacSutherland method (the accepted answer) with some modifications I find useful. Comments are welcome!

poly_mask() accepts multiple polygons as input so that the output mask can be made of multiple, eventually not connected, polygonal regions.
Moreover, because in some cases 0 is not a good value for masking (e.g. if 0 is a valid value of the array to which the maskhas to be applied ) I added a value keyword that sets the actual masking value (e.g. a very small/big number or NAN): to achieve this the mask is converted to array of float.

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

Instead of (width, height) I prefer to have directly shape as input so that I can use it like this:

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)

where my_array is the array to which the mask has to be applied (or another array with the same shape, of course).

my_array_masked = my_array * my_mask
黑白记忆 2024-09-25 03:41:04

这是一个 cv2 版本:

import cv2
import numpy as np

# Create mask
image_width = 800
image_height = 600
mask = np.zeros((image_height, image_width), dtype=np.uint8)

# Define the vertices of a polygon
polygon_vertices = np.array([
    [(100, 100), (300, 100), (200, 300)],
    [(400, 200), (600, 200), (500, 400)]
], dtype=np.int32)

# Draw filled polygons
cv2.fillPoly(mask, polygon_vertices, color=(255)) 

# Display the image with the filled polygons
cv2.imshow('Filled Polygons', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

# Save the image with filled polygons to a file
cv2.imwrite('filled_polygons.png', mask)

Here is a cv2 version:

import cv2
import numpy as np

# Create mask
image_width = 800
image_height = 600
mask = np.zeros((image_height, image_width), dtype=np.uint8)

# Define the vertices of a polygon
polygon_vertices = np.array([
    [(100, 100), (300, 100), (200, 300)],
    [(400, 200), (600, 200), (500, 400)]
], dtype=np.int32)

# Draw filled polygons
cv2.fillPoly(mask, polygon_vertices, color=(255)) 

# Display the image with the filled polygons
cv2.imshow('Filled Polygons', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

# Save the image with filled polygons to a file
cv2.imwrite('filled_polygons.png', mask)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文