如何在Sympy中从B样条曲线创建Bezier曲线?

时间:2019-07-02 20:42:05

标签: scipy bezier bspline

我需要通过一些点绘制一条平滑曲线,然后将其显示为SVG路径。因此,我用scipy.interpolate创建了一个B样条曲线,并且可以访问我认为完全定义了它的一些数组。有人知道从这些数组创建贝塞尔曲线的合理简单方法吗?

import numpy as np
from scipy import interpolate

x = np.array([-1, 0, 2])
y = np.array([ 0, 2, 0])

x = np.r_[x, x[0]]
y = np.r_[y, y[0]]

tck, u = interpolate.splprep([x, y], s=0, per=True)

cx = tck[1][0]
cy = tck[1][1]

print(          'knots: ', list(tck[0]) )
print( 'coefficients x: ', list(cx)     )
print( 'coefficients y: ', list(cy)     )
print(         'degree: ', tck[2]       )
print(      'parameter: ', list(u)      )

enter image description here

红色点是xy中的3个初始点。绿点是cxcy中的6个系数。 (它们的值在3号之后重复,因此每个绿色点都有两个绿色索引号。)

返回值tcku描述为scipy.interpolate.splprep documentation

knots:  [-1.0, -0.722, -0.372, 0.0, 0.277, 0.627, 1.0, 1.277, 1.627, 2.0]

#                   0       1       2       3       4       5
coefficients x:  [ 3.719, -2.137, -0.053,  3.719, -2.137, -0.053]
coefficients y:  [-0.752, -0.930,  3.336, -0.752, -0.930,  3.336]

degree:  3

parameter:  [0.0, 0.277, 0.627, 1.0]

3 个答案:

答案 0 :(得分:2)

不确定从B样条线开始是否有意义:通过这些点形成catmull-rom曲线(虚拟的“ before”和“ afterlast”覆盖在实际点上),然后使用转换为贝塞尔曲线relatively trivial transform?例如。给定您的点p0,p1和p2,第一段将是段p1-p2的catmull-rom曲线{p2,p0,p1,p2},将产生p2- -p0和{p1,p2,p0,p1}将产生p0--p1。然后,您将它们简单地转换为SVG路径即可。

作为演示者,按下https://editor.p5js.org/并粘贴以下代码:

main:
.LFB2:
        .cfi_startproc
        movl    input(%rip), %eax
        leal    3(%rax,%rax), %eax
        movl    %eax, result(%rip)
        xorl    %eax, %eax
        ret
        .cfi_endproc

看起来像这样:

将其转换为Python代码几乎是一件容易的事:几乎没有任何代码可供我们编写=)

当然,现在您需要创建SVG路径,但这几乎不是问题:您现在已经知道所有Bezier点,因此只需在迭代时开始构建var points = [{x:150, y:100 },{x:50, y:300 },{x:300, y:300 }]; // add virtual points: points = points.concat(points); function setup() { createCanvas(400, 400); tension = createSlider(1, 200, 100); } function draw() { background(220); points.forEach(p => ellipse(p.x, p.y, 4)); for (let n=0; n<3; n++) { let [c1, c2, c3, c4] = points.slice(n,n+4); let t = 0.06 * tension.value(); bezier( // on-curve start point c2.x, c2.y, // control point 1 c2.x + (c3.x - c1.x)/t, c2.y + (c3.y - c1.y)/t, // control point 2 c3.x - (c4.x - c2.x)/t, c3.y - (c4.y - c2.y)/t, // on-curve end point c3.x, c3.y ); } } 字符串即可。

答案 1 :(得分:0)

B样条曲线只是连接在一起的Bezier曲线的集合。因此,当然有可能将其转换回多个Bezier曲线而不会损失任何形状保真度。所涉及的算法称为“结插入”,通过两种最著名的算法Boehm算法和Oslo算法,可以采用不同的方法来完成此操作。您可以参考此link了解更多详细信息。

答案 2 :(得分:0)

以下是您问题的直接答案(但对于非周期性情况):

import aggdraw
import numpy as np
import scipy.interpolate as si
from PIL import Image

# from https://stackoverflow.com/a/35007804/2849934
def scipy_bspline(cv, degree=3):
    """ cv:       Array of control vertices
        degree:   Curve degree
    """
    count = cv.shape[0]

    degree = np.clip(degree, 1, count-1)
    kv = np.clip(np.arange(count+degree+1)-degree, 0, count-degree)

    max_param = count - (degree * (1-periodic))
    spline = si.BSpline(kv, cv, degree)
    return spline, max_param

# based on https://math.stackexchange.com/a/421572/396192
def bspline_to_bezier(cv):
    cv_len = cv.shape[0]
    assert cv_len >= 4, "Provide at least 4 control vertices"
    spline, max_param = scipy_bspline(cv, degree=3)
    for i in range(1, max_param):
        spline = si.insert(i, spline, 2)
    return spline.c[:3 * max_param + 1]

def draw_bezier(d, bezier):
    path = aggdraw.Path()
    path.moveto(*bezier[0])
    for i in range(1, len(bezier) - 1, 3):
        v1, v2, v = bezier[i:i+3]
        path.curveto(*v1, *v2, *v)
    d.path(path, aggdraw.Pen("black", 2))

cv = np.array([[ 40., 148.], [ 40.,  48.],
               [244.,  24.], [160., 120.],
               [240., 144.], [210., 260.],
               [110., 250.]])

im = Image.fromarray(np.ones((400, 400, 3), dtype=np.uint8) * 255)
bezier = bspline_to_bezier(cv)
d = aggdraw.Draw(im)
draw_bezier(d, bezier)
d.flush()
# show/save im

b-spline-curve-as-bezier-curves

我对周期性案件没有太多关注,但希望它不太困难。