為了把一串點連成光滑的曲線,先研究貝塞爾曲線,又搞B樣條插值。。。。都沒有成功(數學沒那么強)。
后來在
“[翻譯] AGG 之貝塞爾插值 ”http://liyiwen.javaeye.com/blog/705489 。看到一種比較好的方法:
運用貝塞爾曲線的光滑性來穿過這些點。
大致思路就是 先算出相鄰原始點的中點,在把相鄰中點連成的線段平移到對應的原始點,以平移后的中點作為控制點,相鄰原始點為起始點畫貝塞爾曲線,這樣就保證了連接處的光滑。而貝塞爾曲線本身是光滑的,所以就把這些原始點用光滑曲線連起來了。
我封裝了一個函數,留着以后用。
(c++版,其它語言只要把數組和可變數組稍微變一下就能用)
- void createCurve(CvPoint *originPoint,int originCount,vector<CvPoint> &curvePoint){
- //控制點收縮系數 ,經調試0.6較好,CvPoint是<a href="http://lib.csdn.net/base/opencv" class='replace_word' title="OpenCV知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>OpenCV</a>的,可自行定義結構體(x,y)
- float scale = 0.6;
- CvPoint midpoints[originCount];
- //生成中點
- for(int i = 0 ;i < originCount ; i++){
- int nexti = (i + 1) % originCount;
- midpoints[i].x = (originPoint[i].x + originPoint[nexti].x)/2.0;
- midpoints[i].y = (originPoint[i].y + originPoint[nexti].y)/2.0;
- }
- //平移中點
- CvPoint extrapoints[2 * originCount];
- for(int i = 0 ;i < originCount ; i++){
- int nexti = (i + 1) % originCount;
- int backi = (i + originCount - 1) % originCount;
- CvPoint midinmid;
- midinmid.x = (midpoints[i].x + midpoints[backi].x)/2.0;
- midinmid.y = (midpoints[i].y + midpoints[backi].y)/2.0;
- int offsetx = originPoint[i].x - midinmid.x;
- int offsety = originPoint[i].y - midinmid.y;
- int extraindex = 2 * i;
- extrapoints[extraindex].x = midpoints[backi].x + offsetx;
- extrapoints[extraindex].y = midpoints[backi].y + offsety;
- //朝 originPoint[i]方向收縮
- int addx = (extrapoints[extraindex].x - originPoint[i].x) * scale;
- int addy = (extrapoints[extraindex].y - originPoint[i].y) * scale;
- extrapoints[extraindex].x = originPoint[i].x + addx;
- extrapoints[extraindex].y = originPoint[i].y + addy;
- int extranexti = (extraindex + 1)%(2 * originCount);
- extrapoints[extranexti].x = midpoints[i].x + offsetx;
- extrapoints[extranexti].y = midpoints[i].y + offsety;
- //朝 originPoint[i]方向收縮
- addx = (extrapoints[extranexti].x - originPoint[i].x) * scale;
- addy = (extrapoints[extranexti].y - originPoint[i].y) * scale;
- extrapoints[extranexti].x = originPoint[i].x + addx;
- extrapoints[extranexti].y = originPoint[i].y + addy;
- }
- CvPoint controlPoint[4];
- //生成4控制點,產生貝塞爾曲線
- for(int i = 0 ;i < originCount ; i++){
- controlPoint[0] = originPoint[i];
- int extraindex = 2 * i;
- controlPoint[1] = extrapoints[extraindex + 1];
- int extranexti = (extraindex + 2) % (2 * originCount);
- controlPoint[2] = extrapoints[extranexti];
- int nexti = (i + 1) % originCount;
- controlPoint[3] = originPoint[nexti];
- float u = 1;
- while(u >= 0){
- int px = bezier3funcX(u,controlPoint);
- int py = bezier3funcY(u,controlPoint);
- //u的步長決定曲線的疏密
- u -= 0.005;
- CvPoint tempP = cvPoint(px,py);
- //存入曲線點
- curvePoint.push_back(tempP);
- }
- }
- }
- //三次貝塞爾曲線
- float bezier3funcX(float uu,CvPoint *controlP){
- float part0 = controlP[0].x * uu * uu * uu;
- float part1 = 3 * controlP[1].x * uu * uu * (1 - uu);
- float part2 = 3 * controlP[2].x * uu * (1 - uu) * (1 - uu);
- float part3 = controlP[3].x * (1 - uu) * (1 - uu) * (1 - uu);
- return part0 + part1 + part2 + part3;
- }
- float bezier3funcY(float uu,CvPoint *controlP){
- float part0 = controlP[0].y * uu * uu * uu;
- float part1 = 3 * controlP[1].y * uu * uu * (1 - uu);
- float part2 = 3 * controlP[2].y * uu * (1 - uu) * (1 - uu);
- float part3 = controlP[3].y * (1 - uu) * (1 - uu) * (1 - uu);
- return part0 + part1 + part2 + part3;
- }
翻譯] AGG 之貝塞爾插值
文章分類:綜合技術
原文地址:http://www.antigrain.com/research/
bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
Interpolation with Bezier Curves 貝塞爾插值
A very simple method of smoothing polygons 一種非常簡單的多邊形平滑方法
翻譯:唐風
之前 comp.graphic.algorithms 上有一個討論,是關於怎么樣使用曲線對多邊形進行插值處理,使得最終產生的曲線是光滑的而且能通過所有的頂點。Gernot Hoffmann 建議說使用著名的 B-Spline 來進行插值。這里有他當時的文章。B-Spline 在這里效果很好,它看起來就像是一個固定在多邊形頂點上的橡皮尺(elastic ruler)。
但我有個大膽的推測,我覺得肯定還存在更簡單的方法。比如,使用三次貝塞曲線(cubic Bezier)進行近似。貝塞爾曲線有兩個固定點(起點和終點),另加兩個決定曲線形狀的控制點(CP)。關於貝塞爾曲線的更多知識可以在搜索引擎中找到,比如,你可以參考 Paul Bourke 的站點 。 現在給貝塞爾曲線的錨點(固定點),也就是多邊形的某一對頂點,那么問題是,我們怎么計算控制點的位置?我運行 Xara X 然后畫出了右邊這個圖形,這很簡單,所以我決定嘗試下計算出它們的坐標。很顯然,多邊形兩條相鄰邊的兩個控制點與這兩個控制點之間的頂點應該在一條直線 上,只有這樣,兩條相鄰的插值曲線才能平滑地連接在一起。所以,這兩個控制點應該是相對於頂點是對稱的,不過,也不全是……,因為真正的對稱就要求它們與 中心點的距離應該是相等的,但對於我們的情況中並不完全是這樣的。一開始,我試着先算出多邊形兩條邊的角平分線,然后把控制點放在這條角平分線的垂直線 上。但從圖上可以看到,控制點的連線並不會總是垂直於角平分線的。
最終,我找到一個非常簡單的辦法,不需要任何復雜的數學計算。首先,我們計算出多邊形所有邊線的中點,Ai。
然后連接起相鄰邊中點,得到很多線段,記為 Ci 。並用圖記的方法計算出 Bi 點。
最后一步,只需要簡單地將 Ci 進行平移,平移的路徑就是每條線段上 Bi 到對應頂點的路徑。就這樣,我們計算出了貝塞爾曲線的控制點,平滑的結果看起來也很棒。
這里還可以做一點小小的改進,因為我們已經得到了一條決定控制點的直線,所以,我們可以根據需要,使控制點在這條直線上移動,這樣可以改變插值曲線 的狀態。我使用了一個與控制點和頂點初始距離相關的系數 K ,用來沿直線移動控制點。控制點離頂點越遠,圖形看起來就越銳利。
下面是用原始形狀和系統K=1.0的貝塞爾插值兩種方法來描畫的 SVG 的獅子。
下面是放大圖
這個方法對於自相關的多邊形也適用,下面的例子可以看到,結果非常有意思:
這個方法只是探索和經驗式的,如果從嚴格的數學模型的角度看它可能是錯誤的。但在實際使用中的效果已經足夠好了,而 且這個方法只需要最小的計算量。下面的代碼就是用來畫出上面獅子圖像的。這些代碼並沒有進行優化,只是用來演示的。里面有些變量計算了兩次,在實際程序 中,如果連續的步驟中都用到同一個變量值,我們可以先緩存變量值進行復用(以避免重復的計算)。
This method is pure heuristic and empiric. It probably gives
a wrong result from the point of view of strict mathematical
modeling. But in practice the result is good enough and it
requires absolute minimum of calculations. Below is the source code
that has been used to generate the lions shown above. It's
not optimal and just an illustration. It calculates some variables
twice, while in real programs we can store and reuse them in the
- // Assume we need to calculate the control
- // points between (x1,y1) and (x2,y2).
- // Then x0,y0 - the previous vertex,
- // x3,y3 - the next one.
- double xc1 = (x0 + x1) / 2.0;
- double yc1 = (y0 + y1) / 2.0;
- double xc2 = (x1 + x2) / 2.0;
- double yc2 = (y1 + y2) / 2.0;
- double xc3 = (x2 + x3) / 2.0;
- double yc3 = (y2 + y3) / 2.0;
- double len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
- double len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
- double len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
- double k1 = len1 / (len1 + len2);
- double k2 = len2 / (len2 + len3);
- double xm1 = xc1 + (xc2 - xc1) * k1;
- double ym1 = yc1 + (yc2 - yc1) * k1;
- double xm2 = xc2 + (xc3 - xc2) * k2;
- double ym2 = yc2 + (yc3 - yc2) * k2;
- // Resulting control points. Here smooth_value is mentioned
- // above coefficient K whose value should be in range [0...1].
- ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
- ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;
- ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
- ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
consecutive steps.
使用三次貝塞爾近似的代碼:
- // Number of intermediate points between two source ones,
- // Actually, this value should be calculated in some way,
- // Obviously, depending on the real length of the curve.
- // But I don't know any elegant and fast solution for this
- // problem.
- #define NUM_STEPS 20
- void curve4(Polygon* p,
- double x1, double y1, //Anchor1
- double x2, double y2, //Control1
- double x3, double y3, //Control2
- double x4, double y4) //Anchor2
- {
- double dx1 = x2 - x1;
- double dy1 = y2 - y1;
- double dx2 = x3 - x2;
- double dy2 = y3 - y2;
- double dx3 = x4 - x3;
- double dy3 = y4 - y3;
- double subdiv_step = 1.0 / (NUM_STEPS + 1);
- double subdiv_step2 = subdiv_step*subdiv_step;
- double subdiv_step3 = subdiv_step*subdiv_step*subdiv_step;
- double pre1 = 3.0 * subdiv_step;
- double pre2 = 3.0 * subdiv_step2;
- double pre4 = 6.0 * subdiv_step2;
- double pre5 = 6.0 * subdiv_step3;
- double tmp1x = x1 - x2 * 2.0 + x3;
- double tmp1y = y1 - y2 * 2.0 + y3;
- double tmp2x = (x2 - x3)*3.0 - x1 + x4;
- double tmp2y = (y2 - y3)*3.0 - y1 + y4;
- double fx = x1;
- double fy = y1;
- double dfx = (x2 - x1)*pre1 + tmp1x*pre2 + tmp2x*subdiv_step3;
- double dfy = (y2 - y1)*pre1 + tmp1y*pre2 + tmp2y*subdiv_step3;
- double ddfx = tmp1x*pre4 + tmp2x*pre5;
- double ddfy = tmp1y*pre4 + tmp2y*pre5;
- double dddfx = tmp2x*pre5;
- double dddfy = tmp2y*pre5;
- int step = NUM_STEPS;
- // Suppose, we have some abstract object Polygon which
- // has method AddVertex(x, y), similar to LineTo in
- // many graphical APIs.
- // Note, that the loop has only operation add!
- while(step--)
- {
- fx += dfx;
- fy += dfy;
- dfx += ddfx;
- dfy += ddfy;
- ddfx += dddfx;
- ddfy += dddfy;
- p->AddVertex(fx, fy);
- }
- p->AddVertex(x4, y4); // Last step must go exactly to x4, y4
- }
你可以下載一個能運行的畫獅子的例子,對它進行旋轉和縮放,也可以生成一些隨機的多邊形。點左鍵並拖動它可以圍繞中 心點旋轉和縮放圖像。點右鍵並從左向右拖動,可以改變系統數K。 K=1時大約是距窗口左邊100像素處。每次雙擊會產生一個隨機的多邊形,對於這些多邊形,也可以進行旋轉、縮放以及改變K值的操作。