【问题标题】:Overlapping polygons in Python PILPython PIL中的重叠多边形
【发布时间】:2015-01-04 14:16:51
【问题描述】:

我不想用绘制的最后一个多边形的值覆盖多个多边形的重叠区域,而是绘制这些多边形的平均值。 这在 Python PIL 中可行吗?

示例中的重叠像素的值应为 1.5。

在完整的工作程序中,我必须在一个非常大的网格上绘制大约 100000 个多边形(可能相交或不相交),这就是我使用 PIL 而不是 Numpy 的原因。

from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt

img = Image.new('F', (50, 50), 0)

ImageDraw.Draw(img).polygon([(20, 20), (20, 40), (40, 30), (30, 20)],
                            fill=1., outline=None)
ImageDraw.Draw(img).polygon([(10, 5), (10, 25), (25, 25), (25, 10)],
                            fill=2., outline=None)

myimg = np.ma.masked_equal(np.array(img), 0.)
plt.imshow(myimg, interpolation="None")
plt.colorbar()
plt.show()

【问题讨论】:

  • 您是否尝试过clipping one polygon to the other polygon 生成它们的交集,然后绘制该交集多边形?还是在您的完整数据集中有太多重叠的多边形,以至于多边形的幂集O(2^n) 增长得不可接受?
  • 如果你可以将多边形渲染到一个 numpy 数组上(不知道如何,maybe this 是一个开始?),那么你可以在每个像素处维护一个多边形计数数组和一个累积的数组价值观。然后平均值将是accumulated_values / counts,其中counts > 0
  • 我在我的问题中添加了大约(大量)可能相交的多边形。这就是为什么我要避免使用 Numpy,因为 PIL 在这方面的性能要高得多。
  • 你能在图像中使用透明层吗?那么重叠的多边形会自然地相互“混合”吗?不过,我不确定 PIL 是否支持 float32 的多通道图像。

标签: python numpy python-imaging-library polygon computational-geometry


【解决方案1】:

我建议scikit-imageskimage.draw.polygon() 返回多边形中的坐标。这是一个例子。先创建一些随机多边形数据:

import pylab as pl
from random import randint
import numpy as np
from skimage import draw

W, H = 800, 600

def make_poly(x0, y0, r, n):
    a = np.linspace(0, np.pi*2, n, endpoint=False)
    x = x0 + r * np.cos(a)
    y = y0 + r * np.sin(a)
    return y, x

count_buf = np.zeros((H, W))
sum_buf = np.zeros((H, W))

N = 2000

polys = []
for i in range(N):
    x0, y0, r, n = randint(10, W-10), randint(10, H-10), randint(10, 50), randint(3, 10)
    polys.append((make_poly(x0, y0, r, n), randint(1, 10)))

然后绘制多边形:

for (y, x), v in polys:
    rr, cc = draw.polygon(y, x, (H, W))
    count_buf[rr, cc] += 1
    sum_buf[rr, cc] += v

mean_buf = np.zeros_like(sum_buf)
mask = count_buf > 0
mean_buf[mask] = sum_buf[mask] / count_buf[mask]

在我的电脑上绘制 2000 个平均半径为 30 像素的多边形大约需要 1.5 秒。

结果如下:

如果你需要更好的速度,可以在scikit-image中复制如下代码:

https://github.com/scikit-image/scikit-image/blob/master/skimage/draw/_draw.pyx#L189

https://github.com/scikit-image/scikit-image/blob/master/skimage/_shared/geometry.pyx#L7

如果 point_in_polygon() 返回 True,则在 for 循环中更改 count_bufsum_buf

编辑

这是 Cython 代码:

%%cython
#cython: cdivision=True
#cython: boundscheck=False
#cython: wraparound=False

import numpy as np
cimport numpy as np
from libc.math cimport ceil

cdef unsigned char point_in_polygon(double[::1] xp, double[::1] yp,
                                           double x, double y):
    cdef Py_ssize_t i
    cdef unsigned char c = 0
    cdef Py_ssize_t j = xp.shape[0] - 1
    for i in range(xp.shape[0]):
        if (
            (((yp[i] <= y) and (y < yp[j])) or
            ((yp[j] <= y) and (y < yp[i])))
            and (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i])
        ):
            c = not c
        j = i
    return c


cdef class PolygonAccumulator:

    cdef int width, height
    cdef int[:, ::1] count_buf
    cdef double[:, ::1] sum_buf

    def __cinit__(self, width, height):
        self.width = width
        self.height = height
        shape = (height, width)
        self.count_buf = np.zeros(shape, dtype=int)
        self.sum_buf = np.zeros(shape, dtype=float)

    def reset(self):
        self.count_buf[:, :] = 0
        self.sum_buf[:, :] = 0

    def add_polygon(self, ya, xa, double value):
        cdef Py_ssize_t minr = int(max(0, np.min(ya)))
        cdef Py_ssize_t maxr = int(ceil(np.max(ya)))
        cdef Py_ssize_t minc = int(max(0, np.min(xa)))
        cdef Py_ssize_t maxc = int(ceil(np.max(xa)))

        cdef double[::1] x = xa
        cdef double[::1] y = ya

        cdef Py_ssize_t r, c

        maxr = min(self.height - 1, maxr)
        maxc = min(self.width  - 1, maxc)

        for r in range(minr, maxr+1):
            for c in range(minc, maxc+1):
                if point_in_polygon(x, y, c, r):
                    self.count_buf[r, c] += 1
                    self.sum_buf[r, c] += value

    def mean(self):
        count_buf = self.count_buf.base
        sum_buf = self.sum_buf.base
        mean_buf = np.zeros_like(sum_buf)
        mask = count_buf > 0
        mean_buf[mask] = sum_buf[mask] / count_buf[mask]
        return mean_buf

绘制多边形:

pa = PolygonAccumulator(800, 600)
for (y, x), value in polys:
    pa.add_polygon(y, x, value)
pl.imshow(pa.mean(), cmap="gray")

skimage.draw.polygon() 快大约 4.5 倍

【讨论】:

  • 感谢您提出这个想法!不幸的是,与 PIL 方法相比,它在我的机器上花费了两倍的时间,但可以正确计算重叠。您能否更详细地解释如何提高速度?谢谢!
  • @HyperCube,我添加的 Cython 代码可能比 PIL 快 2 倍。
  • 可能是一个愚蠢的问题,但我如何调用/嵌入 Cython 代码?
  • 如果您不知道如何编译cython代码,请阅读文档:docs.cython.org/src/quickstart/build.html。如果你使用 IPython notebook,运行 %load_ext cythonmagic,然后运行 ​​cython 代码,然后运行 ​​import sys; sys.modules[PolygonAccumulator.__module__].__file__ 以获取编译后的模块。将模块文件复制到您的脚本文件夹,然后重命名。
  • 不幸的是,我得到一个编译错误:hyrycode.pyx:9:43: Expected an identifier or literal。 “hyrycode”是你的 Cython 代码的 pyx 文件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-22
  • 1970-01-01
  • 1970-01-01
  • 2020-01-26
  • 2011-10-14
  • 2022-07-12
  • 1970-01-01
相关资源
最近更新 更多