【问题标题】:Querying points on a 3D spline at specific parametric values in Python在 Python 中以特定参数值查询 3D 样条上的点
【发布时间】:2016-01-30 16:24:04
【问题描述】:

给定一个定义样条曲线的控制顶点列表和查询值列表(0 = 行开始,1= 行结束,0.5= 中途,0.25= 中途等...)我想找到(尽可能快速有效地)样条上这些查询的 3D 坐标。

我试图找到一些内置的 scipy 但失败了。所以我写了一个函数来用蛮力方法解决这个问题:

  1. 将样条细分为n个细分
  2. 添加细分以比较查询
  3. 为每个查询找到适当的细分步骤并推断位置

下面的代码运行良好,但我很想知道是否有更快/更有效的方法来计算我需要的东西,或者更好的一些已经内置的 scipy 我可能错过了。

这是我的功能:

import numpy as np
import scipy.interpolate as interpolate

def uQuery(cv,u,steps=100,projection=True):
    ''' Brute force point query on spline
        cv     = list of spline control vertices
        u      = list of queries (0-1)
        steps  = number of curve subdivisions (higher value = more precise result)
        projection = method by wich we get the final result
                     - True : project a query onto closest spline segments.
                              this gives good results but requires a high step count
                     - False: modulates the parametric samples and recomputes new curve with splev.
                              this can give better results with fewer samples.
                              definitely works better (and cheaper) when dealing with b-splines (not in this examples)

    '''
    u = np.clip(u,0,1) # Clip u queries between 0 and 1

    # Create spline points
    samples = np.linspace(0,1,steps)
    tck,u_=interpolate.splprep(cv.T,s=0.0)
    p = np.array(interpolate.splev(samples,tck)).T  
    # at first i thought that passing my query list to splev instead
    # of np.linspace would do the trick, but apparently not.    

    # Approximate spline length by adding all the segments
    p_= np.diff(p,axis=0) # get distances between segments
    m = np.sqrt((p_*p_).sum(axis=1)) # segment magnitudes
    s = np.cumsum(m) # cumulative summation of magnitudes
    s/=s[-1] # normalize distances using its total length

    # Find closest index boundaries
    s = np.insert(s,0,0) # prepend with 0 for proper index matching
    i0 = (s.searchsorted(u,side='left')-1).clip(min=0) # Find closest lowest boundary position
    i1 = i0+1 # upper boundary will be the next up

    # Return projection on segments for each query
    if projection:
        return ((p[i1]-p[i0])*((u-s[i0])/(s[i1]-s[i0]))[:,None])+p[i0]

    # Else, modulate parametric samples and and pass back to splev
    mod = (((u-s[i0])/(s[i1]-s[i0]))/steps)+samples[i0]
    return np.array(interpolate.splev(mod,tck)).T  

这是一个用法示例:

import matplotlib.pyplot as plt

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


# Lets plot a few queries
u = [0.,0.2,0.3,0.5,1.0]
steps = 10000 # The more subdivisions the better
x,y,z = uQuery(cv,u,steps).T
fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
for i, txt in enumerate(u):
    ax.annotate('  u=%s'%txt, (x[i],y[i]))

# Plot the curve we're sampling
tck,u_=interpolate.splprep(cv.T,s=0.0)
x,y,z = np.array(interpolate.splev(np.linspace(0,1,1000),tck))
plt.plot(x,y,'k-',label='Curve')

# Plot control points
p = cv.T
plt.scatter(p[0],p[1],s=80, facecolors='none', edgecolors='r',label='Control Points')

plt.minorticks_on()
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(35, 70)
plt.ylim(0, 30)
plt.gca().set_aspect('equal', adjustable='box')
plt.show()

以及由此产生的情节:

【问题讨论】:

    标签: python numpy scipy spline


    【解决方案1】:

    抱歉我之前的评论,我误解了这个问题。

    请注意,我会将您的查询称为 w = [0.,0.2,0.3,0.5,1.0],因为我将 u 用于其他内容。

    遗憾的是,您的问题没有简单的解决方案,因为它意味着计算三次样条的长度,这并非易事。但是有一种方法可以使用集成和优化 scipy 库来简化代码,因此您不必担心精度。

    首先您必须了解,在底层,splprep 创建了一个形状为 x=Fx(u) 和 y=Fy(u) 的三次样条曲线,其中u 是一个从 0 到 1 的参数,但是与样条的长度不是线性相关的,例如对于这个控制点:

    cv = np.array([[ 0.,  0.,  0.],
       [ 100,  25,   0.],
       [ 0.,  50.,   0.],
       [ 100,  75,   0.],
       [ 0.,   100.,  0.]])
    

    您可以看到参数u 的行为方式。值得注意的是,您可以为控制点定义您想要的u 值,这会对样条曲线的形状产生影响。

    现在,当您调用splev 时,您实际上是在询问给定u 参数的样条线坐标。所以为了做你想做的事,你需要找到给定的样条长度分数的u

    首先,为了得到样条的总长度,你可以做的不多,但是像你做的那样进行数值积分,但是你可以使用 scipy 的集成库来更容易地做到这一点。

    import scipy.integrate as integrate
    
    def foo(u):
         xx,yy,zz=interpolate.splev(u,tck,der=1)
         return (xx**2 + yy**2)**0.5
    
    total_length=integrate.quad(foo,0,1)[0]
    

    获得样条线的总长度后,您可以使用optimize 库来查找u 的值,该值将整合到您想要的长度的分数。并且使用这个desired_usplev 会给你你想要的坐标。

    import scipy.optimize as optimize
    
    desired_u=optimize.fsolve(lambda uu:  integrate.quad(foo,0,uu)[0]-w*total_length,0)[0]
    
    x,y,z = np.array(interpolate.splev(desired_u,tck))
    


    编辑:我测量了我的方法与你的方法的性能,你的方法更快,也非常精确,唯一最差的指标是内存分配。我找到了一种方法来加快我的方法,但内存分配仍然很低,但它牺牲了精度。

    我将使用 100 个查询点作为测试。

    我现在的方法:

    time = 21.686723 s
    memory allocation = 122.880 kb
    

    我将使用我的方法给出的点作为真实坐标,并测量每种方法与这些点之间的 100 个点的平均距离

    你现在的方法:

    time = 0.008699 s
    memory allocation = 1,187.840 kb
    Average distance = 1.74857994144e-06
    

    可以通过不在每个点的积分上使用fsolve,而是通过从点样本创建插值函数u=F(w),然后使用该函数来提高我的方法的速度,这将是更快。

    import scipy.interpolate as interpolate
    import scipy.integrate as integrate
    
    
    def foo(u):
         xx,yy,zz=interpolate.splev(u,tck,der=1)
         return (xx**2 + yy**2)**0.5
    
    total_length=integrate.quad(foo,0,1)[0]
    
    yu=[integrate.quad(foo,0,uu)[0]/total for uu in np.linspace(0,1,50)]
    
    find_u=interpolate.interp1d(yu,np.linspace(0,1,50))
    
    x,y,z=interpolate.splev(find_u(w),tck)
    

    我得到了 50 个样本:

    time = 1.280629 s
    memory allocation = 20.480 kb
    Average distance = 0.226036973904
    

    这比以前快得多,但仍然不如你的快,精度也不如你的,但在内存方面要好得多。但这取决于样本数量。

    你的方法有 1000 和 100 分:

    1000 points
    time = 0.002354 s
    memory allocation = 167.936 kb
    Average distance = 0.000176413655938
    
    100 points
    time = 0.001641 s
    memory allocation = 61.440 kb
    Average distance = 0.0179918600812
    

    我的方法有 20 个和 100 个样本

    20 samples
    time = 0.514241 s
    memory allocation = 14.384 kb
    Average distance = 1.42356341648
    
    100 samples
    time = 2.45364 s
    memory allocation = 24.576 kb
    Average distance = 0.0506075927139
    

    考虑到所有因素,我认为你的方法更好,点数适合所需的精度,我的方法只有更少的代码行。

    编辑 2:我只是意识到别的东西,你的方法可以在样条线之外给出点,而我的总是在样条线中,这取决于你在做什么这可能很重要

    【讨论】:

    • 感谢所有澄清!我从你的解释中学到了很多,我真的很感激!我将您的答案写成一个函数来针对我的进行测试。你的给了我一个更精确的结果,但运行起来要贵得多。 5 个查询的 0.002 秒与 0.495 秒。对于 100 次查询,我的方法没有受到影响,但你的方法用了 11.568 秒,差别很大。所以它不符合我对速度的标准,但它肯定更简单。
    • 哇,我知道会有不同,但我没想到会那么大。我猜罪魁祸首是fsolve,我看看能不能加快速度。如果我找到什么我会告诉你的
    • 是的,单个样本的 fsolve 需要 0.140 秒。顺便说一句,因为您似乎对样条曲线了解很多,如果您能在this other question i asked 上给我您的意见,我将不胜感激!
    • 抱歉回复晚了,我周末出去了。我将编辑我的答案以包括时间和内存分配的基准测试。还有如何加快我的方法。但是 tl;dr 你的方法更好。
    猜你喜欢
    • 2021-08-06
    • 2018-02-12
    • 1970-01-01
    • 2016-06-13
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 2023-01-03
    • 1970-01-01
    相关资源
    最近更新 更多