Bezier曲线

时间:2017-06-07 22:40:40

标签: python matplotlib bezier

我正在尝试根据matplotlib Path对象选择数据区域,但是当路径包含Bezier曲线(而不仅仅是直线)时,所选区域不会完全填充曲线。看起来它正在尝试,但曲线的远端被切断了。

例如,以下代码定义了一条相当简单的闭合路径,其中包含一条直线和一条三次曲线。当我查看contains_points方法的真/假结果时,它似乎与曲线本身或原始顶点不匹配。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch

# Make the Path
verts = [(1.0, 1.5), (-2.0, 0.25), (-1.0, 0.0), (1.0, 0.5), (1.0, 1.5)]
codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CLOSEPOLY]
path1 = Path(verts, codes)

# Make a field with points to select
nx, ny = 101, 51
x = np.linspace(-2, 2, nx)
y = np.linspace(0, 2, ny)

yy, xx = np.meshgrid(y, x)
pts = np.column_stack((xx.ravel(), yy.ravel()))

# Construct a True/False array of contained points
tf = path1.contains_points(pts).reshape(nx, ny)

# Make a PathPatch for display
patch1 = PathPatch(path1, facecolor='c', edgecolor='b', lw=2, alpha=0.5)

# Plot the true/false array, the patch, and the vertices
fig, ax = plt.subplots()
ax.imshow(tf.T, origin='lower', extent=(x[0], x[-1], y[0], y[-1]))
ax.add_patch(patch1)
ax.plot(*zip(*verts), 'ro-')

plt.show()

这给了我这个情节:

true points do not completely fill the curve

看起来有某种近似 - 这只是matplotlib中计算的一个基本限制,还是我做错了什么?

可以自己计算曲线内的点数,但如果我不需要,我希望不要重新发明这个轮子。

值得注意的是,使用二次曲线的简单构造似乎可以正常工作:

quadratic curves emulating a circle work fine

我正在使用matplotlib 2.0.0。

1 个答案:

答案 0 :(得分:1)

这与评估路径的空间有关,如GitHub问题#6076中所述。来自mdboom的评论:

  

通过将曲线转换为线段来完成路径交叉   然后根据线段转换交叉点。这个   通过以1.0的增量“采样”曲线来进行转换。这个   当路径已经缩放时,通常是正确的做法   在显示空间中,因为以更高的分辨率对曲线进行采样   比单个像素没有真正帮助。但是,在计算时   我们在这里完成了数据空间中的交集,我们显然需要   以更精细的分辨率进行采样。

这是在讨论交叉点,但contains_points也会受到影响。此增强功能仍处于打开状态,因此我们必须查看它是否在下一个里程碑中得到解决。与此同时,有几种选择:

1)如果您要显示补丁,可以使用显示转换。在上面的示例中,添加以下内容演示了正确的行为(基于tacaswell对重复问题#8734的评论,现已关闭):

# Work in transformed (pixel) coordinates
hit_patch = path1.transformed(ax.transData)
tf1 = hit_patch.contains_points(ax.transData.transform(pts)).reshape(nx, ny)

ax.imshow(tf2.T, origin='lower', extent=(x[0], x[-1], y[0], y[-1]))

2)如果您不使用显示器并且只想使用路径进行计算,最好的办法是自己简单地形成贝塞尔曲线并创建一条线段以外的路径。用以下path1的计算替换path2的形成将产生所需的结果。

from scipy.special import binom
def bernstein(n, i, x):
    coeff = binom(n, i)
    return coeff * (1-x)**(n-i) * x**i

def bezier(ctrlpts, nseg):
    x = np.linspace(0, 1, nseg)
    outpts = np.zeros((nseg, 2))
    n = len(ctrlpts)-1
    for i, point in enumerate(ctrlpts):
        outpts[:,0] += bernstein(n, i, x) * point[0]
        outpts[:,1] += bernstein(n, i, x) * point[1]

    return outpts

verts1 = [(1.0, 1.5), (-2.0, 0.25), (-1.0, 0.0), (1.0, 0.5), (1.0, 1.5)]
nsegments = 31
verts2 = np.concatenate([bezier(verts1[:4], nsegments), np.array([verts1[4]])])
codes2 = [Path.MOVETO] + [Path.LINETO]*(nsegments-1) + [Path.CLOSEPOLY]
path2 = Path(verts2, codes2)

任何一种方法都会产生如下所示的内容:

true and false match bezier curve