贝塞尔曲线Bezier curve

现代机械设计方法,图形软件标准,样条曲线和曲面,图形变换

贝塞尔曲线

贝塞尔曲线简介

贝塞尔曲线(Bezier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。

特性:

  • 使用n+1个(有序)控制点{P0,P1,P2,...,Pn}来控制曲线的形状
  • 曲线经过起点P1和终点Pn,但不经过中间点P2Pn1
  • 贝塞尔曲线方程,方程的最高次数即是曲线的阶

Bez(t)=i=0nCniPi(1t)niti=Cn0P0(1t)nt0+Cn1P1(1t)n1t1++Cnn1Pn1(1t)1tn1+CnnPn(1t)0tn,t[0,1]

应用:

  • photoshop中的钢笔工具就是应用的三次贝塞尔曲线

贝塞尔曲线推导

  1. 二阶贝塞尔曲线的绘制

已知3个不共线的控制点A,B,C,线段ABBC上各找到DE2个动点,线段DE1个动点F,这3个动点满足 :

ADDB=BEEC=DFFE

这就是抛物线的三切线定理,最终形成的二级贝塞尔曲线(抛物线)被直线AB,BC,DE相切,切点为A,C,F

images/贝塞尔曲线Bezier curve-20240628181509108.webp

F的集合{F}就是二阶贝塞尔曲线,方程可由一阶贝塞尔曲线:1Bez(t)=(1t)P0+tP1t[0,1]推导出:

  • D(t)1=(1t)A+tB
  • E(t)1=(1t)B+tC
  • F(t)2=(1t)D(t)1+tE(T)1=(1t)2A+2(t1)B+t2C

上述方程使用点P代表其坐标的有序对(xy)T,似乎可以推广到三维空间

  1. n阶贝塞尔曲线的绘制
  • 已知n+1个不共线的控制点{P00,P10,P20,...,Pn0}
  • 相邻点连接成n条线段Pi0Pi+10,并在各个线段上找到1阶动点Pi1n个动点{P01,P11,P21,...,Pn11}
  • 相邻点连接成n1条线段Pi1Pi+11,并在各个线段上找到2阶动点Pi2n1个动点{P02,P12,P22,...,Pn22}
  • ……
  • 相邻点连接成的最后1条线段P0n1P1n1,并在线段上找到n阶动点Pin1个动点{P0n}
  • 这些动点{Pij}动点满足 :

    Pi0Pi1Pi1Pi+10=Pk1Pk2Pk2Pk+11==P0n1P0nP0nP1n1

  • 动点集合{P0n}就是n阶贝塞尔曲线

可视化例子

一阶(两个控制点),即直线,曲线方程为一次多项式

三阶(四个控制点),曲线方程为三次多项式:

Bez(t)=(1t)3P0+3t(1t)2P1+3t2(1t)P2+t3P3,t[0,1]

五阶(六个控制点),曲线方程为五次次多项式:

Bez(t)=(1t)5P0+5t(1t)4P1+10t2(1t)3P2+10t3(1t)2P3+5t4(1t)P4+t5P5,t[0,1]

代码实现

N=length(control_points);
ta=zeros(N,N);%%对数组进行初始化
%%杨辉三角左右两边的值赋1

%%贝塞尔曲线方程的系数
% 杨辉三角的数的规律
% 1
% 1 1
% 1 2 1
% 1 3 3 1
% 1 4 6 4 1
for i=1:N
    ta(i,1)=1;
    ta(i,i)=1;
end
%%从第二个数开始,也就是从第三行开始,等于前列的左边加上正上方的一个
for row=2:N
    for col=2:row
        ta(row,col)=ta(row-1,col-1)+ta(row-1,col);
    end
end

%%曲线生成
for i=1:M
    t=i/M;%%确定每一个点的比例
    for k=0:N-1
        c=k;%分别确定a,b,c三个系数
        b=N-c-1;%分别确定a,b,c三个系数
        a=ta(N,k+1);%分别确定a,b,c三个系数
             
        p(i,1)=p(i,1)+a*(1-t)^b*t^c*control_points(k+1,1);%确定点的x坐标
       
        p(i,2)=p(i,2)+a*(1-t)^b*t^c*control_points(k+1,2);%确定点的y坐标
   end
  
end
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import comb

def inputPoints():
	controlPoints = []
	num = 1
	while True:
		print('\nenter %dst control point:'%num)
		x = input('x:')
		y = input('y:')
		#z = input('z:')
		print('Point:[%f,%f]'%(float(x),float(y)))
		i = input('Are you sure?(y or n)')
		if i=='y' or i=='Y':
			controlPoints.append([float(x),float(y)])
			inp = input('continue entering points or not?(y or n)')
			num = num + 1
			if inp == 'n':
				break
		else:
			continue

	#print(controlPoints)
	return controlPoints

def getInterpolationPoints(controlPoints, tList):
	n = len(controlPoints)-1
	interPoints = []
	for t in tList:
		Bt = np.zeros(2, np.float64)
		for i in range(len(controlPoints)):
			Bt = Bt + comb(n,i) * np.power(1-t,n-i) * np.power(t,i) * np.array(controlPoints[i])
		interPoints.append(list(Bt))

	return interPoints

if __name__ == '__main__':
#	points = inputPoints()
	points = [[1,1],[3,4],[5,5],[7,2]]
	tList = np.linspace(0,1,50)
	interPointsList = getInterpolationPoints(points, tList)
	x = np.array(interPointsList)[:,0]
	y = np.array(interPointsList)[:,1]

	plt.plot(x,y,color='b')
	plt.scatter(np.array(points)[:,0],np.array(points)[:,1],color='r')
	plt.show()

B样条曲线

贝塞尔曲线方程是对n个控制点{Pi}施加权重函数W(t,n,i)后加和得到的,即:B(t)=i=0nWn,i(t)Pi
其中,Wn,i(t)=Cin(1t)niti

可以发现,单独的一个控制点对整体曲线影响较大,曲线阶次过高(阶次与控制节点数量相关)

本质上就是在控制点前增加一个权重函数,然后加和;对权重函数进行修改,并使得控制点仅仅能影响局部形状,这就是B样条曲线(basic spline curve)的设计思路

B样条曲线的数学描述

对于n+1个控制点P0,P1,,Pn,有一个包含m+1个节点的常数列表(或节点向量)t0,t1,,tm,其 kB样条曲线表达式为(且k=mn1>0

P(t)=i=0nWk,i(t)Pit[tk,tn)

其中,Wk,i(t)kB样条曲线的基函数(调和函数,权函数),并满足递推式:

k=0Bk,i(t)={1t[ti,ti+1)0otherwise

k>0Bk,i(t)=ttiti+ktiBk1,i(t)+ti+k+1tti+k+1ti+1Bk1,i+1(t)

显然,Wk,i(t)为一次函数,即高次B样条基函数为若干低次B样条基函数的线性组合。Ni,k(t)的次数k与控制节点的个数n无关,因此B样条曲线自由度更大

B样条曲线的次数指基函数多项式Bk,i(t)的最高次数

节点向量(kont vector)是一个一维单调非递减序列,元素ti称为节点(kont),区间[ti,ti+1)称为第 i 个节点区间(knot range),节点在样条曲线上的映射P(ti)称为曲节点(knot point)

在节点向量中,若某节点ti出现l次,则称ti是重复度为l的多重节点,否则为简单节点。与贝塞尔曲线不同,仅当B样条曲线首末节点重复度为k+1时,曲线本身才穿过首末控制点。

基函数Bk,i(t)在区间[ti,ti+k+1]上非零,因为该区间上总存在不为零的零阶基函数B0,i该区间称为支撑区间,对应样条曲线上的区段称为支撑曲线。由于Bk,i(t)直接与控制节点pi相乘,所以pi只影响其支撑区间[ti,ti+k+1]上对应支撑曲线的形状。

所以B样条曲线可视为若干段贝塞尔曲线的拼接,是贝塞尔曲线的推广,相邻贝塞尔曲线间存在若干重合节点,保留了对称性、几何不变性、变差伸缩性等优良特性。

images/贝塞尔曲线Bezier curve-20240621102154816.webp

B样条曲线分类

节点向量(kont vector)是一个一维单调非递减序列

根据节点分布的性质,可以将样条曲线进行分类

  • 均匀 B 样条:节点均匀分布,所有节点区间等长
  • 准均匀 B 样条:在开始和结束处的节点可重复,中间节点均匀分布;节点向量中的首末节点重复度为k+1,其余节点沿数轴方向等距均匀分布且重复度为1。当k=n时,B样条基函数Bk,i(t)退化为伯恩斯坦多项式,即B样条曲线退化为贝塞尔曲线。
  • 非均匀 B 样条:节点非均匀分布,可任意分布
  • 分段贝塞尔曲线
  • NURBS曲线:B样条无法描述圆锥曲线,为解决此问题,产生了非均匀有理B样条(non-uniform rational b-spline, NURBS)

(准/非)均匀B样条曲线

对于n+1个控制点P0,P1,,Pn,有一个包含m+1个节点的常数列表(或节点向量)t0,t1,,tm

k次均匀B样条曲线节点表达式为(且k=mn1>0
例如,令n=4m=9k=4,则knots=[01/92/93/94/95/96/97/98/91]={i/m} ,其中i=0,1,3,,m

k次准均匀B样条曲线节点表达式为(且k=mn1>0
例如,令n=4m=7k=2,则knots=[001/52/53/54/511]

k次非均匀B样条曲线节点表达式为(且k=mn1>0
例如,令n=4m=7k=2,则可令knots=[01/92.5/94/95.6/97/98/91] 或者knots=[002/94/95/9111]

代码实现

import numpy as np
import matplotlib.pyplot as plt

# 计算在某一特定t下的 B_{i,k}
def getBt(controlPoints, knots, t):
	# calculate m,n,k
	m = knots.shape[0]-1
	n = controlPoints.shape[0]-1
	k = m - n - 1
	# initialize B by zeros 
	B = np.zeros((k+1, m))

	# get t region
	tStart = 0
	for x in range(m+1):
		if t==1:
			tStart = m-1
		if knots[x] > t:
			tStart = x-1
			break
	 
	# calculate B(t)
	for _k in range(k+1):
		if _k == 0:
			B[_k, tStart] = 1
		else:
			for i in range(m-_k):
				if knots[i+_k]-knots[i]== 0:
					w1 = 0
				else:
					w1 = (t-knots[i])/(knots[i+_k]-knots[i]) 
				if knots[i+_k+1]-knots[i+1] == 0:
					w2 = 0
				else:
					w2 = (knots[i+_k+1]-t)/(knots[i+_k+1]-knots[i+1])
				B[_k,i] = w1*B[_k-1, i] + w2*B[_k-1, i+1]
	return B

# 绘制 B_{i,k}(t)函数
def plotBt(Bt,num, i,k):
	print(k,i)
	Bt = np.array(Bt)
	tt = np.linspace(0,1,num)
	yy = [Bt[t,k,i] for t in range(num)]
	plt.plot(tt, yy)

# 根据最后一列(最高阶次)的 B(t),即权重,乘以控制点坐标,从而求出曲线上点坐标
def getPt(Bt, controlPoints):
	Bt = np.array(Bt)
	ptArray = Bt.reshape(-1,1) * controlPoints
	pt = ptArray.sum(axis = 0)
	return pt

# 绘制出生成的样条曲线: useReg 表示是否使用曲线有效定义域[t_k, t_{m-k}]
def main1(useReg = False):
	controlPoints = np.array([[50,50], [100,300], [300,100], [380,200], [400,600]])
	knots = np.array([0,1/9,2/9,3/9,4/9,5/9,6/9,7/9,8/9,1])
	m = knots.shape[0]-1
	n = controlPoints.shape[0]-1
	k = m - n - 1
	print('n:',n)
	print('m:',m)
	print('k:',k)
    
	for t in np.linspace(0,1,100):
		if useReg and not(t >= knots[k] and t<= knots[n+1]):
			continue
		Bt = getBt(controlPoints, knots, t)
		Pt = getPt(Bt[k, :n+1], controlPoints)
		plt.scatter(Pt[0],Pt[1],color='b')
	plt.scatter(controlPoints[:,0], controlPoints[:,1],color = 'r')
	plt.show()

# 绘制 B_{i,k} 变化图:如果不给定{i,k}则显示所有B{i,k}(t)图像
def main2(i=-1,k=-1):
	controlPoints = np.array([[50,50], [100,300], [300,100], [380,200], [400,600]])
	knots = np.array([0,1/9,2/9,3/9,4/9,5/9,6/9,7/9,8/9,1])
	m = knots.shape[0]-1
	n = controlPoints.shape[0]-1
	k = m - n - 1
	print('n:',n)
	print('m:',m)
	print('k:',k)
	B = []
	num = 100 # 离散点数目
	for t in np.linspace(0,1,num):
		Bt = getBt(controlPoints, knots, t)
		B.append(list(Bt))

	figure1 = plt.figure('B_{i,k}')
	if i==-1:
		fig = []
		for i in range(n+1):
			for k in range(k+1):
				plotBt(B,num, i,k)
				fig.append('B_{%d,%d}'%(i,k))
	else:
		plotBt(B,num, i,k)
		fig.append('B_{%d,%d}'%(i,k))
	plt.legend(fig)
	plt.show()   
    
if __name__ == '__main__':
    main1()
    main2()

Nurbs曲线

ISO规定,PHIGS Plus的扩充部分,Bezier、有理Bezier、均匀B样条和非均匀B样条都被统一到NURBS 中。

B样条曲面、及其特例的Bezier曲面都不能精确表示除抛物面以外的二次曲面,而只能给出近似表示

在曲线曲面描述中,B样条方法更多地以非均匀类型出现,而均匀、准均匀、分段Bezier三种类型又被看成是非均匀类型的特例,所以习惯上称之为非均匀有理B样条(Non-Uniform Rational B-Splines)方法,简称为NURBS方法

NURBS方法提出的主要理由是,寻找与描述自由曲线曲面的B样条方法相统一的,而又能精确表示二次曲线曲面的数学方法

  • 非均匀B样条采用分段参数整数多项式,而NURBS方法采用分子分母分别是分段参数多项式函数与分段多项式的分式表示,是有理的
  • 与有理Bezier方法一样,NURBS方法引入了权因子和分母NURBS方法是在有理Bezier方法与非有理B样条方法的基础上发展起来的

NURBS的优点主要表现在以下几个方面:

  • 将初等曲线曲面与自由曲线曲面的表达方式统一起来
  • 增加了权因子,有利于对曲线曲面形状的控制和修改
  • 非有理B样条、有理与非有理Bezier方法是NURBS的特例
  • 在比例、旋转、平移、错切以及平行和透视投影变换下是不变的

NURBS曲线有三种表示方法:

  • 分式表示是有理的由来,它说明:NURBS曲线是非有理与有理Bézier和非有理B样条曲线的推广:但却难以从中了解更多的性质。
  • 在有理基函数表示形式中,可从有理基函数的性质清楚地了解NURBS曲线的性质。
  • 齐次坐标表示形式说明:NURBS曲线是它的控制顶点的齐次坐标或带权控制点在高一维空间里所定义的非有理B样条曲线在ω=1超平面上的投影。
  • 不仅包含了明确的几何意义,而且也说明:非有理B样条曲线的大多数算法都可以推广应用于NURBS曲线。

核心是其精心设计的数据结构,包括控制点网格、权重数组、 knot向量等,利用NumPy进行数值计算,NURBS-Python库具有高度的兼容性和性能,NURBS-Python支持多种格式的导入和导出,如IGES和STEP

矢量函数

一条p次NURBS曲线可以表示为有理矢量函数形式:C(u)=i=0nωiNi,p(u)Pii=0nωiNi,p(u)

式中,Pi是曲线的控制点,ωi是权因子,Ni,p(u)是定义在非周期节点矢量U上的pB样条基函数,Ni,p(u)定义为:

Ni,0(u)={1;uiuui+10;ohterwise

Ni,p(u)=uuiui+puiNi,p1(u)+ui+p+1uui+p+1ui+1Ni+1,p1(u)

节点矢量U为:U={a0,a1,,ap,up+1,,ump1,bmp,,bm},一般情况取a=0,b=1,其值单调不减,且ωi>0

为求简化,可令:

Ri,p(u)=ωiNi,p(u)j=0nωjNj,p(u),称{Ri,p(u)}为有理基函数

C(u)=i=0nPiRi,p(u),0u1,称u为分段有理函数

示例

7个控制点{P0,P1,P2,P3,P4,P5,P6},7个权重系数{ω0,ω1,ω2,ω3,ω4,ω5,ω6}={1,1,1,3,1,1,1},3次NURBS曲线,节点矢量U={0,0,0,0,14,12,34,1,1,1,1}

则可计算出Ni,p(u)或者Ri,p(u)

Frenet框架

曲线的曲率和挠率,切矢量、法矢量、次法矢量

作者:invo

出处:https://www.cnblogs.com/invo/p/18269599

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Invo1  阅读(223)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示