2010-09-06 22 views
20

Necesito crear una matriz numpy 2D que represente una máscara binaria de un polígono, usando paquetes estándar de Python.SciPy Crear máscara de polígono 2D

  • de entrada: vértices del polígono, dimensiones de imagen
  • de salida: máscara binaria del polígono (matriz 2D numpy)

(contexto más amplio: Quiero conseguir la distancia transformada de este polígono usando scipy. ndimage.morphology.distance_transform_edt.)

¿Alguien me puede mostrar cómo hacerlo?

Respuesta

39

La respuesta resulta ser bastante 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) 
+0

uso el modo de imagen 'L', no '1', porque Numpy-1.5.0/PIL-1.1.7 no es compatible con 'numpy.array (img) 'conversión muy bien para imágenes bivalue. La parte superior de la matriz contiene 8 pequeñas subimágenes 1/8 del tamaño de máscara esperado, y las 7/8 restantes de la matriz están llenas de basura. Tal vez la conversión no descomprime los datos binarios correctamente? –

+1

Creo que este método solo funciona con coordenadas enteras (es decir, las coordenadas de la cuadrícula). Si las coordenadas de los vértices son flotantes, la otra solución aún funciona. –

+0

de: @jmetz "Solo FYI: Hice una prueba de tiempo simple y el enfoque PIL es ~ 70 veces más rápido que la versión matplotlib !!!" – Jakobovski

2

Puede intentar usar la Biblioteca de imágenes de Python, PIL. Primero inicializas el lienzo. Luego creas un objeto de dibujo y comienzas a hacer líneas. Esto supone que el polígono reside en R^2 y que la lista de vértices para la entrada está en el orden correcto.

entrada = [(x1, y1), (x2, y2), ..., (xn, yn)], (anchura, altura)

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

Es esto lo que estabas buscando, o estabas preguntando algo diferente?

+1

Gracias Anil, eso es básicamente lo que estaba buscando. Es mejor si usa el método ImageDraw.polygon (ImageDraw.Draw (img) .polygon (vertices, outline = 1, fill = 1)), y utilicé la función numpy.reshape para obtener de manera eficiente una matriz 2D a partir de los datos de la imagen (importar numpy, M = numpy.reshape (list (img.getdata()), (height, width))). Aceptaré tu respuesta si la editas para incluir estas cosas. –

22

Como una alternativa ligeramente más directa a la respuesta de @Anil, matplotlib tiene matplotlib.nxutils.points_inside_poly que se puede usar para rasterizar rápidamente un polígono arbitrario. P.ej.

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 

que produce (una matriz numpy boolean):

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

Usted debe ser capaz de pasar grid a cualquiera de las funciones scipy.ndimage.morphology bastante bien.

+0

Estaba evitando usar points_inside_poly porque funciona con una lista de coordenadas en lugar de operar directamente en una imagen binaria. Debido a esto, y debido a que PIL puede usar la aceleración de hardware para renderizar mi polígono, me parece que la solución de Anil es más eficiente. –

+1

@Issac - Muy bien. Hasta donde yo sé, PIL no usa aceleración de hardware de ningún tipo, sin embargo ... (¿Ha cambiado eso recientemente?) Además, si usas PIL, no hay necesidad de hacer 'M = numpy.reshape (list (img) .getdata()), (alto, ancho))) 'como mencionas en tu comentario anterior. 'numpy.array (img)' hace exactamente lo mismo, mucho más eficientemente. –

+1

¡Fuera! Gracias por señalar la funcionalidad numpy.array (img). Y, es cierto, PIL probablemente aún no use la aceleración de hardware. –

8

Una actualización sobre el comentario de Joe. La API de Matplotlib ha cambiado desde que se publicó el comentario, y ahora debe utilizar un método proporcionado por un submódulo matplotlib.path.

Código de trabajo está por debajo.

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 
Cuestiones relacionadas