插值-樣條插值


百度百科定義

插值:在離散數據的基礎上插補連續函數,使得這條連續曲線經過全部離散點,同時也可以估計出函數在其他點的近似值。

樣條插值:一種以 可變樣條 來作出一條經過一系列點的光滑曲線的數學方法。插值樣條是由一些多項式組成的,每一個多項式都是由相鄰的兩個數據點決定的,這樣,任意的兩個相鄰的多項式以及它們的導數在連接點處都是連續的。

 

樣條插值法

簡單理解,就是每兩個點之間確定一個函數,這個函數就是一個樣條,函數不同,樣條就不同,所以定義中說 可變樣條,然后把所有樣條分段結合成一個函數,就是最終的插值函數。

 

思路1 - 線性樣條

兩點確定一條直線,我們可以在每兩點間畫一條直線,就可以把所有點連起來。

顯然曲線不夠光滑,究其原因是因為連接點處導數不相同

 

思路2 - 二次樣條

直線不行,用曲線代替,二次函數是最簡單的曲線。

假設4個點,x0,x1,x2,x3,有3個區間,需要3個二次樣條,每個二次樣條為  ax^2+bx+c,故總計9個未知數。

1. x0,x3兩個端點都有一個二次函數經過,可確定2個方程

2. x1,x2兩個中間點都有兩個二次函數經過,可確定4個方程

3. 中間點處必須連續,需要保證左右二次函數一階導相等

    2*a1*x1+b1=2*a2*x1+b2

            2*a2*x2+b2=2*a3*x2+b3

可確定2個方程,此時有了8個方程。

4. 這里假設第一方程的二階導為0,即 a1=0,又是一個方程,共計9個方程。   【見補充】 

聯立即可求解。

 

python 實現

# encoding:utf-8
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
"""
二次樣條實現
"""
x = [3, 4.5, 7, 9]
y = [2.5, 1, 2.5, 0.5]

def calculateEquationParameters(x):
    #parameter為二維數組,用來存放參數,sizeOfInterval是用來存放區間的個數
    parameter = []
    sizeOfInterval=len(x)-1
    i = 1
    #首先輸入方程兩邊相鄰節點處函數值相等的方程為2n-2個方程
    while i < len(x)-1:
        data = init(sizeOfInterval*3)
        data[(i-1)*3]=x[i]*x[i]
        data[(i-1)*3+1]=x[i]
        data[(i-1)*3+2]=1
        data1 =init(sizeOfInterval*3)
        data1[i * 3] = x[i] * x[i]
        data1[i * 3 + 1] = x[i]
        data1[i * 3 + 2] = 1
        temp=data[1:]
        parameter.append(temp)
        temp=data1[1:]
        parameter.append(temp)
        i += 1
    #輸入端點處的函數值。為兩個方程,加上前面的2n-2個方程,一共2n個方程
    data = init(sizeOfInterval*3-1)
    data[0] = x[0]
    data[1] = 1
    parameter.append(data)
    data = init(sizeOfInterval *3)
    data[(sizeOfInterval-1)*3+0] = x[-1] * x[-1]
    data[(sizeOfInterval-1)*3+1] = x[-1]
    data[(sizeOfInterval-1)*3+2] = 1
    temp=data[1:]
    parameter.append(temp)
    #端點函數值相等為n-1個方程。加上前面的方程為3n-1個方程,最后一個方程為a1=0總共為3n個方程
    i=1
    while i < len(x) - 1:
        data = init(sizeOfInterval * 3)
        data[(i - 1) * 3] =2*x[i]
        data[(i - 1) * 3 + 1] =1
        data[i*3]=-2*x[i]
        data[i*3+1]=-1
        temp=data[1:]
        parameter.append(temp)
        i += 1
    return parameter

"""
對一個size大小的元組初始化為0
"""
def init(size):
    j = 0
    data = []
    while j < size:
        data.append(0)
        j += 1
    return data


"""
功能:計算樣條函數的系數。
參數:parametes為方程的系數,y為要插值函數的因變量。
返回值:二次插值函數的系數。
"""

def solutionOfEquation(parametes,y):
    sizeOfInterval = len(x) - 1
    result = init(sizeOfInterval*3-1)
    i=1
    while i<sizeOfInterval:
        result[(i-1)*2]=y[i]
        result[(i-1)*2+1]=y[i]
        i+=1
    result[(sizeOfInterval-1)*2]=y[0]
    result[(sizeOfInterval-1)*2+1]=y[-1]
    a = np.array(calculateEquationParameters(x))
    b = np.array(result)
    return np.linalg.solve(a,b)

