原文:Bezier曲線、B樣條和NURBS的基本概念
下面是一個有四個控制點的Bezier曲線:
可以通過改變一個控制點的位置來改變曲線的形狀,比如將上圖曲線中左邊第二個控制點往上移,就可以得到下面的曲線:
可以看到,這種曲線生成方式比較直觀和靈活,我只需要放置控制點,然后調整控制點的位置來得到想要的曲線,這就避免了和復雜的數學方程打交道,豈不快哉?
Bezier曲線、B樣條和NURBS都是根據控制點來生成曲線的,那么他們有什么區別了?簡單來說,就是:
Bezier曲線中的每個控制點都會影響整個曲線的形狀,而B樣條中的控制點只會影響整個曲線的一部分,顯然B樣條提供了更多的靈活性;
Bezier和B樣條都是多項式參數曲線,不能表示一些基本的曲線,比如圓,所以引入了NURBS,即非均勻有理B樣條來解決這個問題;
B樣條克服了Bezier曲線的一些缺點,Bezier曲線的每個控制點對整條曲線都有影響,也就是說,改變一個控制點的位置,整條曲線的形狀都會發生變化,而B樣條中的每個控制點只會影響曲線的一段參數范圍,從而實現了局部修改;
以下內容原文:二次與三次B樣條曲線c++實現
B樣條曲線從Bezier曲線演變而來,了解B樣條曲線首先得了解Bezier曲線。對於平面上的三個點P0,P1,P2,其坐標分別是(x0,y0)、(x1,y1)、(x2,y2)。二次Bezier曲線用一條拋物線進行擬合,其參數方程是
二次Bezier曲線有如下要求:(1)t=0時,曲線經過P0,並與P0P1相切;(2)t=1時,曲線經過P2,並與P1P2相切。即有
其中
,稱為二次Bezier曲線基函數。
每3個離散點形成一條Bezier曲線,由於每條Bezier都經過端點,導致分段的Bezier曲線在端點處難以平滑過渡。二次B樣條曲線克服了這個缺點,把端點移到線段中點(如下圖所示),這樣就能保證各段曲線在連接處能夠一階導數連續。
二次B樣條曲線實現,只需將曲線參數t划分成k等分,t從0開始取值,間隔dt,直至k*dt。如果想讓整條曲線兩端與起始點P0和終止點Pn重合,只需以P0和Pn為中點,構造新點PP1=2*P0-P1,與PP2=2*Pn-Pn-1,替換掉P0與Pn即可。
三次B樣條曲線通過樣條基函數可以得到如下形式:
三次B樣條具有以下物理意義,曲線起點S位於三角形P0P1P2的中線P1M1上,距離P1點1/3倍P1M1處;曲線中點E位於三角形P1P2P3的中線P2M2上,距離1/3倍P2M2處;曲線起點切線平行於P0P2,終點切線平行於P1P3.
三次B樣條曲線要想讓曲線兩端與起始端P0與Pn重合,只需構造新點PP1=2*P0-P1,與PP2=2*Pn-Pn-1,分別加到P0之前與Pn之后即可。由此,參與計算點的增加了2個(注意,二次B樣條是替換不是增加)。
程序實現了二次與三次B樣條曲線,封裝成了BSpline類
BSpine.h
1 #pragma once 2 //#include "position.h" 3 4 typedef struct tagPosition 5 { 6 double x; 7 double y; 8 tagPosition(double _x,double _y) { x=_x; y=_y;} 9 tagPosition() {}; 10 bool operator==(const tagPosition & pt) { return (x==pt.x && y==pt.y);} 11 } CPosition; 12 13 class CBSpline 14 { 15 public: 16 CBSpline(void); 17 ~CBSpline(void); 18 19 void TwoOrderBSplineSmooth(CPosition *pt,int Num); 20 void TwoOrderBSplineInterpolatePt(CPosition *&pt,int &Num,int *InsertNum); 21 double F02(double t); 22 double F12(double t); 23 double F22(double t); 24 25 void ThreeOrderBSplineSmooth(CPosition *pt,int Num); 26 void ThreeOrderBSplineInterpolatePt(CPosition *&pt,int &Num,int *InsertNum); 27 double F03(double t); 28 double F13(double t); 29 double F23(double t); 30 double F33(double t); 31 };
BSpine.cpp
1 //**************************** BSpline.cpp *********************************** 2 // 包含功能:二次B樣條平滑,三次B樣條平滑;二次B樣條平滑后節點插值 3 // 4 // 作者: 蔣錦朋 1034378054@qq.com 5 // 單位: 中國地質大學(武漢) 地球物理與空間信息學院 6 // 日期: 2014/12/03 7 //************************************************************************************* 8 #include "StdAfx.h" 9 #include "BSpline.h" 10 11 12 CBSpline::CBSpline(void) 13 { 14 } 15 16 17 CBSpline::~CBSpline(void) 18 { 19 } 20 //====================================================================== 21 // 函數功能: 二次B樣條平滑,把給定的點,平滑到B樣條曲線上,不增加點的數目 22 // 輸入參數: *pt :給定點序列,執行完成后,會被替換成新的平滑點 23 // Num:點個數 24 // 返回值: 無返回值 25 26 // 編輯日期: 2014/12/03 27 //====================================================================== 28 void CBSpline::TwoOrderBSplineSmooth(CPosition *pt,int Num) 29 { 30 CPosition *temp=new CPosition[Num]; 31 for(int i=0;i<Num;i++) 32 temp[i]=pt[i]; 33 34 temp[0].x=2*temp[0].x-temp[1].x; // 將折線兩端點換成延長線上兩點 35 temp[0].y=2*temp[0].y-temp[1].y; 36 37 temp[Num-1].x=2*temp[Num-1].x-temp[Num-2].x; 38 temp[Num-1].y=2*temp[Num-1].y-temp[Num-2].y; 39 40 CPosition NodePt1,NodePt2,NodePt3; 41 double t; 42 for(int i=0;i<Num-2;i++) 43 { 44 NodePt1=temp[i]; NodePt2=temp[i+1]; NodePt3=temp[i+2]; 45 if(i==0) // 第一段取t=0和t=0.5點 46 { 47 t=0; 48 pt[i].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 49 pt[i].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 50 t=0.5; 51 pt[i+1].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 52 pt[i+1].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 53 }else if(i==Num-3) // 最后一段取t=0.5和t=1點 54 { 55 t=0.5; 56 pt[i+1].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 57 pt[i+1].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 58 t=1; 59 pt[i+2].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 60 pt[i+2].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 61 }else // 中間段取t=0.5點 62 { 63 t=0.5; 64 pt[i+1].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 65 pt[i+1].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 66 } 67 } 68 delete []temp; 69 } 70 71 //================================================================ 72 // 函數功能: 二次B樣條擬合,在節點之間均勻插入指定個數點 73 // 輸入參數: *pt :給定點序列,執行完成后,會被替換成新的數據點 74 // Num:節點點個數 75 // *InsertNum: 節點之間需要插入的點個數指針 76 // 返回值: 無返回值 77 // 78 // 編輯日期: 2014/12/07 79 //================================================================= 80 void CBSpline::TwoOrderBSplineInterpolatePt(CPosition *&pt,int &Num,int *InsertNum) 81 { 82 if(pt==NULL || InsertNum==NULL) return; 83 84 int InsertNumSum=0; // 計算需要插入的點總數 85 for(int i=0;i<Num-1;i++) InsertNumSum+=InsertNum[i]; 86 87 CPosition *temp=new CPosition[Num]; // 二次B樣條不需要增加點數,需要將首尾點替換掉 88 for(int i=0;i<Num;i++) 89 temp[i]=pt[i]; 90 91 temp[0].x=2*temp[0].x-temp[1].x; // 將折線兩端點換成延長線上兩點 92 temp[0].y=2*temp[0].y-temp[1].y; 93 94 temp[Num-1].x=2*temp[Num-1].x-temp[Num-2].x; 95 temp[Num-1].y=2*temp[Num-1].y-temp[Num-2].y; 96 97 delete []pt; // 點數由原來的Num個增加到Num+InsertNumSum個,刪除舊的存儲空間,開辟新的存儲空間 98 99 pt=new CPosition[Num+InsertNumSum]; 100 101 CPosition NodePt1,NodePt2,NodePt3,NodePt4; // 兩節點間均勻插入點,需要相鄰兩段樣條曲線,因此需要四個節點 102 103 double t; 104 int totalnum=0; 105 for(int i=0;i<Num-1;i++) // 每條線段均勻插入點 106 { 107 if(i==0) // 第一段只需計算第一條樣條曲線,無NodePt1 108 { 109 NodePt2=temp[i]; NodePt3=temp[i+1]; NodePt4=temp[i+2]; 110 111 double dt=0.5/(InsertNum[i]+1); 112 for(int j=0;j<InsertNum[i]+1;j++) 113 { 114 t=0+dt*j; 115 pt[totalnum].x=F02(t)*NodePt2.x+F12(t)*NodePt3.x+F22(t)*NodePt4.x; 116 pt[totalnum].y=F02(t)*NodePt2.y+F12(t)*NodePt3.y+F22(t)*NodePt4.y; 117 totalnum++; 118 } 119 }else if(i==Num-2) // 最后一段只需計算最后一條樣條曲線,無NodePt4 120 { 121 NodePt1=temp[i-1]; NodePt2=temp[i]; NodePt3=temp[i+1]; 122 123 double dt=0.5/(InsertNum[i]+1); 124 for(int j=0;j<InsertNum[i]+2;j++) 125 { 126 t=0.5+dt*j; 127 pt[totalnum].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 128 pt[totalnum].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 129 totalnum++; 130 } 131 }else 132 { 133 NodePt1=temp[i-1],NodePt2=temp[i]; NodePt3=temp[i+1]; NodePt4=temp[i+2]; // NodePt1,2,3計算第一條曲線,NodePt2,3,4計算第二條曲線 134 135 int LeftInsertNum,RightInsertNum; // 計算線段間左右曲線段上分別需要插入的點數 136 double rightoffset=0; // 左邊曲線段從t=0.5開始,又邊曲線段從t=rightoffset開始 137 double Leftdt=0,Rightdt=0; // 左右曲線取點t步長 138 if(InsertNum[i]==0 ) 139 { 140 LeftInsertNum=0; 141 RightInsertNum=0; 142 }else if(InsertNum[i]%2==1) // 插入點數為奇數,左邊曲線段插入點個數比右邊多一點 143 { 144 RightInsertNum=InsertNum[i]/2; 145 LeftInsertNum=RightInsertNum+1; 146 Leftdt=0.5/(LeftInsertNum); 147 Rightdt=0.5/(RightInsertNum+1); 148 rightoffset=Rightdt; 149 }else // 插入點數為偶數,左右邊曲線段插入個數相同 150 { 151 RightInsertNum=InsertNum[i]/2; 152 LeftInsertNum=RightInsertNum; 153 Leftdt=0.5/(LeftInsertNum+0.5); 154 Rightdt=0.5/(RightInsertNum+0.5); 155 rightoffset=Rightdt/2; 156 } 157 158 for(int j=0;j<LeftInsertNum+1;j++) 159 { 160 t=0.5+Leftdt*j; 161 pt[totalnum].x=F02(t)*NodePt1.x+F12(t)*NodePt2.x+F22(t)*NodePt3.x; 162 pt[totalnum].y=F02(t)*NodePt1.y+F12(t)*NodePt2.y+F22(t)*NodePt3.y; 163 totalnum++; 164 } 165 166 for(int j=0;j<RightInsertNum;j++) 167 { 168 t=rightoffset+Rightdt*j; 169 pt[totalnum].x=F02(t)*NodePt2.x+F12(t)*NodePt3.x+F22(t)*NodePt4.x; 170 pt[totalnum].y=F02(t)*NodePt2.y+F12(t)*NodePt3.y+F22(t)*NodePt4.y; 171 totalnum++; 172 } 173 } 174 } 175 delete []temp; 176 Num=Num+InsertNumSum; 177 178 } 179 //================================================================ 180 // 函數功能: 二次樣條基函數 181 // 182 // 編輯日期: 2014/12/03 183 //================================================================ 184 double CBSpline::F02(double t) 185 { 186 return 0.5*(t-1)*(t-1); 187 } 188 double CBSpline::F12(double t) 189 { 190 return 0.5*(-2*t*t+2*t+1); 191 } 192 double CBSpline::F22(double t) 193 { 194 return 0.5*t*t; 195 } 196 //======================================================================== 197 // 函數功能: 三次B樣條平滑,把給定的點,平滑到B樣條曲線上,不增加點的數目 198 // 輸入參數: *pt :給定點序列,執行完成后,會被替換成新的平滑點 199 // Num:點個數 200 // 返回值: 無返回值 201 // 202 // 編輯日期: 2014/12/03 203 //======================================================================== 204 void CBSpline::ThreeOrderBSplineSmooth(CPosition *pt,int Num) 205 { 206 CPosition *temp=new CPosition[Num+2]; 207 for(int i=0;i<Num;i++) 208 temp[i+1]=pt[i]; 209 210 temp[0].x=2*temp[1].x-temp[2].x; // 將折線延長線上兩點加入作為首點和尾點 211 temp[0].y=2*temp[1].y-temp[2].y; 212 213 temp[Num+1].x=2*temp[Num].x-temp[Num-1].x; 214 temp[Num+1].y=2*temp[Num].y-temp[Num-1].y; 215 216 CPosition NodePt1,NodePt2,NodePt3,NodePt4; 217 double t; 218 for(int i=0;i<Num-1;i++) 219 { 220 NodePt1=temp[i]; NodePt2=temp[i+1]; NodePt3=temp[i+2]; NodePt4=temp[i+3]; 221 222 if(i==Num-4) // 最后一段取t=0.5和t=1點 223 { 224 t=0; 225 pt[i].x=F03(t)*NodePt1.x+F13(t)*NodePt2.x+F23(t)*NodePt3.x+F33(t)*NodePt4.x; 226 pt[i].y=F03(t)*NodePt1.y+F13(t)*NodePt2.y+F23(t)*NodePt3.y+F33(t)*NodePt4.y; 227 t=1; 228 pt[i+1].x=F03(t)*NodePt1.x+F13(t)*NodePt2.x+F23(t)*NodePt3.x+F33(t)*NodePt4.x; 229 pt[i+1].y=F03(t)*NodePt1.y+F13(t)*NodePt2.y+F23(t)*NodePt3.y+F33(t)*NodePt4.y; 230 }else // 中間段取t=0.5點 231 { 232 t=0; 233 pt[i].x=F03(t)*NodePt1.x+F13(t)*NodePt2.x+F23(t)*NodePt3.x+F33(t)*NodePt4.x; 234 pt[i].y=F03(t)*NodePt1.y+F13(t)*NodePt2.y+F23(t)*NodePt3.y+F33(t)*NodePt4.y; 235 } 236 } 237 delete []temp; 238 } 239 240 //================================================================ 241 // 函數功能: 三次B樣條擬合,在節點之間均勻插入指定個數點 242 // 輸入參數: *pt :給定點序列,執行完成后,會被替換成新的數據點 243 // Num:節點點個數 244 // *InsertNum: 節點之間需要插入的點個數指針 245 // 返回值: 無返回值 246 // 247 // 編輯日期: 2014/12/07 248 //================================================================= 249 void CBSpline::ThreeOrderBSplineInterpolatePt(CPosition *&pt,int &Num,int *InsertNum) 250 { 251 if(pt==NULL || InsertNum==NULL) return; 252 253 int InsertNumSum=0; // 計算需要插入的點總數 254 for(int i=0;i<Num-1;i++) InsertNumSum+=InsertNum[i]; 255 256 CPosition *temp=new CPosition[Num+2]; 257 for(int i=0;i<Num;i++) 258 temp[i+1]=pt[i]; 259 260 temp[0].x=2*temp[1].x-temp[2].x; // 將折線延長線上兩點加入作為首點和尾點 261 temp[0].y=2*temp[1].y-temp[2].y; 262 263 temp[Num+1].x=2*temp[Num].x-temp[Num-1].x; 264 temp[Num+1].y=2*temp[Num].y-temp[Num-1].y; 265 266 CPosition NodePt1,NodePt2,NodePt3,NodePt4; 267 double t; 268 269 delete []pt; // 點數由原來的Num個增加到Num+InsertNumSum個,刪除舊的存儲空間,開辟新的存儲空間 270 271 pt=new CPosition[Num+InsertNumSum]; 272 273 int totalnum=0; 274 for(int i=0;i<Num-1;i++) // 每條線段均勻插入點 275 { 276 NodePt1=temp[i]; NodePt2=temp[i+1]; NodePt3=temp[i+2]; NodePt4=temp[i+3]; 277 double dt=1.0/(InsertNum[i]+1); 278 279 for(int j=0;j<InsertNum[i]+1;j++) 280 { 281 t=dt*j; 282 pt[totalnum].x=F03(t)*NodePt1.x+F13(t)*NodePt2.x+F23(t)*NodePt3.x+F33(t)*NodePt4.x; 283 pt[totalnum].y=F03(t)*NodePt1.y+F13(t)*NodePt2.y+F23(t)*NodePt3.y+F33(t)*NodePt4.y; 284 totalnum++; 285 } 286 287 if(i==Num-2){ // 最后一個尾點 288 t=1; 289 pt[totalnum].x=F03(t)*NodePt1.x+F13(t)*NodePt2.x+F23(t)*NodePt3.x+F33(t)*NodePt4.x; 290 pt[totalnum].y=F03(t)*NodePt1.y+F13(t)*NodePt2.y+F23(t)*NodePt3.y+F33(t)*NodePt4.y; 291 totalnum++; 292 } 293 } 294 295 delete []temp; 296 Num=Num+InsertNumSum; 297 298 } 299 300 //================================================================ 301 // 函數功能: 三次樣條基函數 302 // 303 // 編輯日期: 2014/12/03 304 //================================================================ 305 double CBSpline::F03(double t) 306 { 307 return 1.0/6*(-t*t*t+3*t*t-3*t+1); 308 } 309 double CBSpline::F13(double t) 310 { 311 return 1.0/6*(3*t*t*t-6*t*t+4); 312 } 313 double CBSpline::F23(double t) 314 { 315 return 1.0/6*(-3*t*t*t+3*t*t+3*t+1); 316 } 317 double CBSpline::F33(double t) 318 { 319 return 1.0/6*t*t*t; 320 }
程序調用
1 #include "stdafx.h" 2 #include "math.h" 3 #include "BSpline.h" 4 5 int _tmain(int argc, _TCHAR* argv[]) 6 { 7 int num=8; 8 double x[8]={9.59,60.81,105.57,161.59,120.5,100.1,50.0,10.0}; 9 double y[8]={61.97,107.13,56.56,105.27,120.5,150.0,110.0,180.0}; 10 11 CPosition *testpt=new CPosition[num]; 12 for(int i=0;i<num;i++) testpt[i]=CPosition(x[i],y[i]); 13 14 int *Intnum=new int[num-1]; 15 for(int i=0;i<num-1;i++){ 16 Intnum[i]=10; // 每一個樣條曲線內插入10個點 17 } 18 19 int num2=num; 20 CBSpline bspline; 21 bspline.TwoOrderBSplineInterpolatePt(testpt,num2,Intnum); // 二次B樣條曲線 22 //bspline.ThreeOrderBSplineInterpolatePt(testpt,num2,Intnum); // 三次B樣條曲線 23 24 //bspline.TwoOrderBSplineSmooth(testpt,num2); // 二次B樣條平滑 25 //bspline.ThreeOrderBSplineSmooth(testpt,num2); // 三次B樣條平滑 26 delete Intnum; 27 28 29 FILE *fp_m_x = fopen("Bspline_test_x.txt", "wt"); 30 FILE *fp_m_y = fopen("Bspline_test_y.txt", "wt"); 31 for (int i = 0; i < num2; i++){ 32 fprintf(fp_m_x, "%lf\n", testpt[i].x); 33 fprintf(fp_m_y, "%lf\n", testpt[i].y); 34 } 35 fclose(fp_m_x); 36 fclose(fp_m_y); 37 38 return 0; 39 }










