1. 定义
给定
n
+
1
n+1
n+1个点的位置矢量
P
i
(
i
=
0
,
1
,
…
,
n
)
P_i(i=0,1,\dots,n)
Pi(i=0,1,…,n),则Bezier曲线可以定义为
P
(
t
)
=
∑
i
=
0
n
P
i
B
i
,
n
(
t
)
,
t
∈
[
0
,
1
]
P(t)=\sum_{i=0}^nP_iB_{i,n}(t),\quad t \in [0,1]
P(t)=i=0∑nPiBi,n(t),t∈[0,1]
其中
P
i
P_i
Pi(i=0,1,\dots,n)构成该Bezier曲线的特征多边形,
B
i
,
n
(
t
)
B_{i,n}(t)
Bi,n(t)是
n
n
n次Bernstein基函数
B
i
,
n
(
t
)
=
C
n
i
t
i
(
1
−
t
)
n
−
i
=
n
!
i
!
(
n
−
i
)
!
t
i
⋅
(
1
−
t
)
n
−
i
,
(
i
=
0
,
1
,
…
,
n
)
B_{i,n}(t) = C_n^it^i(1-t)^{n-i}=\frac{n!}{i!(n-i)!}t^i\cdot (1-t)^{n-i},\quad (i=0,1,\dots,n)
Bi,n(t)=Cniti(1−t)n−i=i!(n−i)!n!ti⋅(1−t)n−i,(i=0,1,…,n)
其中
0
0
=
1
,
0
!
=
1
0^0=1,0!=1
00=1,0!=1。
2. Bezier曲线的递推算法
计算Bez曲线上的点,可用Bezier曲线方程直接计算,但使用de Casteljau提出的递推算法则简单得多。
由
n
+
1
n+1
n+1个控制点
P
i
(
i
=
0
,
1
,
…
,
n
)
P_i(i=0,1,\dots,n)
Pi(i=0,1,…,n)定义的
n
n
n次Bezier曲线
P
0
n
P_0^n
P0n可被定义为分别由前、后
n
n
n个控制点定义的两条
n
−
1
n-1
n−1次Bezier曲线
P
0
n
−
1
P_0^{n-1}
P0n−1与
P
1
n
−
1
P_1^{n-1}
P1n−1的线性组合
P
0
n
=
(
1
−
t
)
P
0
n
−
1
+
t
P
1
n
−
1
,
t
∈
[
0
,
1
]
P_0^n = (1-t)P_0^{n-1}+tP_1^{n-1},\quad t \in[0,1]
P0n=(1−t)P0n−1+tP1n−1,t∈[0,1]
由此得到Bezier曲线的递推计算公式为
P
i
k
=
{
P
i
,
k
=
0
(
1
−
t
)
P
i
k
−
1
+
t
P
i
+
1
k
−
1
,
k
=
1
,
2
,
…
,
n
,
i
=
0
,
1
,
…
,
n
−
k
P_i^k= \begin{cases} P_i, & k=0 \\ (1-t)P_i^{k-1}+tP_{i+1}^{k-1}, & k=1,2,\dots,n,i=0,1,\dots,n-k \end{cases}
Pik={Pi,(1−t)Pik−1+tPi+1k−1,k=0k=1,2,…,n,i=0,1,…,n−k
3. 代码实现(python)
如果直接采用递归的方法会有大量的重复计算,这样的求解和斐波那契数列的递归方法类似,参考网上做法,可以使用三个for循环可以简化该方法。
# -*- coding: UTF-8 -*-
import numpy as np
from scipy.special import comb, perm
from matplotlib import pyplot as plt
class MyBezier:
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.bezier(self.xs,self.ys) # Bezier曲线
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 bezier(self,*args): # Bezier曲线公式转换,获取x和y
n = len(args[0]) # 点的个数
xarray,yarray = [],[]
x,y = [],[]
index = 0
for t in np.linspace(0,1):
for i in range(1,n):
for j in range(0,n-i):
if i == 1:
xarray.insert(j,args[0][j]*(1-t) + args[0][j+1]*t)
yarray.insert(j,args[1][j]*(1-t) + args[1][j+1]*t)
continue
# i != 1时,通过上一次迭代的结果计算
xarray[j] = xarray[j]*(1 - t) + xarray[j+1]*t
yarray[j] = yarray[j]*(1 - t) + yarray[j+1]*t
if n == 1:
x.insert(index,args[0][0])
y.insert(index,args[1][0])
else:
x.insert(index,xarray[0])
y.insert(index,yarray[0])
xarray = []
yarray = []
index = index+1
self.line.plot(x,y)
fig = plt.figure(2,figsize=(12,6)) #创建第2个绘图对象,1200*600像素
ax = fig.add_subplot(111) #一行一列第一个子图
ax.set_title('My Bezier')
myBezier = MyBezier(ax)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
结果演示:

参考博客:
https://www.cnblogs.com/GH-123/p/7898774.html
https://blog.youkuaiyun.com/lafengxiaoyu/article/details/51296411
https://blog.youkuaiyun.com/lafengxiaoyu/article/details/56294678
这篇博客介绍了Bezier曲线的基本定义,并详细阐述了de Casteljau递推算法,该算法用于计算Bezier曲线上的点。通过Python代码展示了如何避免递归计算中的重复操作,提供了简化计算的方法。
879

被折叠的 条评论
为什么被折叠?



