B样条曲线——de Boor递推算法实现
1. 定义
为保留Bezier方法的优点,B样条曲线的方程定义为
其中,
是控制多边形的顶点,
称为
阶(
次)B样条基函数,其中每一个称为B样条,它是一个由称为节点矢量的费递增参数
的序列
:
所决定的
阶分段多项式,即为
阶(
次)B多项式样条。
下面是基函数的递推公式,也称为de Boor-Cox公式:
并约定
。
递推公式表明:欲确定第
个
阶B样条
需要用到
共
个节点,称区间
为
的支撑区间。曲线方程中,
个控制节点
要用到
个
阶B样条基
。它们的支撑区间的并集定义了这一组B样条基的节点矢量
。
2. 性质
2.1 局部性
移动该曲线的第 个控制顶点 至多影响定义在区间 上那部分曲线的形状,对曲线的其余部分不产生影响。
2.2 连续性
在 重节点 处的连续阶不低于 ;整条曲线 的连续阶不低于 ,其中 表示位于区间 内节点的最大重数。
2.3 凸包性
在区间 , 上的部分位于 个点 的凸包 内,整条曲线则位于各凸包 的并集 内。
3. B样条曲线类型的划分
3.1 均匀B样条曲线
节点矢量中节点为沿参数轴均匀或等距分布,所有节点区间长度 ,这样的节点矢量定义了均匀B样条基。
3.2 准均匀B样条曲线
准均匀B样条与均匀B样条曲线的差别在于两端点具有重复度k,这样的节点矢量定义了准均匀的B样条曲线。
3.3 分段Bezier曲线
节点矢量中两端点具有重复度 ,所有内节点重复度为k-1,这样的节点矢量定义了分段的Bernstein基。
3.4 非均匀B样条曲线
在这种类型里,任意分布的节点矢量 ,只要在数学上成立(节点序列递增,两端节点的重复度 ,内部节点重复度 )都可以取。这样的节点矢量定义了非均匀B样条基。
4. de Boor算法
先将
固定在区间
上,由de Boor-Cox公式有
现令
其中
最终得到
5. python程序实现均匀B样条曲线
# -*- coding: utf-8 -*-
import numpy as np
from scipy.special import comb, perm
from matplotlib import pyplot as plt
class MyB:
def __init__(self, line):
self.line = line
self.index_02 = None #保存拖动的这个点的索引
self.press = None # 状态标识,1为按下,None为没按下
self.pick = None # 状态标识,1为选中点并按下,None为没选中
self.motion = None #状态标识,1为进入拖动,None为不拖动
self.xs = list() # 保存点的x坐标
self.ys = list() # 保存点的y坐标
self.cidpress = line.figure.canvas.mpl_connect('button_press_event', self.on_press) # 鼠标按下事件
self.cidrelease = line.figure.canvas.mpl_connect('button_release_event', self.on_release) # 鼠标放开事件
self.cidmotion = line.figure.canvas.mpl_connect('motion_notify_event', self.on_motion) # 鼠标拖动事件
self.cidpick = line.figure.canvas.mpl_connect('pick_event', self.on_picker) # 鼠标选中事件
def on_press(self, event): # 鼠标按下调用
if event.inaxes!=self.line.axes: return
self.press = 1
def on_motion(self, event): # 鼠标拖动调用
if event.inaxes!=self.line.axes: return
if self.press is None: return
if self.pick is None: return
if self.motion is None: # 整个if获取鼠标选中的点是哪个点
self.motion = 1
x = self.xs
xdata = event.xdata
ydata = event.ydata
index_01 = 0
for i in x:
if abs(i - xdata) < 0.02: # 0.02 为点的半径
if abs(self.ys[index_01] - ydata) < 0.02:break
index_01 = index_01 + 1
self.index_02 = index_01
if self.index_02 is None: return
self.xs[self.index_02] = event.xdata # 鼠标的坐标覆盖选中的点的坐标
self.ys[self.index_02] = event.ydata
self.draw_01()
def on_release(self, event): # 鼠标按下调用
if event.inaxes!=self.line.axes: return
if self.pick == None: # 如果不是选中点,那就添加点
self.xs.append(event.xdata)
self.ys.append(event.ydata)
if self.pick == 1 and self.motion != 1: # 如果是选中点,但不是拖动点,那就降阶
x = self.xs
xdata = event.xdata
ydata = event.ydata
index_01 = 0
for i in x:
if abs(i - xdata) < 0.02:
if abs(self.ys[index_01] - ydata) < 0.02: break
index_01 = index_01 + 1
self.xs.pop(index_01)
self.ys.pop(index_01)
self.draw_01()
self.pick = None # 所有状态恢复,鼠标按下到稀放为一个周期
self.motion = None
self.press = None
self.index_02 = None
def on_picker(self, event): # 选中调用
self.pick = 1
def draw_01(self): # 绘图
self.line.clear() # 不清除的话会保留原有的图
self.line.axis([0,1,0,1]) # x和y范围0到1
self.b(self.xs,self.ys) # B样条曲线
self.line.scatter(self.xs, self.ys,color='b',s=200, marker="o",picker=5) # 画点
self.line.plot(self.xs, self.ys,color='r') # 画线
self.line.figure.canvas.draw() # 重构子图
def b(self,*args): # Bezier曲线公式转换,获取x和y
k = 3 # 阶数
n = len(args[0])-1 # 顶点的个数-1
T = np.linspace(1,10,n+k+1) # T 范围1到10,均匀B样条曲线
# if n >= k-1:
# T = [1]*k+(np.linspace(2,9,n-k+1)).tolist()+[10]*k # 准均匀样条
x,y = [],[]
# 递推公式
# def de_Boor(r,t,i):
# if r == 0:
# return [args[0][i],args[1][i]]
# else:
# return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor(r-1,t,i-1)
def de_Boor_x(r,t,i):
if r == 0:
return args[0][i]
else:
if T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] != 0:
return ((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i-1)
elif T[i+k-r]-T[i] != 0 and T[i+k-r]-T[i] == 0:
return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i)
elif T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] == 0:
return 0
return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i-1)
def de_Boor_y(r,t,i):
if r == 0:
return args[1][i]
else:
if T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] != 0:
return ((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i-1)
elif T[i+k-r]-T[i] != 0 and T[i+k-r]-T[i] == 0:
return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i)
elif T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] == 0:
return 0
return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i-1)
def plot(x,y):
for j in range(k-1,n+1):
for t in np.linspace(T[j],T[j+1]):
x.append(de_Boor_x(k-1,t,j))
y.append(de_Boor_y(k-1,t,j))
#print(x,y)
self.line.plot(x,y)
if n >= k-1:
plot(x,y)
fig = plt.figure(2,figsize=(12,6)) #创建第2个绘图对象,1200*600像素
ax = fig.add_subplot(111) #一行一列第一个子图
ax.set_title('My B')
myBezier = MyB(ax)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
效果