Bezier曲线——de Casteljau递推算法实现

版权声明:欢迎转载和交流。 https://blog.csdn.net/Hachi_Lin/article/details/89052318

1. 定义

给定 n + 1 n+1 个点的位置矢量 P i ( i = 0 , 1 , , n ) P_i(i=0,1,\dots,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 i P_i (i=0,1,\dots,n)构成该Bezier曲线的特征多边形, B i , n ( t ) B_{i,n}(t) 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)
其中 0 0 = 1 , 0 ! = 1 0^0=1,0!=1

2. Bezier曲线的递推算法

  计算Bez曲线上的点,可用Bezier曲线方程直接计算,但使用de Casteljau提出的递推算法则简单得多。
  由 n + 1 n+1 个控制点 P i ( i = 0 , 1 , , n ) P_i(i=0,1,\dots,n) 定义的 n n 次Bezier曲线 P 0 n P_0^n 可被定义为分别由前、后 n n 个控制点定义的两条 n 1 n-1 次Bezier曲线 P 0 n 1 P_0^{n-1} P 1 n 1 P_1^{n-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]
由此得到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}

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.csdn.net/lafengxiaoyu/article/details/51296411
https://blog.csdn.net/lafengxiaoyu/article/details/56294678

猜你喜欢

转载自blog.csdn.net/Hachi_Lin/article/details/89052318