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

这篇博客介绍了Bezier曲线的基本定义,并详细阐述了de Casteljau递推算法,该算法用于计算Bezier曲线上的点。通过Python代码展示了如何避免递归计算中的重复操作,提供了简化计算的方法。
部署运行你感兴趣的模型镜像

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=0nPiBi,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(1t)ni=i!(ni)!n!ti(1t)ni,(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 n1次Bezier曲线 P 0 n − 1 P_0^{n-1} P0n1 P 1 n − 1 P_1^{n-1} P1n1的线性组合
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=(1t)P0n1+tP1n1,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,(1t)Pik1+tPi+1k1,k=0k=1,2,,n,i=0,1,,nk

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

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值