"""
功能:根據所給參數,計算二次函數的函數值:
參數:parameters為二次函數的系數,x為自變量
返回值:為函數的因變量
"""
def calculate(paremeters,x):
    result=[]
    for data_x in x:
        result.append(paremeters[0]*data_x*data_x+paremeters[1]*data_x+paremeters[2])
    return  result


"""
功能:將函數繪制成圖像
參數:data_x,data_y為離散的點.new_data_x,new_data_y為由拉格朗日插值函數計算的值。x為函數的預測值。
返回值:空
"""
def  Draw(data_x,data_y,new_data_x,new_data_y):
        plt.plot(new_data_x, new_data_y, label=u"擬合曲線", color="black")
        plt.scatter(data_x,data_y, label=u"離散數據",color="red")
        mpl.rcParams['font.sans-serif'] = ['SimHei']
        mpl.rcParams['axes.unicode_minus'] = False
        plt.title(u"二次樣條函數")
        plt.legend(loc="upper left")
        plt.show()

result=solutionOfEquation(calculateEquationParameters(x),y)
new_data_x1=np.arange(3, 4.5, 0.1)
new_data_y1=calculate([0,result[0],result[1]],new_data_x1)
new_data_x2=np.arange(4.5, 7, 0.1)
new_data_y2=calculate([result[2],result[3],result[4]],new_data_x2)
new_data_x3=np.arange(7, 9.5, 0.1)
new_data_y3=calculate([result[5],result[6],result[7]],new_data_x3)
new_data_x=[]
new_data_y=[]
new_data_x.extend(new_data_x1)
new_data_x.extend(new_data_x2)
new_data_x.extend(new_data_x3)
new_data_y.extend(new_data_y1)
new_data_y.extend(new_data_y2)
new_data_y.extend(new_data_y3)
Draw(x,y,new_data_x,new_data_y)

可以看到 y 是多段二次函數拼接而成。

 

輸出

二次樣條插值連續光滑,看起來效果還行。

只是前兩個點之間是條直線,因為假設a1=0,二次函數變成b1x+c1,顯然是直線;

而且最后兩個點之間過於陡峭 

 

思路3 - 三次樣條

二次函數最高項系數為0,導致變成直線,那三次函數最高項系數為0,還是曲線,插值效果應該更好。

三次樣條思路與二次樣條基本相同,

同樣假設4個點,x0,x1,x2,x3,有3個區間,需要3個三次樣條,每個三次樣條為  ax^3+bx^2+cx+d,故總計12個未知數。

1. 內部節點處的函數值應該相等,這里一共是4個方程。

2. 函數的第一個端點和最后一個端點,應該分別在第一個方程和最后一個方程中。這里是2個方程。

3. 兩個函數在節點處的一階導數應該相等。這里是兩個方程。

4. 兩個函數在節點處的二階導數應該相等,這里是兩個方程。       【見補充】

5. 假設端點處的二階導數為零,這里是兩個方程。          【見補充】

           a1=0 

           b1=0


python 實現

# encoding:utf-8
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
"""
三次樣條實現
"""
x = [3, 4.5, 7, 9]
y = [2.5, 1, 2.5, 0.5]

def calculateEquationParameters(x):
    #parameter為二維數組,用來存放參數,sizeOfInterval是用來存放區間的個數
    parameter = []
    sizeOfInterval=len(x)-1;
    i = 1
    #首先輸入方程兩邊相鄰節點處函數值相等的方程為2n-2個方程
    while i < len(x)-1:
        data = init(sizeOfInterval*4)
        data[(i-1)*4] = x[i]*x[i]*x[i]
        data[(i-1)*4+1] = x[i]*x[i]
        data[(i-1)*4+2] = x[i]
        data[(i-1)*4+3] = 1
        data1 =init(sizeOfInterval*4)
        data1[i*4] =x[i]*x[i]*x[i]
        data1[i*4+1] =x[i]*x[i]
        data1[i*4+2] =x[i]
        data1[i*4+3] = 1
        temp = data[2:]
        parameter.append(temp)
        temp = data1[2:]
        parameter.append(temp)
        i += 1
    # 輸入端點處的函數值。為兩個方程, 加上前面的2n - 2個方程,一共2n個方程
    data = init(sizeOfInterval * 4 - 2)
    data[0] = x[0]
    data[1] = 1
    parameter.append(data)
    data = init(sizeOfInterval * 4)
    data[(sizeOfInterval - 1) * 4 ] = x[-1] * x[-1] * x[-1]
    data[(sizeOfInterval - 1) * 4 + 1] = x[-1] * x[-1]
    data[(sizeOfInterval - 1) * 4 + 2] = x[-1]
    data[(sizeOfInterval - 1) * 4 + 3] = 1
    temp = data[2:]
    parameter.append(temp)
    # 端點函數一階導數值相等為n-1個方程。加上前面的方程為3n-1個方程。
    i=1
    while i < sizeOfInterval:
        data = init(sizeOfInterval * 4)
        data[(i - 1) * 4] = 3 * x[i] * x[i]
        data[(i - 1) * 4 + 1] = 2 * x[i]
        data[(i - 1) * 4 + 2] = 1
        data[i * 4] = -3 * x[i] * x[i]
        data[i * 4 + 1] = -2 * x[i]
        data[i * 4 + 2] = -1
        temp = data[2:]
        parameter.append(temp)
        i += 1
    # 端點函數二階導數值相等為n-1個方程。加上前面的方程為4n-2個方程。且端點處的函數值的二階導數為零,為兩個方程。總共為4n個方程。
    i = 1
    while i < len(x) - 1:
        data = init(sizeOfInterval * 4)
        data[(i - 1) * 4] = 6 * x[i]
        data[(i - 1) * 4 + 1] = 2
        data[i * 4] = -6 * x[i]
        data[i * 4 + 1] = -2
        temp = data[2:]
        parameter.append(temp)
        i += 1
    return parameter



"""
對一個size大小的元組初始化為0
"""
def init(size):
    j = 0
    data = []
    while j < size:
        data.append(0)
        j += 1
    return data

"""
功能:計算樣條函數的系數。
參數:parametes為方程的系數,y為要插值函數的因變量。
返回值:三次插值函數的系數。
"""
def solutionOfEquation(parametes,y):
    sizeOfInterval = len(x) - 1
    result = init(sizeOfInterval*4-2)
    i=1
    while i<sizeOfInterval:
        result[(i-1)*2]=y[i]
        result[(i-1)*2+1]=y[i]
        i+=1
    result[(sizeOfInterval-1)*2]=y[0]
    result[(sizeOfInterval-1)*2+1]=y[-1]
    a = np.array(calculateEquationParameters(x))
    b = np.array(result)
    for data_x in b:
        print(data_x)
    return np.linalg.solve(a,b)

"""
功能:根據所給參數,計算三次函數的函數值:
參數:parameters為二次函數的系數,x為自變量
返回值:為函數的因變量
"""
def calculate(paremeters,x):
    result=[]
    for data_x in x:
        result.append(paremeters[0]*data_x*data_x*data_x+paremeters[1]*data_x*data_x+paremeters[2]*data_x+paremeters[3])
    return  result


"""
功能:將函數繪制成圖像
參數:data_x,data_y為離散的點.new_data_x,new_data_y為由拉格朗日插值函數計算的值。x為函數的預測值。
返回值:空
"""
def  Draw(data_x,data_y,new_data_x,new_data_y):
        plt.plot(new_data_x, new_data_y, label=u"擬合曲線", color="black")
        plt.scatter(data_x,data_y, label=u"離散數據",color="red")
        mpl.rcParams['font.sans-serif'] = ['SimHei']
        mpl.rcParams['axes.unicode_minus'] = False
        plt.title(u"三次樣條函數")
        plt.legend(loc="upper left")
        plt.show()


result=solutionOfEquation(calculateEquationParameters(x),y)
new_data_x1=np.arange(3, 4.5, 0.1)
new_data_y1=calculate([0,0,result[0],result[1]],new_data_x1)
new_data_x2=np.arange(4.5, 7, 0.1)
new_data_y2=calculate([result[2],result[3],result[4],result[5]],new_data_x2)
new_data_x3=np.arange(7, 9.5, 0.1)
new_data_y3=calculate([result[6],result[7],result[8],result[9]],new_data_x3)
new_data_x=[]
new_data_y=[]
new_data_x.extend(new_data_x1)
new_data_x.extend(new_data_x2)
new_data_x.extend(new_data_x3)
new_data_y.extend(new_data_y1)
new_data_y.extend(new_data_y2)
new_data_y.extend(new_data_y3)
Draw(x,y,new_data_x,new_data_y)

 

輸出

 

補充

微分連續性

s 代表三次樣條,s'是一階導,s''是二階導

 

端點條件

上面我們對端點處的樣條進行了假設,為什么呢?其實端點可以有多種不同的限制,常見有3種。

自由邊界 Natural

首尾兩端沒有受到任何使他們彎曲的力,二次樣條就是 s’=0,三次樣條就是 s''=0

固定邊界 Clamped

首尾兩端點的微分值被指定

非節點邊界 Not-A-Knot

把端點當做中間點處理,三次函數不做假設,即

 

不同邊界的比較

 

 

 

參考資料:

https://blog.csdn.net/flyingleo1981/article/details/53008931  三次樣條插值(Cubic Spline Interpolation)及代碼實現(C語言)

https://blog.csdn.net/deramer1/article/details/79034201  三次樣條插值法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM