我需要通过一些点绘制一条平滑曲线,然后将其显示为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) )
红色点是x
和y
中的3个初始点。绿点是cx
和cy
中的6个系数。 (它们的值在3号之后重复,因此每个绿色点都有两个绿色索引号。)
返回值tck
和u
描述为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]
答案 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
我对周期性案件没有太多关注,但希望它不太困难。