C++實現二次、三次B樣條曲線


原文: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 };
View Code

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 }
View Code

程序調用

 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 }
View Code

 

更多可以看原文(我不用matlab):https://blog.csdn.net/jiangjjp2812/article/details/100176547?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM