简体   繁体   English

使用Python进行B样条插值

[英]B-spline interpolation with Python

I am trying to reproduce a Mathematica example for a B-spline with Python. 我试图用Python重现B样条的Mathematica示例。

The code of the mathematica example reads mathematica示例的代码读取

pts = {{0, 0}, {0, 2}, {2, 3}, {4, 0}, {6, 3}, {8, 2}, {8, 0}};
Graphics[{BSplineCurve[pts, SplineKnots -> {0, 0, 0, 0, 2, 3, 4, 6, 6, 6, 6}], Green, Line[pts], Red, Point[pts]}]

and produces what I expect. 并产生我所期望的。 Now I try to do the same with Python/scipy: 现在我尝试用Python / scipy做同样的事情:

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si

points = np.array([[0, 0], [0, 2], [2, 3], [4, 0], [6, 3], [8, 2], [8, 0]])
x = points[:,0]
y = points[:,1]

t = range(len(x))
knots = [2, 3, 4]
ipl_t = np.linspace(0.0, len(points) - 1, 100)

x_tup = si.splrep(t, x, k=3, t=knots)
y_tup = si.splrep(t, y, k=3, t=knots)
x_i = si.splev(ipl_t, x_tup)
y_i = si.splev(ipl_t, y_tup)

print 'knots:', x_tup

fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(x, y, label='original')
plt.plot(x_i, y_i, label='spline')
plt.xlim([min(x) - 1.0, max(x) + 1.0])
plt.ylim([min(y) - 1.0, max(y) + 1.0])
plt.legend()
plt.show()

This results in somethng that is also interpolated but doesn't look quite right. 这会产生一些插值,但看起来不太正确。 I parameterize and spline the x- and y-components separately, using the same knots as mathematica. 我使用与mathematica相同的结来分别对x和y分量进行参数化和样条化。 However, I get over- and undershoots, which make my interpolated curve bow outside of the convex hull of the control points. 然而,我得到了过度和过冲,这使得我的插值曲线在控制点的凸包外面弯曲。 What's the right way to do this/how does mathematica do it? 什么是正确的方法/ mathematica如何做到这一点?

I was able to recreate the Mathematica example I asked about in the previous post using Python/scipy. 我能够使用Python / scipy重新创建我在上一篇文章中询问过的Mathematica示例。 Here's the result: 这是结果:

B-Spline, Aperiodic B样条,非周期性

样条曲线通过2D曲线。

The trick was to either intercept the coefficients, ie element 1 of the tuple returned by scipy.interpolate.splrep , and to replace them with the control point values before handing them to scipy.interpolate.splev , or, if you are fine with creating the knots yourself, you can also do without splrep and create the entire tuple yourself. 诀窍是截取系数,即scipy.interpolate.splrep返回的元组的元素1,并在将它们交给scipy.interpolate.splev之前用控制点值替换它们,或者,如果你没有创建你自己结,你也可以不splrep ,自己创造整个元组。

What is strange about this all, though, is that, according to the manual, splrep returns (and splev expects) a tuple containing, among others, a spline coefficients vector with one coefficient per knot. 然而,根据手册, splrep返回(和splev预期)一个元组,其中包含一个每个结具有一个系数的样条系数向量。 However, according to all sources I found, a spline is defined as the weighted sum of the N_control_points basis splines, so I would expect the coefficients vector to have as many elements as control points, not knot positions. 然而,根据我发现的所有来源,样条曲线被定义为N_control_points基础样条曲线的加权和,因此我希望系数向量具有与控制点一样多的元素,而不是结点位置。

In fact, when supplying splrep 's result tuple with the coefficients vector modified as described above to scipy.interpolate.splev , it turns out that the first N_control_points of that vector actually are the expected coefficients for the N_control_points basis splines. 事实上,当将splrep的结果元组与如上所述修改的系数向量提供给scipy.interpolate.splev ,事实证明该向量的第一个N_control_points实际上是N_control_points基本样条的预期系数。 The last degree + 1 elements of that vector seem to have no effect. 该向量的最后一个度+ 1个元素似乎没有效果。 I'm stumped as to why it's done this way. 我很难过为什么这样做。 If anyone can clarify that, that would be great. 如果有人能澄清这一点,那就太好了。 Here's the source that generates the above plots: 以下是生成上述图的来源:

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si

points = [[0, 0], [0, 2], [2, 3], [4, 0], [6, 3], [8, 2], [8, 0]];
points = np.array(points)
x = points[:,0]
y = points[:,1]

t = range(len(points))
ipl_t = np.linspace(0.0, len(points) - 1, 100)

x_tup = si.splrep(t, x, k=3)
y_tup = si.splrep(t, y, k=3)

x_list = list(x_tup)
xl = x.tolist()
x_list[1] = xl + [0.0, 0.0, 0.0, 0.0]

y_list = list(y_tup)
yl = y.tolist()
y_list[1] = yl + [0.0, 0.0, 0.0, 0.0]

x_i = si.splev(ipl_t, x_list)
y_i = si.splev(ipl_t, y_list)

#==============================================================================
# Plot
#==============================================================================

fig = plt.figure()

ax = fig.add_subplot(231)
plt.plot(t, x, '-og')
plt.plot(ipl_t, x_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined x(t)')

ax = fig.add_subplot(232)
plt.plot(t, y, '-og')
plt.plot(ipl_t, y_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined y(t)')

ax = fig.add_subplot(233)
plt.plot(x, y, '-og')
plt.plot(x_i, y_i, 'r')
plt.xlim([min(x) - 0.3, max(x) + 0.3])
plt.ylim([min(y) - 0.3, max(y) + 0.3])
plt.title('Splined f(x(t), y(t))')

ax = fig.add_subplot(234)
for i in range(7):
    vec = np.zeros(11)
    vec[i] = 1.0
    x_list = list(x_tup)
    x_list[1] = vec.tolist()
    x_i = si.splev(ipl_t, x_list)
    plt.plot(ipl_t, x_i)
plt.xlim([0.0, max(t)])
plt.title('Basis splines')
plt.show()

B-Spline, Periodic B样条,周期性

Now in order to create a closed curve like the following, which is another Mathematica example that can be found on the web, 现在为了创建一个如下所示的闭合曲线,这是另一个可以在网上找到的Mathematica示例, 闭合的b样条曲线

it is necessary to set the per parameter in the splrep call, if you use that. 如果你使用它,有必要在splrep调用中设置per参数。 After padding the list of control points with degree+1 values at the end, this seems to work well enough, as the images show. 在最后用度数+ 1值填充控制点列表之后,这似乎工作得很好,如图像所示。

The next peculiarity here, however, is that the first and the last degree elements in the coefficients vector have no effect, meaning that the control points must be put in the vector starting at the second position, ie position 1. Only then are the results ok. 然而,这里的下一个特点是系数向量中的第一个和最后一个元素没有影响,这意味着控制点必须放在从第二个位置开始的向量中,即位置1.然后才是结果好。 For degrees k=4 and k=5, that position even changes to position 2. 对于度k = 4且k = 5,该位置甚至变为位置2。

Here's the source for generating the closed curve: 以下是生成闭合曲线的来源:

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si

points = [[-2, 2], [0, 1], [-2, 0], [0, -1], [-2, -2], [-4, -4], [2, -4], [4, 0], [2, 4], [-4, 4]]

degree = 3

points = points + points[0:degree + 1]
points = np.array(points)
n_points = len(points)
x = points[:,0]
y = points[:,1]

t = range(len(x))
ipl_t = np.linspace(1.0, len(points) - degree, 1000)

x_tup = si.splrep(t, x, k=degree, per=1)
y_tup = si.splrep(t, y, k=degree, per=1)
x_list = list(x_tup)
xl = x.tolist()
x_list[1] = [0.0] + xl + [0.0, 0.0, 0.0, 0.0]

y_list = list(y_tup)
yl = y.tolist()
y_list[1] = [0.0] + yl + [0.0, 0.0, 0.0, 0.0]

x_i = si.splev(ipl_t, x_list)
y_i = si.splev(ipl_t, y_list)

#==============================================================================
# Plot
#==============================================================================

fig = plt.figure()

ax = fig.add_subplot(231)
plt.plot(t, x, '-og')
plt.plot(ipl_t, x_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined x(t)')

ax = fig.add_subplot(232)
plt.plot(t, y, '-og')
plt.plot(ipl_t, y_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined y(t)')

ax = fig.add_subplot(233)
plt.plot(x, y, '-og')
plt.plot(x_i, y_i, 'r')
plt.xlim([min(x) - 0.3, max(x) + 0.3])
plt.ylim([min(y) - 0.3, max(y) + 0.3])
plt.title('Splined f(x(t), y(t))')

ax = fig.add_subplot(234)
for i in range(n_points - degree - 1):
    vec = np.zeros(11)
    vec[i] = 1.0
    x_list = list(x_tup)
    x_list[1] = vec.tolist()
    x_i = si.splev(ipl_t, x_list)
    plt.plot(ipl_t, x_i)
plt.xlim([0.0, 9.0])
plt.title('Periodic basis splines')

plt.show()

B-Spline, Periodic, Higher Degree B样条,周期性,更高度

Lastly, there is an effect that I can not explain either, and this is when going to degree 5, there is a small discontinuity that appears in the splined curve, see the upper right panel, which is a close-up of that 'half-moon-with-nose-shape'. 最后,有一个我无法解释的效果,这是在进入5级时,花键曲线中出现一个小的不连续性,请参见右上图,这是一半的特写-moon与 - 鼻形”。 The source code that produces this is listed below. 产生此信息的源代码如下所示。

间断。

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si

points = [[-2, 2], [0, 1], [-2, 0], [0, -1], [-2, -2], [-4, -4], [2, -4], [4, 0], [2, 4], [-4, 4]]

degree = 5

points = points + points[0:degree + 1]
points = np.array(points)
n_points = len(points)
x = points[:,0]
y = points[:,1]

t = range(len(x))
ipl_t = np.linspace(1.0, len(points) - degree, 1000)

knots = np.linspace(-degree, len(points), len(points) + degree + 1).tolist()

xl = x.tolist()
coeffs_x = [0.0, 0.0] + xl + [0.0, 0.0, 0.0]

yl = y.tolist()
coeffs_y = [0.0, 0.0] + yl + [0.0, 0.0, 0.0]

x_i = si.splev(ipl_t, (knots, coeffs_x, degree))
y_i = si.splev(ipl_t, (knots, coeffs_y, degree))

#==============================================================================
# Plot
#==============================================================================

fig = plt.figure()

ax = fig.add_subplot(231)
plt.plot(t, x, '-og')
plt.plot(ipl_t, x_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined x(t)')

ax = fig.add_subplot(232)
plt.plot(t, y, '-og')
plt.plot(ipl_t, y_i, 'r')
plt.xlim([0.0, max(t)])
plt.title('Splined y(t)')

ax = fig.add_subplot(233)
plt.plot(x, y, '-og')
plt.plot(x_i, y_i, 'r')
plt.xlim([min(x) - 0.3, max(x) + 0.3])
plt.ylim([min(y) - 0.3, max(y) + 0.3])
plt.title('Splined f(x(t), y(t))')

ax = fig.add_subplot(234)
for i in range(n_points - degree - 1):
    vec = np.zeros(11)
    vec[i] = 1.0
    x_i = si.splev(ipl_t, (knots, vec, degree))
    plt.plot(ipl_t, x_i)
plt.xlim([0.0, 9.0])
plt.title('Periodic basis splines')

plt.show()

Given that b-splines are ubiquitous in the scientific community, and that scipy is such a comprehensive toolbox, and that I have not been able to find much about what I'm asking here on the web, leads me to believe I'm on the wrong track or overlooking something. 鉴于b-splines在科学界无处不在,而且scipy是如此全面的工具箱,而且我无法在网上找到我所要求的内容,让我相信我在错误的轨道或俯视的东西。 Any help would be appreciated. 任何帮助,将不胜感激。

Use this function i wrote for another question i asked here. 使用这个函数我写的另一个问题我在这里问。

In my question i was looking for ways to calculate bsplines with scipy (this is how i actually stumbled upon your question). 在我的问题中,我一直在寻找用scipy计算bsplines的方法(这就是我实际上偶然发现你的问题)。

After much obsession, i came up with the function below. 经过多次痴迷,我想出了下面的功能。 It'll evaluate any curve up to the 20th degree (way more than we need). 它将评估任何高达20度的曲线(比我们需要的更多)。 And speed wise i tested it for 100,000 samples and it took 0.017s 并且速度明智,我测试了100,000个样品,它需要0.017秒

import numpy as np
import scipy.interpolate as si


def bspline(cv, n=100, degree=3, periodic=False):
    """ Calculate n samples on a bspline

        cv :      Array ov control vertices
        n  :      Number of samples to return
        degree:   Curve degree
        periodic: True - Curve is closed
                  False - Curve is open
    """

    # If periodic, extend the point array by count+degree+1
    cv = np.asarray(cv)
    count = len(cv)

    if periodic:
        factor, fraction = divmod(count+degree+1, count)
        cv = np.concatenate((cv,) * factor + (cv[:fraction],))
        count = len(cv)
        degree = np.clip(degree,1,degree)

    # If opened, prevent degree from exceeding count-1
    else:
        degree = np.clip(degree,1,count-1)


    # Calculate knot vector
    kv = None
    if periodic:
        kv = np.arange(0-degree,count+degree+degree-1,dtype='int')
    else:
        kv = np.concatenate(([0]*degree, np.arange(count-degree+1), [count-degree]*degree))


    # Calculate query range
    u = np.linspace(periodic,(count-degree),n)


    # Calculate result
    return np.array(si.splev(u, (kv,cv.T,degree))).T

Results for both open and periodic curves: 开放和周期曲线的结果:

cv = np.array([[ 50.,  25.],
   [ 59.,  12.],
   [ 50.,  10.],
   [ 57.,   2.],
   [ 40.,   4.],
   [ 40.,   14.]])

周期性(闭合)曲线 打开曲线

I believe scipy's fitpack Library is doing something more complicated than what Mathematica is doing. 我相信scipy的fitpack Library正在做一些比Mathematica正在做的更复杂的事情。 I was confused as to what was going on as well. 我对于发生的事情感到困惑。

There is the smoothing parameter in these functions, and the default interpolation behavior is to try to make points go through lines. 这些函数中存在平滑参数,默认的插值行为是尝试使点遍历线。 That's what this fitpack software does, so I guess scipy just inherited it? 这就是这个fitpack软件所做的,所以我猜scipy只是继承了它? ( http://www.netlib.org/fitpack/all -- I'm not sure this is the right fitpack) http://www.netlib.org/fitpack/all - 我不确定这是不是合适的装备)

I took some ideas from http://research.microsoft.com/en-us/um/people/ablake/contours/ and coded up your example with the B-splines in there. 我从http://research.microsoft.com/en-us/um/people/ablake/contours/中获取了一些想法,并在那里用B样条编写了你的​​例子。

样条拟合

基础功能

import numpy

import matplotlib.pyplot as plt

# This is the basis function described in eq 3.6 in http://research.microsoft.com/en-us/um/people/ablake/contours/
def func(x, offset):
    out = numpy.ndarray((len(x)))

    for i, v in enumerate(x):
        s = v - offset

        if s >= 0 and s < 1:
            out[i] = s * s / 2.0
        elif s >= 1 and s < 2:
            out[i] = 3.0 / 4.0 - (s - 3.0 / 2.0) * (s - 3.0 / 2.0)
        elif s >= 2 and s < 3:
            out[i] = (s - 3.0) * (s - 3.0) / 2.0
        else:
            out[i] = 0.0

    return out

# We have 7 things to fit, so let's do 7 basis functions?
y = numpy.array([0, 2, 3, 0, 3, 2, 0])

# We need enough x points for all the basis functions... That's why the weird linspace max here
x = numpy.linspace(0, len(y) + 2, 100)

B = numpy.ndarray((len(x), len(y)))

for k in range(len(y)):
    B[:, k] = func(x, k)

plt.plot(x, B.dot(y))
# The x values in the next statement are the maximums of each basis function. I'm not sure at all this is right
plt.plot(numpy.array(range(len(y))) + 1.5, y, '-o')
plt.legend('B-spline', 'Control points')
plt.show()

for k in range(len(y)):
    plt.plot(x, B[:, k])
plt.title('Basis functions')
plt.show()

Anyway I think other folks have the same problems, have a look at: Behavior of scipy's splrep 无论如何,我认为其他人有同样的问题,看看: scipy的splrep的行为

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM