2016-01-15 83 views
6

我需要在python中計算3D bspline曲線。我查看了scipy.interpolate.splprep和其他一些scipy模塊,但找不到任何能給我所需的東西。所以我寫下了自己的模塊。代碼工作正常,但速度很慢(測試函數運行在0.03s,這似乎很多,考慮到我只要求具有6個控制頂點的100個樣本)。numpy/scipy的快速b樣條算法

有沒有一種方法可以簡化下面的幾個scipy模塊調用的代碼,這可能會加快速度?如果不是,我可以通過我的代碼來改善其性能?

import numpy as np 

# cv = np.array of 3d control vertices 
# n = number of samples (default: 100) 
# d = curve degree (default: cubic) 
# closed = is the curve closed (periodic) or open? (default: open) 
def bspline(cv, n=100, d=3, closed=False): 

    # Create a range of u values 
    count = len(cv) 
    knots = None 
    u = None 
    if not closed: 
     u = np.arange(0,n,dtype='float')/(n-1) * (count-d) 
     knots = np.array([0]*d + range(count-d+1) + [count-d]*d,dtype='int') 
    else: 
     u = ((np.arange(0,n,dtype='float')/(n-1) * count) - (0.5 * (d-1))) % count # keep u=0 relative to 1st cv 
     knots = np.arange(0-d,count+d+d-1,dtype='int') 


    # Simple Cox - DeBoor recursion 
    def coxDeBoor(u, k, d): 

     # Test for end conditions 
     if (d == 0): 
      if (knots[k] <= u and u < knots[k+1]): 
       return 1 
      return 0 

     Den1 = knots[k+d] - knots[k] 
     Den2 = knots[k+d+1] - knots[k+1] 
     Eq1 = 0; 
     Eq2 = 0; 

     if Den1 > 0: 
      Eq1 = ((u-knots[k])/Den1) * coxDeBoor(u,k,(d-1)) 
     if Den2 > 0: 
      Eq2 = ((knots[k+d+1]-u)/Den2) * coxDeBoor(u,(k+1),(d-1)) 

     return Eq1 + Eq2 


    # Sample the curve at each u value 
    samples = np.zeros((n,3)) 
    for i in xrange(n): 
     if not closed: 
      if u[i] == count-d: 
       samples[i] = np.array(cv[-1]) 
      else: 
       for k in xrange(count): 
        samples[i] += coxDeBoor(u[i],k,d) * cv[k] 

     else: 
      for k in xrange(count+d): 
       samples[i] += coxDeBoor(u[i],k,d) * cv[k%count] 


    return samples 




if __name__ == "__main__": 
    import matplotlib.pyplot as plt 
    def test(closed): 
     cv = np.array([[ 50., 25., -0.], 
       [ 59., 12., -0.], 
       [ 50., 10., 0.], 
       [ 57., 2., 0.], 
       [ 40., 4., 0.], 
       [ 40., 14., -0.]]) 

     p = bspline(cv,closed=closed) 
     x,y,z = p.T 
     cv = cv.T 
     plt.plot(cv[0],cv[1], 'o-', label='Control Points') 
     plt.plot(x,y,'k-',label='Curve') 
     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() 

    test(False) 

下面的兩張圖片顯示了我的代碼既封閉條件返回: Open curve Closed curve

回答

8

因此,在深入瞭解我的問題和很多研究之後,我終於得到了我的答案。一切都在scipy中可用,我在這裏把我的代碼,所以希望別人可以找到這個有用的。

該函數接受N-d個點的數組,曲線度數,週期狀態(打開或關閉),並沿該曲線返回n個樣本。有一些方法可以確保曲線樣本是等距的,但是我暫時將重點放在這個問題上,因爲它全部是關於速度的。

值得一提的是:我似乎無法超越20度的曲線。當然,這已經過分了,但我認爲值得一提。

另外值得一提的:我的機器上下面的代碼可以計算0.017s

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) 
    else: 
     kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree) 

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


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

10萬個樣本爲了測試它:

兩個打開或週期性的曲線
import matplotlib.pyplot as plt 
colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k') 

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

plt.plot(cv[:,0],cv[:,1], 'o-', label='Control Points') 

for d in range(1,21): 
    p = bspline(cv,n=100,degree=d,periodic=True) 
    x,y = p.T 
    plt.plot(x,y,'k-',label='Degree %s'%d,color=colors[d%len(colors)]) 

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() 

結果:

Opened curve Periodic (closed) curve

附錄

從scipy-0.19.0起,有一個新的scipy.interpolate.BSpline函數可以使用。

import numpy as np 
import scipy.interpolate as si 
def scipy_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 
    """ 
    cv = np.asarray(cv) 
    count = cv.shape[0] 

    # Closed curve 
    if periodic: 
     kv = np.arange(-degree,count+degree+1) 
     factor, fraction = divmod(count+degree+1, count) 
     cv = np.roll(np.concatenate((cv,) * factor + (cv[:fraction],)),-1,axis=0) 
     degree = np.clip(degree,1,degree) 

    # Opened curve 
    else: 
     degree = np.clip(degree,1,count-1) 
     kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree) 

    # Return samples 
    max_param = count - (degree * (1-periodic)) 
    spl = si.BSpline(kv, cv, degree) 
    return spl(np.linspace(0,max_param,n)) 

測試的等效:

p1 = bspline(cv,n=10**6,degree=3,periodic=True) # 1 million samples: 0.0882 sec 
p2 = scipy_bspline(cv,n=10**6,degree=3,periodic=True) # 1 million samples: 0.0789 sec 
print np.allclose(p1,p2) # returns True 
+0

真棒,非常感謝!在我的應用程序中完美地工作,其中有兩個結束的三維座標和一堆已知的三維控制點。它非常好地繪製樣條曲線! BRAVO!我正在使用三維圖像數據的ndarrays。 – kabammi

+0

太棒了!你提示我編輯我的答案,並在最後刪除不需要的for循環。我還在最後提出了一個附錄,提到在scipy中添加的官方BSpline函數0.19.0 – Fnord

+0

嗯......我遇到scipy_bspline函數的錯誤。我將一個列表作爲CV傳遞,因此cv = np.asarray(cv)對您的原始函數有幫助。然後我使用degree = 5,新函數會拋出一個錯誤,並告訴我需要至少12節...舊代碼不關心,只是工作。所以舊代碼爲我贏得勝利。 :) – kabammi

1

給予優化建議,而不分析數據有點像在黑暗中拍攝。但是,函數coxDeBoor似乎經常被調用。這是我開始優化的地方。

Python中的函數調用are expensive。您應該嘗試用迭代替換coxDeBoor遞歸以避免過多的函數調用。在this question的答案中可以找到一些一般信息如何做到這一點。作爲堆棧/隊列,您可以使用collections.deque